Printed on: August 21, 2024
When you begin migrating to the Swift 6 language mode, you may almost certainly activate strict concurrency first. As soon as you’ve got accomplished this there might be a number of warings and errors that you will encounter and these errors will be complicated at instances.
I will begin by saying that having a strong understanding of actors, sendable, and knowledge races is a big benefit once you wish to undertake the Swift 6 language mode. Just about all the warnings you may get in strict concurrency mode will let you know about potential points associated to working code concurrently. For an in-depth understanding of actors, sendability and knowledge races I extremely suggest that you just check out my Swift Concurrency course which is able to get you entry to a sequence of movies, workout routines, and my Sensible Swift Concurrency e-book with a single buy.
WIth that out of the best way, let’s check out the next warning that you just would possibly encounter in your undertaking:
Activity-isolated worth of kind ‘() async -> Void’ handed as a strongly transferred parameter
After I first encountered the error above, I used to be puzzled. The code that made this occur wasn’t all that unusual and I had no concept what could possibly be flawed right here.
Let us take a look at an instance of the code that might make this error present up:
var myArray = [1, 2, 3]
await withTaskGroup(of: Void.self) { group in
for _ in 0..<10 {
// Activity-isolated worth of kind '() async -> Void' handed as a strongly transferred parameter; later accesses might race;
group.addTask {
myArray.append(Int.random(in: 0..<10))
}
}
}
The issue above can even happen once you create an unstructured process with Activity
or a indifferent process with Activity.indifferent
. The error and the explanation for the error showing are the identical for all circumstances, however what precisely is flawed within the code above?
Sadly, the compiler is not of a lot assist right here so we’ll must determine this one out on our personal…
In each case that I’ve seen for this particular error, the duty that we create (whether or not it is a youngster process, unstructured process or a indifferent process) captures a non-sendable object. To be taught extra about sendable, check out my put up that explains Sendable and @Sendable closures.
So whereas the compiler error is extraordinarily arduous to learn and perceive, the explanation for it showing is definitely comparatively easy. We have got a powerful seize to one thing that is not Sendable
inside a process which may run concurrently with different work. The result’s a attainable knowledge race.
The repair can typically be comparatively easy when you’re capable of make the captured kind sendable or an actor. Within the case of the code above that might be difficult; myArray
is an array of Int
which signifies that we’re already as sendable as we could possibly be. However as a result of the array is mutable, there’s an opportunity that we’ll race.
There are a number of attainable fixes on this case. One in all them is to mutate the array exterior of the kid duties by having youngster duties produce numbers after which iterating over the duty group:
var myArray = [1, 2, 3]
await withTaskGroup(of: Int.self) { group in
for _ in 0..<10 {
group.addTask {
// Activity-isolated worth of kind '() async -> Void' handed as a strongly transferred parameter; later accesses might race;
return (myArray.first ?? 2) * 2
}
}
for await worth in group {
myArray.append(worth)
}
}
Sadly, the above nonetheless produces an error…
The explanation for that’s that myArray
continues to be being accessed from inside a baby process. In order that signifies that whereas a baby process is studying, our async for loop could possibly be writing after which we’ve got a knowledge race.
To repair that we have to make a replica of myArray
within the youngster process’s seize listing like this:
group.addTask { [myArray] in
return (myArray.first ?? 2) * 2
}
With that change in place, the code compiles and runs appropriately.
Sadly, Activity-isolated worth of kind '() async -> Void' handed as a strongly transferred parameter
is a really powerful to learn error with no single repair. What this error tells you although, is that you just’re accessing or capturing a worth that is not sendable or protected to be accessed concurrently. Fixes for this could possibly be:
- To make the captured object an actor
- To make the captured object sendable
- To make a replica of the thing
- To seize properties on the thing exterior of your process
- To rethink your strategy fully (that is not often wanted)
As with many different strict concurrency associated points, fixing this error will rely in your capability to research the issue, and your understanding of actors and sendable. These are matters that it is best to try to perceive nearly as good as you’ll be able to earlier than you try to migrate to Swift 6.