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

Published on: August 12, 2024
Updated on: August 21, 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.

Expand your learning with my books

Practical Core Data header image

Learn everything you need to know about Core Data and how you can use it in your projects with Practical Core Data. It contains:

  • Twelve chapters worth of content.
  • Sample projects for both SwiftUI and UIKit.
  • Free updates for future iOS versions.

The book is available as a digital download for just $39.99!

Learn more