Hi everyone,
I'd like to work on handling C++ exceptions from Swift and discuss how the APIs should be imported before getting too far into the implementation.
Currently, Swift cannot catch a C++ exception that escapes into Swift code. To handle one, we need a C++ wrapper that catches the exception and reports an error back to Swift. I'd like the compiler to generate that adaptation so callers can use ordinary try and do/catch. Current interoperability support
One approach would be an annotation in <swift/bridging>, something like SWIFT_THROWS:
#include <swift/bridging>
int checkedDivide(int numerator, int denominator) SWIFT_THROWS;
Swift would import this as:
func checkedDivide(_ numerator: CInt, _ denominator: CInt) throws -> CInt
And callers could handle a failure normally:
do {
let result = try checkedDivide(42, 0)
print(result)
} catch {
print(error)
}
Unannotated APIs would keep their current behavior. Adding the annotation to an existing API would change its Swift interface, so library authors would need to account for callers now requiring try.
For the error representation, I'd start with a Swift type such as CxxException, containing a copied message: String. For exceptions derived from std::exception, that would come from what(). Other native C++ exceptions would use a fallback message.
This would let Swift callers handle the failure and read its message. Preserving the original C++ exception for type inspection or rethrowing would be a separate extension. I'd be interested in whether a message is enough for people's use cases, or whether losing the original exception would make this too limiting.
The implementation would catch the exception inside a generated C++ adapter, then propagate a Swift error. C++ would perform its normal cleanup inside that boundary, and Swift would use its existing throwing-call cleanup. Exceptions must not unwind through Swift frames.
I also found the earlier discussion about throws!, which seems worth revisiting here. The suggested behavior was:
// Hypothetical syntax:
func readValue() throws! -> Int
let a = readValue() // Implicit try!: trap on failure
let b = try readValue() // Propagate the error
let c = try? readValue() // Return nil on failure
That would allow existing calls to keep working while giving callers a way to opt into error handling, including for libraries whose headers they cannot annotate.
There are some language-design questions around that. For example, allowing a throws! method to satisfy a nonthrowing protocol requirement would need an explicit adaptation rule. A generated implementation could trap on failure, preserving the conformance, but calls through that protocol would still be nonthrowing. Adding try at the outer call site would not make the error recoverable. Function-value conversions would need rules too.
Another option I'd like to explore is an opt-in strict import mode, with a flag along these lines:
-cxx-exception-mode=strict
In that mode, supported C++ functions, methods, and constructors would import as ordinary throws unless Clang can establish that their exception specification is nonthrowing. noexcept functions would stay nonthrowing.
This would be a source-breaking option. Some automatic protocol conformances would no longer be possible because their requirements are nonthrowing. Potentially throwing operations that Swift cannot represent, such as throwing setters, would need to become unavailable.
Strict mode would also need to account for compiled Swift dependencies and the CxxStdlib overlay. That overlay contains nonthrowing conveniences backed by potentially throwing C++ operations. I'd initially keep strict mode experimental, require matching import policies in C++-enabled Swift dependencies, and omit incompatible Swift overlay conveniences while retaining access to raw C++ declarations.
These approaches could coexist: SWIFT_THROWS could require explicit handling for selected APIs, throws! could offer optional handling for unannotated imports, and strict mode could require handling across all supported explicit calls. I'd like to hear whether that combination makes sense or whether one approach should be the focus first.
I'll work on draft PRs to test the implementation and its behavior. My plan is to keep them small, starting with the exception bridge and free/static functions, then adding instance methods and value-type constructors separately. I'd initially test on Darwin and Linux behind an experimental feature flag, with coverage for cleanup and partially initialized objects.
Implicit copies and destructors would retain their existing behavior in the initial implementation. A violated noexcept contract would still terminate. Objective-C exceptions and runtime forced unwinding would remain outside the recovery supported by this feature.
For anyone involved in the previous discussions, has the direction changed since then? I'd particularly like feedback on whether SWIFT_THROWS is a reasonable first step, or whether it would be better to settle the throws! semantics before introducing an annotation.