This pitch is a refinement of my CppReference
pitch, making the relevant functionality both more integrated into Swift as well as clarifying the relationship to import:reference
. For those of you who I've been discussing this with, there might be some rehashing here but I felt it would be useful to try to put the whole story in one place.
There has been much discussion of how Swift could import C++ types with customized copy behavior, such as move-only types. So far, the main tool for working with such type is the import:reference
annotation, which tells the C++ importer that a particular type has Swift class
semantics and should be imported as such. import:reference
is tailor-made for connecting ARC with C++ types that have retain/release semantics (indeed it requires specifying a retain and release function for your type). However, early adopters of C++ interop are currently using it more generally to work around the importer's inability to import types with customized value behavior, such as move-only types (this is evidenced by the common usage of retain:immortal
and release:immortal
as a hack to opt-out of retain/release semantics).
Currently, the path forward has been advertised to me roughly as "Swift will get move-only types eventually, and at that point we can update C++ interop to handle such types". There is an opportunity to unlock many of these use cases prior to the implementation of move-only types in Swift, and to do so in a way that will cover use cases we probably never want to be valid in Swift. The core of this approach is to enable the use of UnsafePointer
and friends to interop with C++ in much the same way we currently use that family of types to interop with C.
In my experience bringing up Swift support in MLIR (part of the LLVM project) and CIRCT (an LLVM incubator project), there are many different types that the current C++ importer fails to import. For most of these types, the fact that they are move-only (or have other customized value semantics) is not directly relevant to their API. By this I mean the value semantics don't affect how developers interact with these types. For most of them, the API consists of instantiating this type (likely on the heap) and then only ever interacting with it through a pointer. Even for those where eventually we may want to interact with the value itself (as opposed to a pointer), it isn't necessarily the case that the type matches Swift's class
semantics (i.e. is retain/release) or that it will match the eventual move-only or borrow-based semantics we come up with for Swift. As evidenced by the existence of Unmanaged
in Swift, even Swift types with retain/release semantics may require an escape hatch from ARC in certain special cases. I posit that the same will be true for imported C++ types.
I suggest a three-part solution:
- Implement a
.new
static method onUnsafePointer<T>
whenT
is an imported C++ type. This method will behave the same as callingnew T(args)
in C++, but from Swift (adelete
method also makes sense). Note: it is likely we will needpointee
to become unavailable in the case a type cannot be represented in Swift, sinceT
in this case would likely have the same semantics asNever
. - Import C++ methods directly on
UnsafePointer<T>
when that API can operate on an instance ofT
by reference. - Allow passing of
UnsafePointer
and friends as an argument to C++ methods when the argument is a reference (I believe something similar to this is already the case for C interop).
This way, we can directly interact with unimportable types in the short term, exactly how we would in C++ and with no need to annotate the source or add API notes. As types become importable source code can be updated on the developer's time frame to use a newly-importable T
instead of UnsafePointer<T>
. Going forward, as Swift finalizes semantics for borrows and move-only types, we can use UnsafePointer
as an escape hatch where Swift's semantics don't support certain use cases (since Swift prioritizes progressive disclosure and safety more than C++, I think it is safe to expect Swift's version of these semantics to be more restrictive than C++).
Regarding import:reference
, there will still be a need to mark certain types as import:reference
when they match the semantics of Swift's class
, but it will no longer be necessary to do this for types with customized value semantics that have pointer-based APIs.
I'd love to hear your thoughts!