Hello all, I'm trying to figure out if the following is a bug or not related to the Swift6 language mode & structured concurrency. It seems that adding an @MainActor annotation to a method assumes that closures used within that method are also @MainActor even though they may not be. This came up when migrating my codebase to the Swift6 language mode using Xcode 16 betas.
Given a class that looks like this:
class Foo {
func bar() {
SomeOtherType(onBaz: { [weak self] in
self?.baz()
})
}
@MainActor
func baz() {
...
}
}
The compiler will give us the proper error, Call to main actor-isolated instance method 'baz()' in a synchronous nonisolated context. This makes sense, because baz() is marked as @MainActor. However, adding @MainActor to bar() will resolve the error even though the closure onBaz may not be called on the MainActor. Here is an example where you can mutate state on both a background task and on the main actor possibly causing a data race:
import Foundation
class Foo {
var mutableState: Int = 0
@MainActor
func bar() {
SomeOtherType { [weak self] in
// mutates state from bg task:
self?.baz()
}
Task {
// mutate state from mainactor:
mutableState = 20
}
}
@MainActor func baz() {
print(Thread.isMainThread) // false
mutableState = 10
}
}
class SomeOtherType {
init(onBaz: @escaping () -> Void) {
Task { // background task...
onBaz()
}
}
}
let foo = Foo()
Task { @MainActor in
foo.bar()
}
Is this a bug? Is Foo being assumed Sendable when it is not? This happens regardless of if I annotate mutableState with @MainActor.
2 Likes
vns
2
This won't compile in Swift 6 mode, as onBaz should be @Sendable. Marking it so will trigger chain of errors, resolve which would be possible my making Foo conform to Sendable.
Ah! You're right— when using this in a project it doesn't compile, that's good. I incorrectly assumed that Playgrounds in Xcode 16 would default to the Swift6 language mode, but it appears they're created with Swift5 still. I hope that is updated in a future beta.
Thanks @vns !
2 Likes