What is Sendable, and why does Swift 6 care about it?
TL;DR
Sendable is a protocol with no methods. It means: "values of this type are safe to share across tasks and actors".
Value types made of Sendable parts, actors, and immutable final classes are Sendable. Mutable classes are not.
In Swift 6 language mode, the compiler checks this strictly. Sending a non-Sendable value across actors is an error.
Why it exists
A data race happens when two threads touch the same memory and one writes. Classes are shared by reference, so they can cause this. Swift needs to know which types are safe to pass to another task.
final class Counter { var value = 0 } // not Sendable
let counter = Counter()
Task.detached { counter.value += 1 } // Swift 6 error: capture of non-Sendable 'counter'
counter.value += 1What is Sendable automatically
| Type | Sendable? |
|---|---|
Int, String, Bool, Data, URL, Date | Yes |
Struct / enum where all stored values are Sendable | Yes (inferred if not public) |
Array, Dictionary, Optional of Sendable elements | Yes |
| Actors | Yes, always |
final class with only let properties of Sendable types | Yes, if you declare it |
Class with var properties | No |
| Closures | Only if marked @Sendable (or inferred) |
Structs
struct User: Sendable {
let id: UUID
var name: String
}For internal types, Swift infers Sendable for you. For public types you must write it, because it becomes part of your API.
Final classes
final class Config: Sendable {
let baseURL: URL
let timeout: Double
init(baseURL: URL, timeout: Double) {
self.baseURL = baseURL
self.timeout = timeout
}
}Must be final, and every stored property must be a let of a Sendable type.
@unchecked Sendable
For a class that protects its own state, for example with a lock, the compiler cannot prove it is safe. You promise it.
final class TokenStore: @unchecked Sendable {
private let lock = NSLock()
private var token: String?
func set(_ new: String) { lock.withLock { token = new } }
func get() -> String? { lock.withLock { token } }
}If you are wrong, you get real data races. Use it carefully and add a comment.
Better: Mutex (iOS 18+)
The Synchronization module has Mutex, which is Sendable. No @unchecked needed.
import Synchronization
final class TokenStore: Sendable {
private let token = Mutex<String?>(nil)
func set(_ new: String) { token.withLock { $0 = new } }
func get() -> String? { token.withLock { $0 } }
}@Sendable closures
A closure that runs in another task must not capture mutable state unsafely.
func runLater(_ work: @escaping @Sendable () -> Void) {
Task.detached { work() }
}
var count = 0
// runLater { count += 1 } // error: mutation of captured var in @Sendable closure
let snapshot = count
runLater { print(snapshot) } // OK: captures an immutable Sendable valueRules for @Sendable closures:
- Captured values must be
Sendable. - Captured variables must not be mutable
vars.
sending: transfer instead of share
Swift 6 can move a non-Sendable value to another actor if you never use it again. The sending keyword marks such parameters and results.
actor Library {
var books: [Book] = []
func add(_ book: sending Book) { books.append(book) } // Book is a non-Sendable class
}
func addNewBook(to library: Library) async {
let book = Book(title: "Swift")
await library.add(book) // OK: book is transferred
// book.title = "x" // error: book was sent away
}How to fix "non-Sendable" errors
- Change the class to a struct, if it is just data.
- Make the class immutable and
final. - Turn it into an actor, if it has shared mutable state.
- Keep it on one actor, like
@MainActor, so it never crosses. - Protect it with
Mutexor a lock, as a last step.
Common follow-up questions
- Is
Sendablechecked at run time? No. It is a compile-time check only. There is no runtime cost. - Why are
@MainActorclasses Sendable? All their state is protected by the main actor. Any access from elsewhere needsawait, so sharing the reference is safe. - What is
@preconcurrency? It softens Sendable errors for types from modules that are not updated for Swift concurrency yet. - Can a protocol require Sendable? Yes:
protocol Service: Sendable { }. All conforming types must then be Sendable. - Is
[String: Any]Sendable? No, becauseAnymight hold a class. Use a concrete type or[String: any Sendable].