What is an isolated parameter, and when would you write a function that takes one instead of making it an actor method?

10.9.2 · L5 · Interview

Predict first

Two isolated calls bump the balance. What is printed?

actor Account { var balance = 0 }
func read(_ a: isolated Account) -> Int { a.balance }
func bump(_ a: isolated Account) { a.balance += 1 }
let a = Account()
await bump(a)
await bump(a)
print(await read(a))
  • 2
  • 0
  • Compile error: isolated is only for methods

Mental model. Instead of phoning the office for every item, you step into the room, do everything on the list while you are inside, and walk out once.

An isolated parameter says “this function runs on that actor, for the whole call”. Inside bump(_ a: isolated Account) you can read and write a.balance directly with no await, because the compiler knows the function is executing in a’s isolation; callers cross the boundary once with a single await bump(a). In the check, two calls leave the balance at 2. It is a way to write free functions, extensions and generic helpers that operate on an actor’s state without being declared inside the actor’s body (or without a dozen tiny await hops).

Use it when the work is a transaction on actor state that should not be split by suspension points, or when you want a utility function usable by several actors (a generic func log<A: Actor>(_ a: isolated A), or a function taking isolated (any Actor)? with #isolation to inherit its caller’s isolation). The rules are simple: only one parameter may be isolated, it must be an actor (or global-actor instance) type, and the function is then isolated to it. Prefer a method on the actor for its own core logic, and an isolated parameter for helpers that belong elsewhere but need atomic access.

Each call runs inside the actor, so mutating the balance needs no await in the function.

Common traps

  • Awaiting between reads and writes in a caller. Each hop is a separate stretch; put the whole transaction in one isolated function.
  • Passing two actors and expecting both to be isolated. Only one parameter can be isolated at a time.

They might follow up with

  • What does #isolation do? It captures the caller’s current isolation so a helper can run in it without hopping.
  • How is this different from assumeIsolated? assumeIsolated checks at runtime that you already are on the actor; an isolated parameter is checked at compile time.

Related questions

  • 10.9.1 Which accesses to an actor compile without await, which do not compile at all, and what do nonisolated…
  • 10.9.3 How do you define your own global actor, what does annotating a type with it do, and when is it better than…
  • 27.4.1 Two tasks each withdraw 80 from an actor account holding 100, with an await between the balance check and the…
  • 10.4.1 What does an actor guarantee, and what is actor reentrancy?
  • 10.4.2 How do @MainActor, global actors, isolated and nonisolated work, and how does isolation propagate?

← Which accesses to an actor compile without await, which do not…   How do you define your own global actor, what does annotating a type… →