axum: add docs describing how, why and when to use &'static state (#3838)

This commit is contained in:
Falk Woldmann Lu
2026-08-03 21:32:21 +02:00
committed by GitHub
parent a2e64a8fc3
commit a5116d6b1b
+70 -1
View File
@@ -186,7 +186,76 @@
//! State is cloned for every request. Wrapping your state in `Arc` makes those
//! clones cheap. If all fields are already cheap to clone (for example, each field
//! is itself an `Arc` or a copy type), you can `#[derive(Clone)]` directly on the
//! struct instead.
//! struct instead. Many client types, such as [`reqwest::Client`], AWS SDK service
//! clients, and [`mongodb::Client`], already use shared ownership internally and
//! are cheap to clone. Such clients generally do not need another `Arc` solely to
//! make cloning cheap; doing so adds another level of indirection.
//!
//! [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
//! [`mongodb::Client`]: https://docs.rs/mongodb/latest/mongodb/struct.Client.html
//!
//! ### Using `&'static` state
//!
//! For state built once and intended to live until the process exits, [`Box::leak`]
//! or a static [`LazyLock`] can provide a `&'static AppState` to the router. Use
//! `Box::leak` for state initialized in `main`, including when state initialization
//! requires asynchronous work; use a static `LazyLock` for global state with a
//! synchronous lazy initializer. This is useful for state built from runtime
//! configuration, database pools, or service clients:
//!
//! ```rust
//! use axum::{
//! extract::State,
//! routing::get,
//! Router,
//! };
//!
//! struct AppState {
//! // A database pool, service clients, configuration, etc.
//! }
//!
//! impl AppState {
//! async fn work(&self) {}
//! }
//!
//! fn main() {
//! let app_state = AppState {
//! // Initialize fields at startup.
//! };
//! let app_state: &'static AppState = Box::leak(Box::new(app_state));
//!
//! let app = Router::new()
//! .route("/", get(handler))
//! .with_state(app_state);
//! # let _: Router = app;
//! }
//!
//! async fn handler(State(state): State<&'static AppState>) {
//! let _task = tokio::spawn(async move {
//! state.work().await;
//! });
//!
//! // `state` is still available in the handler because it was copied.
//! state.work().await;
//! }
//! ```
//!
//! Like every shared reference, a `&'static T` is `Copy`, so axum can clone it for
//! each request without an atomic reference-count operation. This can be cheaper than
//! cloning an `Arc`, but it is rarely a reason on its own to choose process-lifetime
//! state since the request's actual work normally matters more. The more practical
//! advantage of combining `Copy` with the `'static` lifetime is shown above: an
//! `async move` block copies the reference into the spawned Tokio task while leaving
//! the handler's copy available. With `Arc`, call [`Arc::clone`] before spawning when
//! the handler also needs the state.
//!
//! `Box::leak` deliberately leaks its allocation, and state held by a static
//! `LazyLock` is likewise never dropped. Use either only for process-lifetime state.
//! Use `Arc` when the state needs a managed lifetime.
//!
//! [`Arc::clone`]: https://doc.rust-lang.org/std/sync/struct.Arc.html#method.clone
//! [`Box::leak`]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.leak
//! [`LazyLock`]: https://doc.rust-lang.org/std/sync/struct.LazyLock.html
//!
//! ### Substates with `FromRef`
//!