Solving “Converting non-sendable function value may introduce data races” in Swift

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

Converting non-sendable function value may introduce data races

Usually the warning is a bit more detailed, for example in a project I worked on this was the full warning:

Converting non-sendable function value to '@Sendable (Data?, URLResponse?, (any Error)?) -> Void' may introduce data races

This warning (or error in the Swift 6 language mode) tells you that you're trying to pass a non-sendable closure or function to a place that expects something that's @Sendable. For convenience I will only use the term closure but this applies equally to functions.

Consider a function that's defined as follows:

func performNetworkCall(_ completion: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) {
    // ...
}

This function should be called with a closure that's @Sendable to make sure that we're not introducting data races in our code. When we try and call this function with a closure that's not @Sendable the compiler will complain:

var notSendable: (Data?, URLResponse?, (any Error?)) -> Void = { data, response, error in 
    // ...
}

// Converting non-sendable function value to '@Sendable (Data?, URLResponse?, (any Error)?) -> Void' may introduce data races
performNetworkCall(notSendable)

The compiler is unable to guarantee that our closure is safe to be called in a different actor, task, or other isolation context. So it tells us that we need to fix this.

Usually, the fix for this error is to mark your function or closure as @Sendable:

var notSendable: @Sendable (Data?, URLResponse?, (any Error?)) -> Void = { data, response, error in 
    // ...
}

Now the compiler knows that we intend on our closure to be Sendable and it will perform checks to make sure that it is. We're now also allowed to pass this closure to the performNetworkCall method that you saw earlier.

If you'd like to learn more about Sendable and @Sendable check out my course or read a summary of the topic right here.

Subscribe to my newsletter