Why is Sendable important?
The problem Sendable solves
Isolation domains keep mutable state from being touched by two threads at once, but only if nothing shared and mutable ever crosses domains unsynchronized. Sendable is the compiler’s way of verifying that claim at every boundary crossing.
class Counter { var value = 0 }
let counter = Counter()
Task { counter.value += 1 } // Task 1
Task { counter.value += 1 } // Task 2 — races with Task 1
Without Sendable checking, this compiles and silently corrupts data.
What Sendable actually certifies
A type conforming to Sendable asserts it’s safe to use from multiple concurrent contexts without external synchronization:
- Immutable value types
- Actors (automatically
Sendable) - Internally synchronized classes (
@unchecked Sendable) - Immutable/
finalclasses with onlyletproperties ofSendabletypes
Where it’s enforced
- Capturing a value in a
Task { }closure - Passing an argument to an actor’s method from outside
- Storing a value in an actor-isolated property from non-isolated code
- Returning a value from an actor method to a different isolation context
Why this actually matters
- Converts a runtime bug class into a compile-time error.
- Makes concurrency-safe API contracts explicit and machine-checked.
- It’s what makes actors’ guarantees actually hold end-to-end — an actor’s serialization is worthless if a caller can hand a mutable, unsynchronized class reference in.
- Surfaces genuine design problems, not just busywork.
Its limits
Sendable verifies aliasing/sharing safety, not correctness. A type can be perfectly Sendable and still have reentrancy bugs or wrong ordering assumptions.
One-liner if pressed: “Sendable is the compile-time contract that a value has no unsynchronized shared mutable state, so it’s safe to cross between isolation domains — without it enforced at every boundary, actor isolation’s guarantees would be trivially bypassable just by handing a mutable reference across.”