Solving “Value of non-Sendable type accessed after being transferred; later accesses could race;”

Published on: August 23, 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:

Value of non-Sendable type 'MyType' accessed after being transferred; later accesses could race;

For example, the following code produces such an error:

var myArray = [Int]()

Task {
  // Value of non-Sendable type '@isolated(any) @async @callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for <()>' accessed after being transferred; later accesses could race;
  myArray.append(1)
}

myArray.append(2)

Xcode offers a little guidance as to what that error is telling us:

Access can happen concurrently

In other words, the compiler is telling us that we're accessing myArray after we've "transferred" that property to our Task. You can see how we're appending to the array both inside of the task as well as outside of it.

Swift is telling us that we're potentially causing data races here because our append on myArray after the task might actually collide with the append inside of the task. When this happens, we have a data race and our code would crash.

The fix here would be to explicitly make a copy for our task when it's created:

Task { [myArray] in
  var myArray = myArray
  myArray.append(1)
}

This gets rid of our data race potential but it's also not really achieving our goal of appending to the array from inside of the task.

The fix here could be one of several approaches:

  1. You can wrapp your array in an actor to ensure proper isolation and synchronization
  2. You can rework your approach entirely
  3. Global actors could be useful here depending on the structure of your code

Ultimately, most strict concurrency related issues don't have a single solution that works. It's always going to require a case-by-case analysis of why a certain error appears, and from there you should figure out a solution.

In this case, we're taking a mutable object that we're mutating from within a task as well as right after where we've defined the task. The compiler is warning us that that will most likely cause a data race and you'll need to determine which solution works for you. My first attempt at fixing this would be to wrap the mutable state in an actor to make sure we achieve proper isolation and prevent future data races.

Subscribe to my newsletter