In the previous post, we looked at running work in parallel with async let and TaskGroup. But once tasks start running in parallel, there’s a bigger question: what happens to the data they share? Swift’s answer is the Sendable protocol.
What is Sendable?
Sendable is a marker protocol that tells the compiler a type can safely be passed across concurrency domains like tasks and actors. If a type isn’t Sendable, Swift will stop you from moving it into a concurrent context because that might cause a data race. The compiler does a lot of this checking automatically, but sometimes you’ll need to be explicit.
Value Types
Immutable value types are generally safe. For example:
| |
Because Company is a struct with immutable properties, it’s automatically safe to send between tasks.
Enums made up of other Sendable types also conform without extra work. Take this example:
| |
Each associated value here (String and Int) is already Sendable, so the entire enum is safe to move across concurrency boundaries. Reference types are more complicated. Take this example:
| |
This is not Sendable. Two tasks could read and write title at the same time, leading to undefined behaviour. If you try to pass it into a TaskGroup, the compiler will complain.
Not all classes are excluded, though. A final class that is completely immutable can conform to Sendable safely. For example:
| |
Because ImmutableUser is final (so it can’t be subclassed) and all its properties are immutable and Sendable, this class is safe to share across concurrency domains. If you do have a mutable class, there are two common fixes. The first is to make the type immutable:
| |
The second is to isolate mutation using an actor:
| |
The actor guarantees that only one task can access articles at a time, so Sendable conformance is no longer needed. You’ll see Sendable complaints when trying to move a non-Sendable type across concurrency domains. For example:
| |
The compiler stops you here because Article isn’t safe to send into the child tasks.
nonisolated
Another place this shows up is when you mark an actor method as nonisolated. Once you do that, the compiler no longer guarantees access through the actor’s queue. Any values crossing into that method need to be Sendable.
| |