uniffi-rs
uniffi-rs copied to clipboard
UniFFI generated Swift code fails to compile "Swift 6" mode - due to mutation of shared state.
Whenever we land #2045 (or other PR enabling Sendable types) we should try to ensure that UniFFI generated Swift code compiles (and works...) under "Swift 6" mode, that is with enableExperimentalFeature("StrictConcurrency")
We can already now "emulate Swift 6" mode, by adding:
let swiftSettings: [SwiftSetting] = [
.enableExperimentalFeature("StrictConcurrency"),
.unsafeFlags(["-warnings-as-errors"])
]
And then use swiftSettings: swiftSettings in our Swift SPM targets, like so
Doing this shows some issues with how UniFFI sets up Callbacks, specifically with vtable in UniffiCallbackInterface<MY_TRAIT> and handleMap in FfiConverterType<MY_TRAIT>.
The problem is they use static mutable properties, which is verboten in Swift 6.
There are a few ways to solve this, I think perhaps using @TaskLocal is the simplest way forward. See PointFree's episode of ShardState, in this code they have a very similar scenario, a global shared stated inside an enum namespace:
enum SharedLocals { @TaskLocal static var isAsserting = false; }
Here is the documentation about @TaskLocal
We could need to change how uniffiCallbackInit<MY_TRAIT> works
private func uniffiCallbackInit<MY_TRAIT>() {
uniffi_<MY_PROJECT>_fn_init_callback_vtable_<MY_TRAIT>(&UniffiCallbackInterface<MY_TRAIT>.vtable)
}
to be able to perhaps take to closures instead, using UniffiCallbackInterface<MY_TRAIT>.$vtable.get() as a () -> Value read method, N.B. the dollarsign $ (using the propertywrapper, and not the projected value), probably using withValue on TaskLocal to set. I'm not sure though...
The private class UniffiHandleMap also needs to change, probably it should become an actor instead, potentially a @GlobalActor. But if we are certain about the thread safety of class UniffiHandleMap we can just might get away with @unchecked Sendable marking it, like so: private class UniffiHandleMap<T>: @unchecked Sendable