Using @MainActor values in initializer default arguments

I can't seem to figure out how to get default arguments to work with global actors. Marking either the class or the init method as MainActor doesn't seem to do anything.

@MainActor
class FakeFileDownloader: FileDownloader {
	@MainActor
	// Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
	init(sessionManager: SessionManager = SessionManager()) {
2 Likes

I have the same problem, including in Xcode 13.3 beta 3 (13E5104i).

Reported as [SR-15916] Not implemented: @MainActor-isolated default arguments for @MainActor-isolated functions - Swift

An ugly workaround is to define an unsafe nonisolated initializer:

@MainActor class Foo {
    init() { } // The safe one
    nonisolated init(unsafeNonisolated: Bool) { } // Here be dragons
}

// error: call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
// @MainActor func f1(foo: Foo = Foo()) { }
//                               ^
// note: calls to initializer 'init()' from outside of its actor context are implicitly asynchronous
// @MainActor class Foo {
//                  ^
@MainActor func f1(foo: Foo = Foo()) { }

// OK
@MainActor func f1(foo: Foo = Foo(unsafeNonisolated: true)) { }
1 Like

One note here that the general diagnostic does seem incorrect, but a reminder that that code is inlined in the caller's context. An off-actor call to:

let it = await FakeFileDownloader()

is equivalent to:

let value = SessionManager()
let it = await FakeFileDownloader(sessionManager: value)

which is clearly not correct for an arbitrary context. It may not be just a matter of allowing the syntax you propose.

Which syntax are you talking about?

One single default argument can "easily" replaced with two overloads. But several arguments can not:

@MainActor class Foo {}

// ❌ Won't compile
@MainActor func f(foo: Foo = Foo()) { ... }

// ✅ OK: replace with 2 overloads
@MainActor func f(foo: Foo) { ... }
@MainActor func f() { f(foo: Foo()) }

// ❌ Won't compile, can be replaced with 4 overloads
@MainActor func f(foo: Foo = Foo(), bar: Bar = Bar()) { ... }

// ❌ Won't compile, can be replaced with 8 overloads
@MainActor func f(foo: Foo = Foo(), bar: Bar = Bar(), baz: Baz = Baz()) { ... }

Yeah, sorry, to be more clear: I meant here that the execution environment in which the default value is computed is not trivial, and thus whoever fixes that bug — which is definitely worth fixing — may have to make choices about tightening the semantics of default arguments in order to fix this.

1 Like