Solving “Reference to captured var in concurrently-executing code” in Swift

Published on: July 31, 2024

Once you start migrating to the Swift 6 language mode, you'll most likely turn on strict concurrency first. Once you've done this there will be several warings and errors that you'll encounter and these errors can be confusing at times.

I'll start by saying that having a solid understanding of actors, sendable, and data races is a huge advantage when you want to adopt the Swift 6 language mode. Pretty much all of the warnings you'll get in strict concurrency mode will tell you about potential issues related to running code concurrently. For an in-depth understanding of actors, sendability and data races I highly recommend that you take a look at my Swift Concurrency course which will get you access to a series of videos, exercises, and my Practical Swift Concurrency book with a single purchase.

WIth that out of the way, let's take a look at the following warning that you might encounter in your project:

Reference to captured var in concurrently-executing code

This warning tells you that you're capturing a variable inside of a body of code that will run asynchornously. For example, the following code will result in this warning:

var task = NetworkTask<Int, URLSessionUploadTask>(
    urlsessionTask: urlSessionTask
)

upload(fromTask: urlSessionTask, metaData: metaData, completion: { result in
    Task {
        await task.sendResult(result) // Reference to captured var 'task' in concurrently-executing code; this is an error in the Swift 6 language mode
    }
})

The task variable that we create a couple of lines earlier is mutable. This means that we can assign a different value to that task at any time and that could result in inconsistencies in our data. For example, if we assign a new value to the task before the closure starts running, we might have captured the old task which could be unexpected.

Since strict concurrency is meant to help us make sure that our code runs as free of surprises as possible, Swift wants us to make sure that we capture a constant value instead. In this case, I'm not mutating task anyway so it's safe to make it a let:

let task = NetworkTask<Int, URLSessionUploadTask>(
    urlsessionTask: urlSessionTask
)

upload(fromTask: urlSessionTask, metaData: metaData, completion: { result in
    Task {
        await task.sendResult(result)
    }
})

This change gets rid of the warning because the compiler now knows for sure that task won't be given a new value at some unexpected time.

Another way to fix this error would be to make in explicit capture in the completion closure that I'm passing. This capture will happen immediately as a let so Swift will know that the captured value will not change unexpectedly.

var task = NetworkTask<Int, URLSessionUploadTask>(
    urlsessionTask: urlSessionTask
)

upload(fromTask: urlSessionTask, metaData: metaData, completion: { [task] result in
    Task {
        await task.sendResult(result.mapError({ $0 as any Error }))
    }
})

Altenatively, you could make an explicit constant capture before your Task runs:

var task = NetworkTask<Int, URLSessionUploadTask>(
    urlsessionTask: urlSessionTask
)

let theTask = task
upload(fromTask: urlSessionTask, metaData: metaData, completion: { result in
    Task {
        await theTask.sendResult(result)
    }
})

This is not as elegant but might be needed in cases where you do want to pass your variable to a piece of concurrently executing code but you also want it to be a mutable property for other objects. It's essentially the exact same thing as making a capture in your completion closure (or directly in the task if there's no extra wrapping closures involved).

When you first encounter this warning it might be immediately obvious why you're seeing this error and how you should fix it. In virtual all cases it means that you need to either change your var to a let or that you need to perform an explicit capture of your variable either by making a shadowing let or through a capture list on the first concurrent bit of code that accesses your variable. In the case of the example in this post that's the completion closure but for you it might be directly on the Task.

Subscribe to my newsletter