I compile an SPM target with:
.unsafeFlags([
"-Xfrontend", "-warn-concurrency",
"-Xfrontend", "-enable-actor-data-race-checks",
]),
Conceptually, I wanna do this, but can't, due to async' call cannot occur in a global variable
. So I need to find some alternative:
public final actor FooStore {
public static let shared: FooStore = await FooStore() // ❌ 'async' call cannot occur in a global variable initializer
private let foo: Foo
private init() async {
self.foo = await Self.initFooUsingMustBeAsyncMethod()
}
}
So instead I naively try this:
public final actor FooStore {
private static var _shared: FooStore?
/// Completely fine that this is a function
public static func shared() async -> FooStore {
// ⚠️ WARNING: Reference to static property '_shared' is not concurrency-safe because it involves shared mutable state
if let _shared {
return _shared
}
let shared = await FooStore()
// ⚠️ WARNING: Reference to static property '_shared' is not concurrency-safe because it involves shared mutable state
_shared = shared
return shared
}
private let foo: Foo
private init() async {
self.foo = await Self.initFooUsingMustBeAsyncMethod()
}
}
OK I understand the warning and take it seriously, bad idea!
So I figured I'd try fixing those warnings, by... wrapping an actor around it..?! And hey, it compiles without warning!
public final actor FooStore {
private final actor StoreOfFooStore {
private var _shared: FooStore?
fileprivate func shared() async -> FooStore {
if let shared = _shared {
return shared
}
let shared = await FooStore()
_shared = shared
return shared
}
}
private static let storeOfSharedFooStore = StoreOfFooStore()
/// Completely fine that this is a function
public static func shared() async -> FooStore {
await Self.storeOfSharedFooStore.shared()
}
private let foo: Foo
private init() async {
self.foo = await Self.initFooUsingMustBeAsyncMethod()
}
}
Is this an OK solution, as in thread safe, data race safe?