Skip to content

Commit a5116d6

Browse files
authored
axum: add docs describing how, why and when to use &'static state (#3838)
1 parent a2e64a8 commit a5116d6

1 file changed

Lines changed: 70 additions & 1 deletion

File tree

axum/src/lib.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,76 @@
186186
//! State is cloned for every request. Wrapping your state in `Arc` makes those
187187
//! clones cheap. If all fields are already cheap to clone (for example, each field
188188
//! is itself an `Arc` or a copy type), you can `#[derive(Clone)]` directly on the
189-
//! struct instead.
189+
//! struct instead. Many client types, such as [`reqwest::Client`], AWS SDK service
190+
//! clients, and [`mongodb::Client`], already use shared ownership internally and
191+
//! are cheap to clone. Such clients generally do not need another `Arc` solely to
192+
//! make cloning cheap; doing so adds another level of indirection.
193+
//!
194+
//! [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
195+
//! [`mongodb::Client`]: https://docs.rs/mongodb/latest/mongodb/struct.Client.html
196+
//!
197+
//! ### Using `&'static` state
198+
//!
199+
//! For state built once and intended to live until the process exits, [`Box::leak`]
200+
//! or a static [`LazyLock`] can provide a `&'static AppState` to the router. Use
201+
//! `Box::leak` for state initialized in `main`, including when state initialization
202+
//! requires asynchronous work; use a static `LazyLock` for global state with a
203+
//! synchronous lazy initializer. This is useful for state built from runtime
204+
//! configuration, database pools, or service clients:
205+
//!
206+
//! ```rust
207+
//! use axum::{
208+
//! extract::State,
209+
//! routing::get,
210+
//! Router,
211+
//! };
212+
//!
213+
//! struct AppState {
214+
//! // A database pool, service clients, configuration, etc.
215+
//! }
216+
//!
217+
//! impl AppState {
218+
//! async fn work(&self) {}
219+
//! }
220+
//!
221+
//! fn main() {
222+
//! let app_state = AppState {
223+
//! // Initialize fields at startup.
224+
//! };
225+
//! let app_state: &'static AppState = Box::leak(Box::new(app_state));
226+
//!
227+
//! let app = Router::new()
228+
//! .route("/", get(handler))
229+
//! .with_state(app_state);
230+
//! # let _: Router = app;
231+
//! }
232+
//!
233+
//! async fn handler(State(state): State<&'static AppState>) {
234+
//! let _task = tokio::spawn(async move {
235+
//! state.work().await;
236+
//! });
237+
//!
238+
//! // `state` is still available in the handler because it was copied.
239+
//! state.work().await;
240+
//! }
241+
//! ```
242+
//!
243+
//! Like every shared reference, a `&'static T` is `Copy`, so axum can clone it for
244+
//! each request without an atomic reference-count operation. This can be cheaper than
245+
//! cloning an `Arc`, but it is rarely a reason on its own to choose process-lifetime
246+
//! state since the request's actual work normally matters more. The more practical
247+
//! advantage of combining `Copy` with the `'static` lifetime is shown above: an
248+
//! `async move` block copies the reference into the spawned Tokio task while leaving
249+
//! the handler's copy available. With `Arc`, call [`Arc::clone`] before spawning when
250+
//! the handler also needs the state.
251+
//!
252+
//! `Box::leak` deliberately leaks its allocation, and state held by a static
253+
//! `LazyLock` is likewise never dropped. Use either only for process-lifetime state.
254+
//! Use `Arc` when the state needs a managed lifetime.
255+
//!
256+
//! [`Arc::clone`]: https://doc.rust-lang.org/std/sync/struct.Arc.html#method.clone
257+
//! [`Box::leak`]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.leak
258+
//! [`LazyLock`]: https://doc.rust-lang.org/std/sync/struct.LazyLock.html
190259
//!
191260
//! ### Substates with `FromRef`
192261
//!

0 commit comments

Comments
 (0)