LazyLock removes a dependency, not your startup policy
Rust 1.80 brought lazy initialization into the standard library. Choose when failures surface before replacing once_cell or raising your compiler requirement.
A service can start successfully and still be unable to do its essential job. Put mandatory configuration behind first-access initialization and that failure may appear only when someone uses the affected feature. Rust 1.80 made this pattern easier to express without an external crate. It did not decide when your application should discover that it cannot run.
Use the standard-library types when they cover the required behaviour and your supported compiler permits them. Decide the startup policy separately. Critical configuration deserves deliberate validation before the service accepts work; an optional, expensive value may reasonably wait. Looking back at the July 2024 release, that distinction matters more than the shorter dependency list.
What became standard
The 25 July 2024 Rust 1.80 announcement introduced stable LazyCell and LazyLock. Both carry their initializer and defer its execution until first access. LazyLock supplies thread synchronization; LazyCell does not implement Sync. The choice therefore depends on where the value lives and how it is shared.
This followed Rust 1.70, released on 1 June 2023, which stabilized OnceCell and OnceLock. Those earlier types support one-time initialization without storing the initializer in the same way. The releases establish a useful chronology: one-time storage was already available, while the convenient lazy types arrived in 1.80.
That is enough reason to inspect an existing dependency. It is insufficient reason to delete it everywhere. A library may promise an older minimum supported Rust version. An application may still use other features from the same crate. Removing one import does not prove the crate has disappeared from the dependency graph, and a cleaner manifest does not establish a performance improvement.
First access assigns the work to a caller
Consider an illustrative service with a configuration value needed for every accepted order. If parsing that value happens only when the first order arrives, a successful process launch says little about its readiness to handle orders. The first customer becomes the occasion for discovering a deployment mistake.
For that service, an explicit startup step gives the operator a better boundary: either required configuration is valid and work may begin, or startup fails with a useful explanation. This is an architectural recommendation, not an account of a measured incident. The benefit is that the failure is attached to deployment rather than hidden in an ordinary request.
There is a latency question too. The current LazyLock documentation says a caller blocks if another thread is running the initializer. Those docs are mutable and were read in November 2026; they are used here to explain the documented contract, not to assign every method on the page to Rust 1.80. A shared initializer still has to finish before waiting callers can use its result.
Do not describe that waiting as automatically unacceptable. A small local calculation may be harmless. A costly initialization on a frequently used path deserves investigation. Without measurements from the actual workload, neither “faster startup” nor “slower requests” is a quantified result.
Failure needs a design of its own
The same current documentation states that an initialization panic poisons LazyLock, and later access panics too. It is therefore a poor basis for casually promising automatic retries after a temporary problem. A one-time value and a recoverable connection attempt solve different problems.
An expected configuration error also deserves different treatment from a programming panic. If invalid input is part of the operating environment, report it through a deliberate error path. If the stored value represents a failed result, callers still need to handle that result. Wrapping the failure in shared storage does not make the required business action disappear.
For optional functionality, the opposite choice can be sound. Suppose an infrequently used export feature needs an expensive local lookup structure. Making every process construct it before serving unrelated requests may waste work. First access can be the right boundary if the feature has an understandable failure response and its first-use delay is acceptable. This scenario is illustrative, not a benchmark or client example.
The strongest reason to keep what works
Dependency removal can be a distraction. A mature application with a supported older compiler and a working initialization policy may gain little from a substitution. Raising the compiler floor can affect downstream users or the build environment. That is a real compatibility cost, even when the source change is small.
The sensible objection is therefore about total maintenance cost. If an external crate supplies behaviour you still need, keeping it may be cheaper than rebuilding that behaviour around a standard type. If a library's compatibility promise excludes Rust 1.80, that promise should outweigh the desire for fewer dependencies until the version policy changes deliberately.
For an application already on a compatible compiler, a narrow migration is easier to justify. Identify each initializer's owner, inputs and failure behaviour. Keep startup timing unchanged while replacing equivalent storage. Review any timing change as a separate decision, because it changes which operation encounters the work and the error.
A useful software maintenance brief should say which values must be ready before work begins and which may wait. Rust 1.80 gives that design a standard implementation option. The application still needs an explicit answer about when it is ready to accept responsibility for a request.
Paul