@MainActor
protocol Pro {
    func foo()
}

class Base {
    var val = 0
}

extension Base: Pro {
    func foo() {
        print(val)
    }
}

let b = Base()
Task.detached {
    // warning.
    // Passing argument of non-sendable type 'Base' into main actor-isolated context may introduce data races
    await b.foo() 
    await MainActor.run {
        // no warning.
        b.foo()
    }
}

The example above, I don't understand why I wrap the code in MainActor.run. It won't show warning. But without wrapping it, it gets warning.

By default, top-level statements in package are treated as main-actor isolated, so b is isolated to main actor, so you can safely access it inside the task within MainActor.run. But overall behaviour seems a bit odd to me, I am either missing some detail why it does not inherits MainActor isolation from protocol, or that is some sort of an issue.

Detached won’t inherit the actor.

class Base: Pro {
    var val = 0
}

extension Base {
    func foo() {
        print(val)
    }
}

Putting the Pro adoption (and implying MainActor) on the main declaration (instead of the extension) turns off the error.

You can also turn off the error by explicitly making Base a MainActor:

@MainActor
protocol Pro {
    func foo()
}

@MainActor class Base {
    var val = 0
}

extension Base: Pro {
    func foo() {
        print(val)
    }
}
1 Like

A top-level statement relevant to the behaviour here is let b = Base(), that's why inside the Task it works with MainActor.

Oh, so seems like actor isolation inheritance isn't working in extension confirmation. That makes sense I guess.

@hborla Would probably know more if this is a legit "false positive" that we can fix one day… or if trying to fix the false positives would actually open up more "false negatives" somewhere else.

1 Like

Well, with more throughs put into it, for me that seems legit not to inherit isolation in extension (weird way to alter type isolation tbh), yet maybe in the same package it can be allowed as I know many prefer to conform protocols in extensions.

Task.init and Task.detached are different. Task.init uses an unofficial attribute @_inheritActorContext. but Task.detached doesn't use this attribute. Altought top-level is on MainActor which I know. But b is nonSendable.
When a protocol marking with a global actor, the type extends it with this protocol. only the requirements are on this global actor isolation. But when creating a type immediately conforming this protocol(not using extension to conform this protocol.), the whole type's properties and methods will be on the same global actor's isolation.