Confusing error: Global actor isolated global function cannot be called from outside of the actor

After reading this post, to try things out:

I wrote this code
@main
enum Driver {
    static func main () async throws {
        test ()
        // Error: Global actor 'A'-isolated global function 'test()' cannot be called from outside of the actor
        // Insert `await`
        
        act ("str")
        // Error: Global actor 'A'-isolated global function 'test()' cannot be called from outside of the actor
        // Insert `await`
    }
}

// [https://forums.swift.org/t/global-actor-isolation-doesnt-recognize-shared-actor-instance-as-isolated/83979]
@globalActor
actor A: GlobalActor {
    static let shared: A = .init()

    private init () {}
    
    func act(_ str: String) {
        print(str)
    }
}

@A
class C {
    var str = "str"

    /*
     // A.shared.assumeIsolated:
        Assume that the current task is executing on this actor’s serial executor, or stop program execution otherwise
     */
    
    #if false
    func f() {
        A.shared.act(self.str) // Call to actor-isolated instance method 'act' in a synchronous global actor 'A'-isolated context
    }

    func g() {
        A.shared.assumeIsolated { a in
            a.act(self.str) // Global actor 'A'-isolated property 'str' can not be referenced from a nonisolated context
        }
    }
    #endif
    
    func h() {
        // successful gymnastics
        let str = self.str
        A.shared.assumeIsolated { a in
            a.act(str)
        }
    }
}

// [https://forums.swift.org/t/global-actor-isolation-doesnt-recognize-shared-actor-instance-as-isolated/83979/2]
@A func test () {
    let c = C ()
    c.h ()
}

@A func act (_ str: String) async {
    await A.shared.act (str)
}

I was immediately greeted by the error, shown below.

@main
enum Driver {
    static func main () async throws {
        test ()
        // Error: Global actor 'A'-isolated global function 'test()' cannot be called from outside of the actor
        // Insert `await`
        
        act ("str")
        // Error: Global actor 'A'-isolated global function 'test()' cannot be called from outside of the actor
        // Insert `await`
    }
}

I understand the Insert await advice, but I don't really understand the error text itself when I read it literally.

If I insert the await:

@main
enum Driver {
    static func main () async throws {
        await test ()     // Okay
        await act ("str") // Okay
    }
}

The error goes away, but those functions are still being called from outside of the actor.

Is this error confusing in the given context? If so, what would be a better phrasing?

I think the error is confusing, but accurate. Neither test() nor act() are declared to be async. This means that they can be called synchronously from within the actor’s context. Trying to call them synchronously from outside the actor (by omitting await) is not allowed because the invocation won’t be within the actor’s context. Once you add await, the compiler knows you want to hop to the actor’s context before the function is invoked.

2 Likes

In my view, the error text is good so long as it correctly identifies the root cause of the issue. In this case, I believe it does.

Understandably, there are multiple ways one can go about addressing the root cause, and I don’t believe it’s the error’s responsibility to read the context well enough to identify a most likely approach for you. In this case, one might isolate the caller to the actor or one might add a suspension point to allow Swift to switch isolation contexts for you, both are workable solutions. Perhaps we could just add the former to the suggestion.

I read this error as a shorter version of this full version:

3 Likes