Are property wrappers thread-safe? Is lazy?

1.6.5 · L5 · Interview

Mental model. lazy is a “first one in builds the fire” rule with no referee. Two people can both start building.

No by default. A wrapper is exactly as safe as its implementation. lazy uses a hidden initialization flag that is not atomic — two threads can both observe “not initialized” and run the initializer twice, leaking one result (or worse, if the initializer has side effects).

If you need safety, use an actor, OSAllocatedUnfairLock, or compute the value once at initialization. A hand-rolled @Atomic wrapper still has to document reentrancy and what happens under contention.

Common traps

  • “lazy is fine because it only runs once.” “Once” is not guaranteed under concurrency — it is a data race, and in Swift 6 mode it is a diagnosable one.
  • “A lock makes the wrapper correct.” A lock around a getter that calls back into the same object can deadlock. Prefer immutable state or an actor.

They might follow up with

  • What is the safe pattern for a cache? An actor, or a lock-protected dictionary with a documented policy for in-flight work.
  • Does @MainActor solve it? Only if every access is main-actor isolated. It moves the race to “did you actually stay on the main actor?”

Related questions

  • 1.6.1 Stored vs computed vs lazy vs property observers (willSet/didSet)?
  • 1.6.3 What is a property wrapper? Can you write a custom one?
  • 1.6.4 What is projectedValue? Give a real example.

← What is projectedValue? Give a real example.