Actually, there is one other important change that needs to be mentioned in this proposal: it is important that isolated actor references are not sendable across actors and other concurrency domains. We need to explicitly mention this in the proposal (and check it in the compiler) because isolated isn't a type. Example:
actor MyActor {
var state : Int
func x(a: isolated MyActor) { // Ok
// Ok
a.state += 1
state += 1
}
func y(other: MyActor) async {
self.x(a: self) // ok
// error: cannot pass isolated self across actor hop boundary.
await other.x(a: self)
}
}
The same thing happens with structured concurrency, but I believe this is handled by promoting the isolated references to non-isolated. If there are other cases (like capture lists) where this isn't happening, then we'll need explicit checks.
One other clarification about the point in my previous comment, what I'm saying is that instead of allowing this through an implicit suspension:
extension A {
func h() {
acceptSendable {
// implicit suspension happens here to get an isolated self, possibly causing reentrancy!
[isolated self] async in
f() // synchronous call to f() is okay, because closure is explicitly isolated to `self`
}
}
}
That we instead add a generally useful function like this:
extension Actor {
func doOnActor(_ fn: @Sendable (isolated Self) -> ()) {
fn(self)
}
}
And then implement the above as:
extension A {
func h() {
acceptSendable {
// 'self' is implicitly non-isolated in this context because this is a sendable closure.
// explicit await makes suspension and possible-reentrancy explicit.
await self.doOnActor { isolatedSelf in
isolatedSelf.f()
}
}
}
}
The doOnActor
sort of function is also helpful when you want to invoke multiple sync functions from outside of an actor without a suspention:
actor YourActor { func syncThing1() {..} func syncThing2() {..} }
func doTwoThings(a: YourActor) async {
// Reentrancy is possible between these.
await a.syncThing1()
await a.syncThing2()
await a.doOnActor { isolatedA in
// Reentrancy is not possible between these.
isolatedA.syncThing1()
isolatedA.syncThing2()
}
}
Of course, doOnActor
deserves a better name :-)
-Chris