swiftconcurrencyactorswift-concurrencyswift6

Swift 6: Capture of 'self' with non-sendable type in a @Sendable closure


Context

Consider this simple example, where we have an Actor and a Class which holds a reference to that Actor:

actor MyActor
{
    func doWork() -> Int {
        return 42
    }
}



class MyClass
{
    let worker: MyActor = MyActor()
    
    func swift6IsJustGreat()
    {
        Task {
            let number: Int = await worker.doWork()   // ERROR: Capture of 'self' with non-sendable type 'MyClass' in a `@Sendable` closure
            print(number)
        }
    }
}

Question

Xcode 16 Beta 1, when using Swift 6 Language Mode, shows this error on the line where we attempt to use the actor:

Capture of 'self' with non-sendable type 'MyClass' in a `@Sendable` closure

I understand the error. worker is a property of MyClass and the Task closure is capturing the instance of MyClass, which is not Sendable. Got it. But then...how the hell do I use anything in concurrency unless it's a global?

If my actor is an iVar on a non-Sendable class, how can I ever call anything on the actor?


Solution

  • Capture worker into the Task rather than self:

    Task { [worker] in  // This makes a local copy of `self.worker`
        let number: Int = await worker.doWork()   // No problem
        print(number)
    }
    

    The Task does not actually need self. It doesn't do anything with it, and since worker is a let property, it is impossible for it to change between the time Task begins and when worker is used. (If it were var, then it would be even more important that it be captured rather than self, since it could change in a thread-unsafe way.)

    An Actor is always Sendable, so can always be captured by a Task.