swiftconcurrencytaskactorswift6

Difference Between @MainActor func and Task { @MainActor in } in Swift 6


I have a class MyUIClass that is @MainActor isolated:

@MainActor
class MyUIClass {
    func show(object: Any?) {}
}

To use the show function, it needs to be accessed within the MainActor context:

class Santa {
    @MainActor
    func present1(gift: Any?) {
        MyUIClass().show(object: gift) // βœ…
    }
    
    func present2(gift: Any?) {
        Task { @MainActor in
            MyUIClass().show(object: gift) // πŸ’₯ Task or actor isolated value cannot be sent; this is an error in the Swift 6 language mode."
        }
    }
}

What I don’t understand is that when I try to use gift inside the Task block in present2, I get the warning

Task or actor isolated value cannot be sent; this is an error in the Swift 6 language mode.

In the first function, present1, the argument gift: Any? is not isolated to MainActor (right?) and is not Sendable, but there is no error. However, the error only occurs in the second case, inside the Task block.

I know the title is a bit odd. I understand that @MainActor functions can only be executed in the MainActor context, and Task { @MainActor in } can be executed in any context.

Still, in these two cases (present1 / present2), it seems that MyUIClass().show(object: gift) is executed in the MainActor context, so why is there a difference in the error behavior?


Solution

  • The @MainActor annotation should indeed be read positionally - it affects what comes after it, which, in your first function, is the entirety of the function including its parameters, and in your second, is only what happens between the { } of the Task.

    Therefore, in the first example, gift is already MainActor isolated and nothing needs to cross an actor boundary.

    In the second example however, gift is not MainActor isolated, and as it is also not Sendable, it cannot cross the boundary, resulting in a compilation error.

    Just for further illustration, this would compile just fine:

    func present2(gift: Any?) {
        Task { @MainActor in
            MyUIClass().show(object: "gift") // βœ… no attempt to capture non Sendable value
        }
    }
    

    So would this...

    func present2(gift: String) {
        Task { @MainActor in
            MyUIClass().show(object: gift) // βœ… capturing Sendable value
        }
    }
    

    ... because String is Sendable.