I cannot figure out how to get this to work. I have a class (A) that must be initialized on the main actor. An instance of that class is used as the default argument for another type's initializer:
@MainActor func testMainActor() {
class A {
// A's initializer must be on the main actor
@MainActor init() {}
}
class B {
// But A is a default parameter for a different initializer.
init(a: A = A()) {} // error: call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
// Adding MainActor doesn't work either
@MainActor init(i: Int, a: A = A()) {} // error: call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
}
_ = B()
}
I just ran into this as well. I think the issue is that default arguments use something similar to autoclosure to defer execution until needed, and that closure doesn't inherit the MainActor context.
The workaround I used was to make the parameter optional in the affected function, default it to nil, and fill it in in the function body (which will be bound to the MainActor) if it's nil:
class A {
@MainActor init() {}
}
class B {
@MainActor init(a: A? = nil) {
let sureA: A
if let a {
sureA = a
} else {
sureA = A()
}
}
}
This is obviously far less than ideal. I think the compiler has enough information available (e.g. is the closure escaping?) to enforce the actor isolation guarantees. Can somebody on the compiler team weigh in here?
Ah, yep. You're right. I had seen an error message in one version of this I was playing around with ("Main actor-isolated … can not be referenced from a non-isolated autoclosure"), but that was before I added the @MainActor annotation to the initializer.
Regardless of the approach used for the workaround, this seems like something the language should support without having to do the workaround.
Any thoughts here? Is there something blocking the ability for the compiler to understand the interaction between default arguments and actor isolation?