Fatal error: unexpectedly found nil while unwrapping an Optional value – Swift lazy initialization
0 reputation · 26 Apr 2021, 13:57 UTC
0 reputation · 26 Apr 2021, 13:57 UTC
In a low‑traffic Swift service that relies on lazy properties to defer expensive configuration loading, the first access triggers a thread‑synchronization barrier. If the initialization closure fails or returns nil, the runtime throws fatal error: unexpectedly found nil while unwrapping an Optional value, terminating the process.
The goal is to reduce memory and CPU usage by never allocating these heavy objects unless a request actually needs them. However, the cost of the synchronization on first access, potential nil unwrapping, and the guarantee that a lazy property is truly never allocated when unused remain uncertain.
Unresolved questions:
lazy var in a multi‑threaded Swift runtime?nil without causing a fatal error, or must the closure always produce a non‑optional value?34775 reputation · 27 Apr 2021, 00:46 UTC
The fatal error: unexpectedly found nil while unwrapping an Optional value is almost certainly not caused by lazy initialization itself. It is the standard Swift runtime trap for force-unwrapping a nil Optional. In your setup the most likely explanation is one of two things: the lazy property is typed as an Optional, its closure legitimately returned nil, and a later call site force-unwraps it — or the initialization closure itself contains a force-unwrap (or a force-unwrapped failable initializer) that traps on first access. Check the crash backtrace: if the trapping frame is inside your closure, fix the closure; if it is at a use site, fix the unwrap there.
Only if the property's type is Optional. A lazy var with a non-optional type cannot return nil — the compiler rejects it. So this compiles and never crashes at initialization:
lazy var config: AppConfig? = loadConfig() // may be nil, no trap here
The crash arrives later, at something like config!.endpoint. The fix for a service like yours is to make the closure total: return a non-optional value, handling failure inside the closure with a fallback or a throwing path you catch:
lazy var config: AppConfig = {
if let loaded = try? loadConfig() { return loaded }
return AppConfig.defaults // never nil, never traps
}()
That removes the nil-unwrap failure mode entirely, at the cost of deciding on a fallback policy once, in one place.
Swift guarantees a lazy var initializes at most once, and the runtime guards first access so the closure cannot run concurrently on two threads. The exact nanosecond cost of that guard is implementation- and toolchain-dependent — there is no published fixed figure, and any exact number you read elsewhere should be treated as unverifiable. Two practical points: after initialization, reads are ordinary property accesses with no barrier, and in a low-traffic service the one-time first-access cost is almost always dwarfed by the expensive work the closure itself performs. If the cost genuinely matters, benchmark first-access versus steady-state access on your exact Swift version and platform rather than trusting a generic number.
The guarantee you actually get is narrower than "zero memory": if the property is never accessed, the closure never runs, so the heavy object is never allocated. However, the enclosing instance still reserves a small backing slot (storage for the value plus its initialization state), so the instance footprint is not literally zero. You can verify the meaningful half of this cheaply — put a log statement or breakpoint in the closure and confirm it never executes for requests that don't touch the property.
If this lazy property lives on a struct rather than a class, accessing it is a mutating operation, which interacts badly with concurrent use of copies. For shared service configuration, keep lazy state on a class (or an actor) so the once-only semantics apply to a single shared instance.
! from the path.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.