SE-0414: Region Based Isolation

Would I be correct in saying that this proposal showcases an advantage of Swift using "concurrency domains" instead of threads as the basis of concurrency safety? If it had used threads instead, then non-Sendable object might use thread-local storage or some other thread-dependent runtime mechanism, and therefore might not be movable to a different region. But since "concurrency domains" are a construct entirely defined by the Swift language/compiler, we can make stronger guarantees about them, such as a non-Sendable value being movable to another actor in the right circumstances.

However, there is a (as far as I know) single hole in this: assumeIsolated. Specifically, something like this could be unsound:

class NonSendableType {
    ...
}

actor Actor1 {
    var x: NonSendableType

    nonisolated func get() -> NonSendableType {
        self.assumeIsolated { isolatedSelf in
            return isolatedSelf.x
        }
    }
}

actor Actor2 {
    var x: NonSendableType
    
    func take(_ x: NonSendableType) {
        self.x = x
    }
}

func unsound(actor1: Actor1, actor2: Actor2) async {
    // Regions: [] 
    let nonSendable = actor1.get()
    // Regions: [(nonSendable)]
    // Not [{(nonSendable), actor1}], because `get()` is nonisolated
    await actor2.take(nonSendable)
    // Regions: [{(nonSendable), actor2}]
    // `nonSendable` crosses actor boundaries!
}

Maybe to fix this problem, we could disallow a non-Sendable value returned from assumeIsolated to escape outside the enclosing function or into another isolation domain. Or we could restrict assumeIsolated to returning Sendable values only, and have an unsafeAssumeIsolated variant for non-Sendable values.

1 Like