I've crafted a class that encapsulates a variable that's lazily initialized via an async function. If overridden, the initializer task and result are discarded.
/// A wrapper for a mutable variable that is lazily initialized via an async function.
///
/// If [set] is called before or while the variable is being initialized, the initialization is
/// canceled and the new value is stored instead.
actor LazyAsyncVariable<T: Sendable> {
private var state: State = .unset
private let initializer: @Sendable () async -> T
init(_ initializer: @escaping @Sendable () async -> T) {
self.initializer = initializer
}
/// Returns the value, initializing it if necessary.
func get() async -> T {
if case .unset = state {
state = .initializing(Task { [initializer] in await initializer() })
}
if case .initializing(let task) = state {
state = .set(await task.value)
}
guard case .set(let value) = state else {
fatalError("Impossible state")
}
return value
}
/// Sets the value, canceling the initialization if it is still in progress.
func set(_ value: T) {
if case .initializing(let task) = state {
task.cancel()
}
self.state = .set(value)
}
private enum State {
case unset
case initializing(Task<T, Never>)
case set(T)
}
}
When using this class in an actor, I get a warning from the compiler. I'm not sure what to do about it:
actor MyActor {
private var asyncVariableHolder: LazyAsyncVariable<Int>
private func resetAsyncVariable() {
asyncVariableHolder = LazyAsyncVariable<Int>({ return 1 })
}
var asyncVariable: Int {
get async {
await asyncVariableHolder.get()
// ------------------- <- warning is here
}
}
}
Here's the warning:
passing argument of non-sendable type 'LazyAsyncVariable' outside of actor-isolated context may introduce data races [non_sendable_call_argument]
I have no idea what to do with this. If I make LazyAsyncVariable
an actor, the warning disappears. But I'm really not sure why it would need to be an actor. Any suggestions are welcome, as well as explanations. I really don't know why it's warning me here.