Bryan
Bryan

Reputation: 5779

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?

Upvotes: 13

Views: 14576

Answers (2)

lorem ipsum
lorem ipsum

Reputation: 29614

Floating Task are the source of many concurrency issues. The idea that an operation should be "let lose" is an old way of thinking you should just add the word async to the method.

import Foundation

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



class MyClass
{
    let worker: MyActor = MyActor()
    
    func swift6IsJustGreat() async
    {
        let number: Int = await worker.doWork() 
        print(number)
    }
}

Upvotes: 1

Rob Napier
Rob Napier

Reputation: 299565

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.

Upvotes: 21

Related Questions