Chapter 13 — Isolation in depth

Isolation is a property of every declaration

  • isolated to a specific actor instance (a method on an actor),
  • isolated to a global actor (@MainActor func),
  • or nonisolated (belongs to no actor — the default for free functions and value-type methods).

nonisolated, precisely

actor Job: CustomStringConvertible {
    let id: Int
    var progress = 0
    init(id: Int) { self.id = id }

    // Must be nonisolated to satisfy the synchronous protocol requirement.
    nonisolated var description: String { "Job(\(id))" }   // can only touch immutable `id`
}

Isolated parameters: "run me on this actor"

// This function runs isolated to whatever actor you hand it.
func addEntry(to cache: isolated ImageCache, url: URL, image: UIImage) {
    cache.insert(image, for: url)   // no await! we're isolated to `cache` here
}

Region-based isolation: the compiler tracks where values go

func rehome() async {
    let chicken = Chicken()          // non-Sendable
    await island.adopt(chicken)      // ✅ crossing allowed: `chicken` is never used again here
    // If we touched `chicken` after this line, the compiler would reject the crossing.
}
flowchart LR C["create non-Sendable value"] --> SEND["send it to another domain"] SEND --> Q{"used again in
the sender?"} Q -->|"No"| OK["✅ it was a transfer — safe"] Q -->|"Yes"| BAD["❌ it's shared — rejected"]

sending: transfer across a boundary by contract

// Without sending: ERROR — ColorComponents is non-Sendable, crossing to @MainActor.
func updateStyle(backgroundColor: ColorComponents) async {
    await applyBackground(backgroundColor)   // ❌ risks a data race
}

// With sending: the parameter is transferred; the caller can't use it afterward → safe.
func updateStyle(backgroundColor: sending ColorComponents) async {
    await applyBackground(backgroundColor)   // ✅ proven safe: it's a hand-off, not sharing
}

The four fixes for a boundary-crossing error

flowchart TB ERR["'sending value risks data races'"] --> F1["1. Make the type Sendable
(if it can be a value type / immutable)"] ERR --> F2["2. Mark the parameter `sending`
(if it's a genuine transfer)"] ERR --> F3["3. Isolate the whole function
(so there's no boundary to cross)"] ERR --> F4["4. Use a @Sendable closure
(compute the value in the target domain)"]
  1. Make the type Sendable. If it's really a value or can be made immutable, this is the cleanest —
  2. Mark the parameter sending. If the value is genuinely handed off and not reused, this expresses
  3. Isolate the whole function. If the "boundary" only exists because your function is nonisolated
  4. Use a @Sendable closure. Instead of sending a value in, pass a closure that produces the

nonisolated(unsafe): the storage escape hatch

// A global the compiler would otherwise demand be Sendable / isolated.
nonisolated(unsafe) var legacyGlobalCache: [String: Data] = [:]

Reading isolation errors fluently

  1. What is the isolation of the code I'm in? (nonisolated? @MainActor? some actor?)
  2. What boundary is being crossed, and in which direction? (into an actor, into a Task, to
  3. Is the value being shared or transferred? If transferred, sending or region analysis should
  4. Pick from the four fixes based on whether the type can be Sendable, whether it's a genuine

What we built in this chapter

  • Established that every declaration has an isolation (some actor, a global actor, or nonisolated),
  • Introduced isolated parameters (isolated ImageCache) for functions that run *on a passed-in
  • Explained region-based isolation — the compiler proves a non-Sendable value is transferred,
  • Laid out the four principled fixes for a boundary-crossing error (make it Sendable, mark it
  • Turned all of it into a method for reading isolation errors: identify your isolation, the boundary

Mental model to take away

  • Isolation is a property of every declaration. The annotations (nonisolated, isolated,
  • Region-based isolation means Swift 6 allows many non-Sendable crossings it can prove are
  • A crossing error has four principled fixes (Sendable / sending / isolate the function / Sendable
  • Read every isolation error as **"which value crosses which boundary, in which direction, shared or