Skip to content

Commit 04446c8

Browse files
committed
Add a section on exceptions to the C++ interop manifesto.
1 parent 0eeefae commit 04446c8

File tree

1 file changed

+113
-6
lines changed

1 file changed

+113
-6
lines changed

docs/CppInteroperabilityManifesto.md

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ Assumptions:
8080
* [`operator[]`](#operator-square-brackets)
8181
* [`operator()`](#operator-parentheses)
8282
* [Literal operators](#literal-operators)
83+
* [Exceptions](#exceptions)
8384
* [Atomics](#atomics)
8485
* [Importing non-const pointer as extra return values](#importing-non-const-pointer-as-extra-return-values)
8586
- [Enhancing C++ API mapping into Swift with bridging](#enhancing-c-api-mapping-into-swift-with-bridging)
@@ -932,16 +933,19 @@ value types, move-only, or non-movable.
932933

933934
If C++ classes are imported in Swift as structs, there seems to be no reasonable
934935
way to map exceptions thrown from special member functions to an error that can
935-
be handled in Swift. Special member functions will be used to implement value
936-
witnesses, which are called by the compiler implicitly. Therefore, either such
937-
exceptions have to be mapped to fatal errors, or calls to such special member
936+
be handled in Swift.
937+
938+
While it is possible to [propagate C++ exceptions](#exceptions) thrown by normal
939+
member functions to Swift code, special member functions are different as they
940+
used used to implement value witnesses, which are called by the compiler
941+
implicitly. Therefore, either such exceptions have to be mapped to fatal errors
942+
(as we do for other unhandled C++ exceptions), or calls to such special member
938943
functions must be prevented statically.
939944

940945
Preventing such calls statically seems difficult. Also, in practice, special
941946
member functions throw mostly due to out-of-memory conditions, which is
942-
considered unrecoverable in Swift. Therefore, the practical solutions are to
943-
either map exceptions from special member functions to fatal errors, or compile
944-
C++ code with `-fno-exceptions`.
947+
considered unrecoverable in Swift. Therefore, the practical solution is to
948+
map exceptions from special member functions to fatal errors.
945949

946950
If the user must use a C++ class with throwing special member functions from
947951
Swift, a reasonable workaround would be to write a wrapper that exposes these
@@ -2857,6 +2861,109 @@ This mapping rule would handle the call operator of `std::function`,
28572861

28582862
TODO
28592863

2864+
## Exceptions
2865+
2866+
### Background
2867+
2868+
C++ unfortunately has the opposite default to Swift where exception throwing is
2869+
concerned. Any C++ function that is not explicitly marked `noexcept` must be
2870+
assumed to be potentially throwing, even though many such functions don't throw
2871+
in practice.
2872+
2873+
If we imported all of these functions into Swift as `throws` functions, the code
2874+
calling them would be littered with `try!`s. Requiring all imported functions (and
2875+
everything they call transitively) to be marked `noexcept` also seems excessively
2876+
burdensome and is not idiomatic C++.
2877+
2878+
### Baseline functionality: Import functions as non-throwing, terminate on uncaught C++ exceptions
2879+
2880+
In the first iteration of C++ interop, we will import all C++ functions as
2881+
non-throwing Swift functions. If a C++ function called from Swift throws an
2882+
exception that is not caught within the C++ code, the program will terminate.
2883+
2884+
This approach is similar to that taken by the [Python interop
2885+
library](https://github.com/pvieito/PythonKit/blob/master/PythonKit/Python.swift),
2886+
which also terminates the program by default if a Python exception is raised and
2887+
not caught within the Python code.
2888+
2889+
If exceptions thrown in C++ need to be handled in Swift, this can be done by
2890+
writing a C++ wrapper that catches the exception and returns a corresponding
2891+
error object.
2892+
2893+
### Extended functionality: Optionally propagate exceptions to Swift
2894+
2895+
If we find that developers routinely need to handle C++ exceptions in Swift,
2896+
writing C++ wrappers as described above will obviously not be a satisfactory
2897+
solution. In this case, we will add an option to propagate C++ exceptions as
2898+
Swift exceptions; this will be backward compatible with the baseline approach
2899+
described above.
2900+
2901+
We again take inspiration from Python interop. There, appending `.throwing` to
2902+
the function name calls a variant of the function that propagates the Python
2903+
exception to Swift.
2904+
2905+
It isn't really practical to do something exactly analogous in C++ interop. We
2906+
could import each function twice and identify the throwing version by a name
2907+
suffix or a dummy parameter. However, this solution would bloat the interface of
2908+
imported classes, and it would not be universal: Name suffixes can't be used
2909+
with constructors or operators; a dummy parameter could be used with most
2910+
constructors, but still doesn't work for default constructors or operators.
2911+
2912+
Instead, we propose extending Swift with a `throws!` marker. A function marked
2913+
with `throws!` can potentially throw exceptions, but the function does not need
2914+
to be called with `try`. If the function _is_ called with `try`, exceptions are
2915+
handled as they would be for a normal throwing function. If the function is not
2916+
called with `try` and the function raises an exception, the program is
2917+
terminated. These semantics are close to C++ exception handling semantics.
2918+
2919+
All C++ functions that are not marked `noexcept` would be imported as `throws!`
2920+
functions; `noexcept` functions would be imported as non-throwing functions.
2921+
2922+
This brief sketch obviously leaves many questions unanswered on the detailed
2923+
semantics that a `throws!` feature would have, for example whether user-written
2924+
Swift code should be allowed to use `throws!` -- see also [this forum
2925+
discussion](https://forums.swift.org/t/handling-c-exceptions/34823). Before we
2926+
take any steps towards implementing C++ exception proagation, we will submit a
2927+
formal Swift Evolution proposal for the `throws!` feature.
2928+
2929+
The other question to answer is how we would map the C++ exception to a
2930+
corresponding Swift object implementing `Error`. In theory, C++ allows
2931+
objects of any copy-initializable type to be thrown. In practice, most
2932+
user-defined C++ exceptions derive from `std::exception`, so it would be natural
2933+
to propagate C++ exceptions as the Swift-imported equivalent of
2934+
`std::exception` and make that type implement `Error`. We could add a separate
2935+
fallback error type (e.g. `CxxUnknownException`) for the case where the
2936+
exception does not derive from `std::exception`.
2937+
2938+
As `std::exception` is a polymorphic type, the details of how `std::exception`
2939+
will be represented in Swift will need to wait until we have finalized how
2940+
polymorphism is handled (see also the section on [virtual member
2941+
functions](#virtual-member-functions)).
2942+
2943+
### Implementation
2944+
2945+
Many common C++ ABIs allow the default behavior (terminate the program if a
2946+
C++ exception is thrown) to be implemented at zero cost. In particular, it is not
2947+
necessary to wrap every C++ function in a synthesized try-catch block.
2948+
2949+
For example, the Itanium C++ ABI defines a so-called personality routine that is
2950+
called for each stack frame as the stack is being unwound. The idea is that
2951+
different languages can have different personality routines, so that different
2952+
languages can co-exist on the stack and can each define their own stack frame
2953+
cleanup logic.
2954+
2955+
The stack unwinding infrastructure finds the correct personality routine by
2956+
consulting a so-called unwind table, which maps program counter ranges to
2957+
personality routines.
2958+
2959+
We would define unwind table entries covering all Swift code that does not
2960+
attempt to handle C++ exceptions and have these entries map to a personality
2961+
routine that simply terminates the program.
2962+
2963+
If exception support is turned off in the C++ compiler by passing `-Xcc
2964+
-fno-exceptions` to `swiftc`, we assume that the C++ code never throws
2965+
exceptions and will not emit any unwind table entries for Swift code.
2966+
28602967
## Atomics
28612968

28622969
TODO

0 commit comments

Comments
 (0)