swiftswift-concurrency

Actor isolation and non-sendable closure (use higher-order function inside Actor)


How to adjust forEachAsync for use within TestActor without a warning? It should stay reusable in other places.

I need TestActor to pass around non-sendable stuff within it. And I want to use higher-order convenience functions like forEachAsync within it. Execution is sequential on purpose.

Build settings -> Strict Concurrency Checking: Complete

class NotSendable { }

actor TestActor {
    func foo() async {
        let nonSendables = [NotSendable(), NotSendable()]

        // No problem
        for notSendable in nonSendables {
            await bar(notSendable)
        }

        // Warning: Sending 'nonSendables' risks causing data races
        await nonSendables.forEachAsync{ notSendable in
            await bar(notSendable)
        }
    }
    
    private func bar(_: NotSendable) async { }
}

extension Sequence {
    func forEachAsync(_ operation: (Element) async -> Void) async {
        for element in self {
            await operation(element)
        }
    }
}

Solution

  • Solution:

    class NotSendable { }
    
    actor TestActor {
        func foo() async {
            let nonSendables = [NotSendable(), NotSendable()]
            
            await nonSendables.forEachAsync{ notSendable, isolation in
                await bar(notSendable, isolation)
            }
        }
        
        private func bar(
            _: NotSendable,
            _ isolation: isolated (any Actor)? = #isolation
        ) async {
        }
    }
    
    extension Sequence {
        func forEachAsync(
            _ operation: (Element, isolated (any Actor)?) async -> Void,
            isolation: isolated (any Actor)? = #isolation
        ) async {
            for element in self {
                await operation(element, isolation)
            }
        }
    }
    

    It's ugly but it works in Xcode 16.2 with Swift Language Version set to Swift 6 in the Build Settings. You can still use forEachAsync outside of an Actor, in which case isolation is nil.

    Sources:
    SE-0313 – Improved control over actor isolation
    SE-0420 – Inheritance of actor isolation

    Hopefully this proposal will be accepted and make things easier and prettier:
    SE-0461 – Run nonisolated async functions on the caller's actor by default