When importing C++ codebases, I propose that the Swift compiler transform C++ APIs into specific, well-known, safe patterns that feel native in Swift. In order to make sure every C++ API is imported in this way, the Swift compiler cannot import all APIs and must use annotations, API notes, and flags to determine how best to map a given C++ API.
To accommodate this new direction, and these goals for the importer, how we develop interop must change. Rather than trying to import everything all at once, with a “best-effort” attitude, we should find specific mappings for each C++ API pattern. For implementation, this means focusing on one pattern at a time, and not importing APIs that don’t fit into any of the defined patterns. Initially, I propose that we start with the following patterns, but we can add more in the future.
(Each of the below patterns will need its own pitch and evolution post outlining the pattern in more detail.)
Trivial types: trivial types have no special members except for constructors. They are the types from C and Objective-C that can already be imported. Pointers are not trivial types. These types can be automatically imported. (Note: “trivial” types are also allowed to hold Objective-C classes as members.)
Iterators: iterators are unsafe in C++. You can easily create a dangling reference or iterate past the end of the range. Iterators will be imported through a safe, native Swift interface that checks bounds and manages ownership. For example:
std::vector ’s begin and end methods will not be directly callable in Swift, instead they will be transformed into a native Swift iterator/collection pattern, so the vector may be used in for-loops, etc. Iterators can be automatically imported using begin and end methods, or manually imported using annotations.
Owned types: owned types manage their storage using copy constructors and destructors. If they allocate storage, they must destroy it when the destructor is invoked, and copy the storage when the copy constructor is invoked. Their special members must not have side effects and copies/destroys must balance out. Examples of owned types include
std::vector . Owned types must be annotated as such in order to be imported.
Foreign reference types (FRT): FRTs are a way to express non-Swift, non-Objective-C class types. FRTs must be reference counted or marked as “immortal” to maintain safety. FRTs must have reference semantics and object identity. FRTs must always be trafficked with at least one layer of indirection in C++ (i.e., FRTs must not be returned by value). FRTs must be annotated as such in order to be imported.
Unsafe escape hatch: If developers need to use other APIs, we can consider an unsafe escape hatch where the Swift compiler will do a “best-effort” import of the unsafe API. This will need to be developed further, and will likely be accompanied by warnings or an “unsafe” prefix.
These four patterns will provide a set of ergonomic, safe APIs that can be used to test and adopt interop. With this strategy, the bounds of interop will be well defined and every importable API will have a clear mapping. This will improve stability and usability greatly. Additionally, in terms of development, these goals will allow us to have clear objectives to work toward.