What are Swift concurrency isolation rules?
The core concept: isolation domains
Every piece of code belongs to exactly one isolation domain: a specific actor, the global MainActor, or “non-isolated.” The rules govern (1) what you can access synchronously vs. needing await, and (2) what’s allowed to cross domains.
Rule 1: Actor-isolated state requires being “on” that actor
actor BankAccount {
var balance: Double = 0
func deposit(_ amount: Double) { balance += amount }
}
await account.deposit(10) // must await from outside
Rule 2: Global actors apply the same rule to free-floating declarations
@MainActor extends actor isolation to types/functions/properties that aren’t actors themselves.
Rule 3: Isolation is inherited by default within a context
A Task { } created inside an isolated context inherits that isolation unless broken by Task.detached.
Rule 4: nonisolated opts a declaration out of its enclosing isolation
actor Logger {
nonisolated let id = UUID()
nonisolated func staticInfo() -> String { "Logger" }
}
Swift 6.2’s nonisolated(nonsending) additionally lets a nonisolated async function choose to stay on the caller’s isolation domain vs. @concurrent hopping to the global executor.
Rule 5: Crossing an isolation boundary requires Sendable (or sending)
class MutableBox { var value = 0 } // not Sendable
func launch(_ box: MutableBox) {
Task { box.value += 1 } // ❌ error unless Sendable or `sending`
}
Rule 6: Structured concurrency ties child task isolation to the parent
async let and TaskGroup children inherit the isolation context of where they’re created.
Rule 7: Protocol conformance and isolation must agree
A method satisfying a protocol requirement must match the protocol’s isolation expectations.
The unifying model
Two enforced invariants:
- You can’t touch isolated mutable state without proving you’re already on that isolation domain.
- You can’t move a value across isolation domains without proving it’s safe to share.
What these rules explicitly do NOT cover
- No reentrancy bugs
- No deadlocks
- Correct business logic
One-liner if pressed: “Swift’s isolation rules boil down to two enforced invariants: you can’t synchronously touch actor-isolated state unless you’re already on that actor, and you can’t move a value across isolation domains unless it’s Sendable or provably safe to send — everything else is about controlling where code runs and how isolation propagates through that structure.”