@MainActor should make the function async

The @MainActor attribute should be a guarantee that a method is called on the main thred, yet it is not.

If you don't mark a @MainActor func explicitly as async, it is still possible to call it from some other queue/actor.

  1. Either the compiler should enforce that all @MainActor annotated funcs have the async attribute
  2. Or the compiler should make all @MainActor methods implicitly async

Otherwise @MainActor is more of a suggestion than a guarantee.

1 Like

The compiler ensures that all calls to @MainActor methods are implicitly async unless called from something that is also @MainActor. However, that checking is being phased in over multiple releases to avoid breaking existing code that looks like this:

// Existing code that calls the completion on the main thread,
// but is not yet annotated with `@MainActor`
func fetchUser(_ completion: @escaping (User)->Void) {
  // ...
}

func viewDidAppear() {
  fetchUser() { (user) in
    // We know that this always runs on the main thread,
    // but `fetchUser`'s parameter has not been annotated
    // with @MainActor, so the *compiler* doesn't know it
    // yet. 
    //
    // If the compiler enforced an 'await' here, it would 
    // break too much existing code
    self.label.text = user.name
  }
}

I believe that the plan is to produce a warning about the above code in Swift 5.7, and then upgrade it to a hard error whenever Swift 6.0 comes out. It would also be an error right now, in Swift 5.6, if the surrounding code uses any other concurrency annotations to indicate that it is concurrency-aware.

But yes, for the moment @MainActor is a suggestion but not yet a guarantee.

4 Likes

Could anyone confirm if these issues have been addressed in latter Swift iterations?

Just checked this code on Windows, Swift 6.0.3, compiled in Swift 6 mode:

import Foundation

@MainActor
func foo() {
    print("main thread = \(Thread.isMainThread), main actor = \(#isolation === MainActor.shared)")
}

let task = Task {
    foo()
}

let _ = await task.result

Output:
main thread = false, main actor = true

TBH I don't know how to interpret the output and what is guaranteed and what's not.

Yes, with the Swift 6 compiler (Xcode 16.2), even in the Swift 5 language mode with "Minimal" concurrency checking enabled, this code now produces an error: