Hi guys,
I have a design question related to swift 6 concurrency. I am familiar with actor concepts and isolation and I have successfully migrated lot of my existing swift code to be swift 6 clean, however I struggle in this one case.
I have a nice 3rd party high performance C library that provides some primitives (shared state) (in form of C structs) and methods operating on that shared state. It's synchronisation free and is job of the caller to ensure proper synchronisation.
I have a nice swift wrapper where each C primitive and associated methods are wrapped as swift classes so the API is comfortable to use from Swift. All working great.
Because there is no synchronisation happening neither at C level, nor at Swift level, turning on strike concurrency produces lots of warnings around concurrent use of shared state. All expected.
Now due to the nature how this library and its primitives are used - as they are high performance, there are the options I found so far to make the library concurrency safe:
- Naive, tie each C primitive wrapping Swift class into
@MainActor
. For obvious reasons, do not want to do that. - Turn every C primitive (and its wrapping Swift class into its own
actor
). That would work but would introduce actor jump at every invocation assuming the primitive may be interacted with from other actors. - Make each wrapping Swift class
@unchecked Sendable
to silence the compiler and continue manually ensuring that it's used properly. - ???
What I'd like to ideally do is to tie my wrapper Swift class to a certain actor that may be specified for example when instantiating the class and then ensure that all methods are invoked on that actor.
Something like the following pseudo code:
class SwiftWrapperAroundCStruct {
private let thisClassIsolation: ???
private let underlyingCStruct: CStruct
// init will derive isolation from caller and remember it
public init(isolation: ???) {
thisClassIsolation = isolation
underlyingCStruct = ... // init the C shared state
}
public metodOperatingOnCStruct(args) {
// either modify the method signature
// to make sure it is always invoked on `thisClassIsolation`
// or perhaps precondition we are on `thisClassIsolation`
invokeCMethodOperatingOn(underlyingCStrunct, args)
}
}
This would allow the consumers of the library to choose what actor to use the C shared state and methods on, and would skip unnecessary context switches and actor jumps when calling the high performance methods while still getting the benefits of swift 6.
I'm trying to follow all concurrency proposals but I am getting lost lately and maybe I have missed some proposals to achieve this. Any pointers how to design such API?
thanks for your help,
Martin