Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Swift 6] Async functions results in compile errors about data races #2274

Open
ThomasCle opened this issue Oct 15, 2024 · 5 comments
Open

Comments

@ThomasCle
Copy link

ThomasCle commented Oct 15, 2024

I am currently upgrading my project to Swift 6 and have solved a lot of the concurrency warnings with the experimental_sendable_value_types = true for values-types.
I have a couple of struct implementations with async functions, which is exported with #[derive(uniffi::Object)], so they are generated as aclass in Swift.
When awaiting those async functions in Swift I get this error:

Sending 'self'-isolated value of type 'any StoreHandlerProtocol' with later accesses to nonisolated context risks causing data races.

This is the generated Swift code for the protocol:

public protocol StoreHandlerProtocol : AnyObject {

    func stores() async throws  -> [Store]

}

And this is how I call the StoreHandler class implementation:

let storeHandler: StoreHandlerProtocol

...

private func updateSelectedStore(from selectedStoreId: SelectedStoreIdStorageDTO) async {
    var stores: [Store] = []
    do {
        stores = try await self.storeHandler.stores() // <---  ⛔ Sending 'self'-isolated value of type 'any StoreHandlerProtocol' with later accesses to nonisolated context risks causing data races.
        ...
    } catch {
         ...
    }
}

I believe the compile-error would disappear if the StoreHandlerProtocol implemented Sendable and the StoreHandler class-implementation would be defined as @unchecked Sendable. But I am not sure that is the right solution, though.

Is there any way I can work around this problem and are you even considering this as a issue that should be solved in uniffi?

@mhammond
Copy link
Member

We do have a few issues about swift 6 support, but don't really have any swift experts as regular contributors. So I think it's safe to say that we would consider this an issue because it should really be impossible for uniffi to generate code which causes a compile error, but it's not something any of the regular contributors are either working on or even understand in detail.

@ThomasCle
Copy link
Author

ThomasCle commented Oct 16, 2024

I've been working on a work-around and managed to remove the compiler error by using a Swift wrapper making it @unchecked Sendable. It is definitely not ideal and not the approach uniffi should be using.

But as long as you aren't mutating state in the Rust implementations you should be good with this wrapper solution in Swift:

struct UncheckedSendableRustClass<T>: @unchecked Sendable {
    private let instance: T
    
    init(_ instance: T) {
        self.instance = instance
    }
    
    func callAsFunction() -> T {
        instance
    }
}

It allows me to refactor the original example into this:

let storeHandler: UncheckedSendableRustClass<StoreHandlerProtocol>

...

private func updateSelectedStore(from selectedStoreId: SelectedStoreIdStorageDTO) async {
    var stores: [Store] = []
    do {
        stores = try await self.storeHandler().stores() // ✅
         ...
     } catch {
          ...
     }
}

@ThomasCle ThomasCle changed the title [Swift 6] Async functions givs compile errors about data races [Swift 6] Async functions results in compile errors about data races Oct 16, 2024
@mhammond
Copy link
Member

Adding @unchecked Sendable to the generated code sounds fine because we do promise that. It looks like it needs to be on the protocol but I can't make that work. Are you able to hand-edit the uniffi generated code to make it work?

@ThomasCle
Copy link
Author

Adding @unchecked Sendable to the generated code sounds fine because we do promise that. It looks like it needs to be on the protocol but I can't make that work. Are you able to hand-edit the uniffi generated code to make it work?

The protocol has to conform to Sendable and then the class-implementation should confirm to @unchecked Sendable for it to work.

Like this example:

public protocol RustGeneratedProtocol: Sendable {
    ...
}

public class RustGenerated: RustGeneratedProtocol, @unchecked Sendable {
    ...
}

That should make it work. Let me know if it doesn't 😊

@mhammond
Copy link
Member

I tried that before I asked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants