chinedufn / swift-bridge

swift-bridge facilitates Rust and Swift interop.
https://chinedufn.github.io/swift-bridge
Apache License 2.0
842 stars 62 forks source link

Support `#[swift_bridge(Sendable)]` to implement `Rust Send+Sync` for `Swift Sendable` types #269

Open chinedufn opened 7 months ago

chinedufn commented 7 months ago

Right now when generating the Rust representation of a Swift type we create a non-Send and non-Sync type.

#[swift_bridge::bridge]
mod ffi {
    extern "Swift" {
        type SomeSwiftType;
    }
}

// Generated code
// This DOES NOT implement Send or Sync
struct SomeSwiftType(*mut std::ffi::c_void);

If the user knows that SomeSwiftType is Sendable then they should be able to Send + Sync on the Rust side.

#[swift_bridge::bridge]
mod ffi {
    extern "Swift" {
        #[swift_bridge(Sendable)]
        type AnotherSwiftType;
    }
}

// Generated Rust code
struct AnotherSwiftType(std::ffi::c_void);
unsafe impl Send for AnotherSwiftType {}
unsafe impl Sync for AnotherSwiftType {}

Then it would generate Swift code like:

// This would appear once in `SwiftBridgeCore.swift`
func __assert_sendable<__SwiftType: Sendable>() {}

// This is auto-generated.
// It confirms that `AnotherSwiftType` does in fact
// implement Swift's `Sendable` protocol.
func check_AnotherSwiftType_is_Sendable() {
    __assert_sendable::<AnotherSwiftType>()
}
chinedufn commented 7 months ago

Implementation Guide

Check out the contributing guide https://chinedufn.github.io/swift-bridge/contributing/adding-support-for-a-signature/index.html


Here's a pull-request where we implemented support for Swift's Equatable protocol via #[swift_bridge(Equatable)].

It should serve as a guide for implementing #[swift_bridge(Sendable)] support.

https://github.com/chinedufn/swift-bridge/pull/139


unsafe impl Send for MySwiftType {} and unsafe impl Sync for MySwiftType {} would go here https://github.com/chinedufn/swift-bridge/blob/dd5bef56af28db4f1a1244d86115def6abede931/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs#L236-L248

let impl_send_sync = if ty.attributes.sendable {
    quote! {
        unsafe impl Send for #ty_name {}
        unsafe impl Sync for #ty_name {}
    }
} else {
    quote!{}
};

The Swift XCode test can call a Rust function that:

  1. Creates an instance of the Swift type
  2. Sends the instance to another thread

Example of an integration test:

https://github.com/chinedufn/swift-bridge/blob/930866e39d89af885d483b4f1efbbe16275ecd0a/crates/swift-integration-tests/src/swift_function_uses_opaque_rust_type.rs#L22-L36

https://github.com/chinedufn/swift-bridge/blob/930866e39d89af885d483b4f1efbbe16275ecd0a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SwiftFnUsesOpaqueRustTypeTests.swift#L22-L24


Here's where SwiftBridgeCore.swift is generated https://github.com/chinedufn/swift-bridge/blob/7e140ca5e6ec3f969c19001c844f98fe76bd787c/crates/swift-bridge-build/src/generate_core.rs#L21-L32