Are property wrappers thread-safe? Is lazy?
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
- “
lazyis 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
@MainActorsolve it? Only if every access is main-actor isolated. It moves the race to “did you actually stay on the main actor?”