diff --git a/documentation/cxx-interop/index.md b/documentation/cxx-interop/index.md index 3c854dc76..cd4e44d2c 100644 --- a/documentation/cxx-interop/index.md +++ b/documentation/cxx-interop/index.md @@ -1239,8 +1239,8 @@ To specify that a C++ type is a shared reference type, use the `SWIFT_SHARED_REF class SharedObject : IntrusiveReferenceCounted { public: SharedObject(const SharedObject &) = delete; // non-copyable - - static SharedObject* create(); + SharedObject(); + static SharedObject* _Nonnull create() SWIFT_RETURNS_RETAINED; void doSomething(); } SWIFT_SHARED_REFERENCE(retainSharedObject, releaseSharedObject); @@ -1248,14 +1248,122 @@ void retainSharedObject(SharedObject *); void releaseSharedObject(SharedObject *); ``` +In the example above, the `SWIFT_RETURNS_RETAINED` annotation specifies that the returned value is passed with `+1` ownership. +For more details, see [Calling conventions when returning Shared Reference Types from C++ to Swift](#calling-conventions-when-returning-shared-reference-types-from-c-to-swift). + Now that `SharedObject` is imported as a reference type in Swift, the programmer will be able to use it in the following manner: ```swift -let object = SharedObject.create() -object.doSomething() +let object1 = SharedObject.create() +let object2 = SharedObject() // The C++ constructor is imported as a Swift initializer +object1.doSomething() +object2.doSomething() // `object` will be released here. ``` -### Inheritance and Virtual Member Functions +#### Constructing objects of Shared Reference Types from Swift + +As demonstrated in the provided example, starting from Swift 6.2, you can create instances of `SWIFT_SHARED_REFERENCE` types by invoking their initializers. +Note that the Swift compiler uses the default `new` operator to construct C++ shared reference types. + +You can also import a user-defined C++ static factory function as a Swift initializer by annotating it with `SWIFT_NAME("init(…)")` annotation macro, ensuring that the number of underscore placeholders matches the number of parameters in the factory function. +For example: + +```cpp +struct SharedObject { + static SharedObject* make(int id) SWIFT_NAME("init(_:)"); + + void doSomething(); +} SWIFT_SHARED_REFERENCE(retainSharedObject, releaseSharedObject); +``` + +In this case, Swift will import the static `make` function as a Swift initializer: + +```swift +let object = SharedObject(42) +``` + +Note that if a C++ constructor and a user-annotated static factory (using `SWIFT_NAME`) have identical parameter signatures, Swift favors the static factory when resolving initializer calls. +This is particularly useful when you want to use a custom allocator or want to disable direct construction entirely and expose only factories. + +#### Inference of Shared Reference behaviour in Derived Types + +When a C++ type inherits from a `SWIFT_SHARED_REFERENCE` base type, the Swift compiler automatically infers `SWIFT_SHARED_REFERENCE` annotation for the derived type. +The derived type also gets imported as a reference type, and uses the same `retain` and `release` functions as its base class. +This inference works as long as all the annotated base types in the inheritance chain (including multiple or indirect inheritance) have the same `retain` and `release` functions. +If multiple base types have conflicting `retain` or `release` functions, the derived type is imported as a Swift value type, and the compiler emits a warning. + + +Note that this inference currently applies only to `SWIFT_SHARED_REFERENCE`. +It does not apply to types annotated with `SWIFT_IMMORTAL_REFERENCE` or `SWIFT_UNSAFE_REFERENCE`. + +#### Calling conventions when returning Shared Reference Types from C++ to Swift + +When C++ functions and methods return `SWIFT_SHARED_REFERENCE` types, it is necessary to specify the ownership of the returned value. +For this you should use the `SWIFT_RETURNS_RETAINED` and `SWIFT_RETURNS_UNRETAINED` annotations on functions and methods. +These annotations tell the Swift compiler whether the type is returned as `+1` (retained) or `+0` (unretained). + +```c++ +// Returns +1 ownership. +SharedObject* _Nonnull makeOwnedObject() SWIFT_RETURNS_RETAINED; + +// Returns +0 ownership. +SharedObject* _Nonnull getUnOwnedObject() SWIFT_RETURNS_UNRETAINED; +``` + +These annotations are necessary to ensure that appropriate `retain`/`release` operations are inserted at the boundary: + +```swift +let owned = makeOwnedObject() +owned.doSomething() +// `owned` is already at +1, so no further retain is needed here + +let unOwned = getUnOwnedObject() +// Swift inserts a retain operation on `unowned` here to bring it to +1. +unOwned.doSomething() +``` + +Note that the Swift compiler will automatically infer the ownership conventons for Swift functions returning `SWIFT_SHARED_REFERENCE` types. +See [Exposing C++ Shared Reference Types back from Swift](#exposing-c-shared-reference-types-back-from-swift) for calling Swift functions returning `SWIFT_SHARED_REFERENCE` types from C++. + +#### Calling conventions when passing Shared Reference Types from Swift to C++ + +If a C++ shared reference type is passed as an argument to a C++ API from Swift, the Swift compiler guarantees that the passed value would be alive. +Swift also retains the ownership of the value. +In other words, the argument is passed at `+0` and there is no transfer of ownership. +The C++ function should not assume that it has the ownership of the value and should do necessary retain operations if it is needs to take ownership. +The C++ function is responsible for ensuring that the value pointed to by the parameter is alive during and at the end of the function call. + + +```swift +var obj = SharedObject.create() +receiveSharedObject(obj) // Swift guarantees that obj is alive and it is passed at +0 +``` + +```c++ +void receiveSharedObject(SharedObject *sobj) { + ... + // Swift assumes that sobj is a valid, non-null object at the end of this function +} +``` + +Note that if the argument is an inout (non-const reference) as shown below: + +```c++ +void takeSharedObjectAsInout(SharedObject *& x) { ... } +``` + +which would be imported in Swift as + +```swift +func takeSharedObjectAsInout(_ x: inout SharedObject) { ... } +``` + +The C++ function can overwrite the value of the argument with the new value. +However, the C++ function is responsible for releasing the old value, and ensuring that the new value is properly retained so that the Swift caller has ownership of the new value when the function returns. +Adhering to these rules is necessary to safely and correctly pass around `SWIFT_SHARED_REFERENCE` between Swift and C++. +These rules are also generally recommended conventions to manage shared objects that use reference counting. + +#### Inheritance and Virtual Member Functions Similar to value types, casting an instance of a derived reference type to a base reference type, or vice versa, is not yet supported by Swift.