Sharing State
Application state.
Learning Objectives
- Inject shared data into handlers
##Sharing data between handlers
Handlers are independent functions, but real applications need shared things: a database pool, configuration, a cache, a counter. Actix Web shares them with application state.
#Register state with app_data
Wrap the value in web::Data and register it on the App. Handlers receive it by adding a web::Data<T> argument:
use actix_web::{get, web, App, HttpServer, Responder};
struct AppState {
app_name: String,
}
#[get("/")]
async fn index(data: web::Data<AppState>) -> impl Responder {
format!("Hello from {}", data.app_name)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let state = web::Data::new(AppState { app_name: "ActixWeb".into() });
HttpServer::new(move || {
App::new()
.app_data(state.clone())
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}web::Data is an Arc under the hood, so state.clone() is cheap and every worker points to the same value.
#Mutable state needs a lock
Handlers run on many threads at the same time, so Rust will not let you mutate shared data without synchronization. Wrap the field in a Mutex:
use std::sync::Mutex;
struct Counter {
visits: Mutex<u32>,
}
#[get("/visit")]
async fn visit(data: web::Data<Counter>) -> impl Responder {
let mut visits = data.visits.lock().unwrap();
*visits += 1;
format!("Visits: {}", *visits)
}Create the web::Data<Counter> outside the HttpServer::new closure and clone it inside. If you create it inside the closure, every worker would get its own separate counter.
#Keep locks short
Never hold a std::sync::Mutex guard across an .await. Lock, change the value, and release it before awaiting anything. For read-heavy data use RwLock, and for counters prefer atomics such as AtomicUsize.
#What belongs in state
Good candidates are database pools (they are already thread safe, so no Mutex), configuration, HTTP clients and caches. Avoid storing per-request data there; use extractors for that.
Interactive Code Inspector
Select any highlighted line of code below to deeply inspect its mechanics, understand why it exists, and what happens if you change it.
"What If?" Experiment
Learning by doing. Modify the code to see what happens.
Prove why state must be created outside the closure.
Move the web::Data::new(...) call inside the closure and reload the page several times with .workers(4).
The counter appears to jump back and forth because each worker has its own copy.
Explanation: Requests are spread across workers. With one Data per worker, each one counts separately; one shared Data gives a single consistent count.
Common Mistakes
Anticipate and avoid these frequent pitfalls when working with this concept.
Creating state inside the factory closure
The closure runs once per worker, so each worker gets a separate counter.
Explanation: Build the Data once, move it into the closure and clone it there so every worker shares the same value.
Holding a Mutex guard across an await
The guard stays locked while the task is suspended, which can block other requests or deadlock.
Explanation: Limit the lock to a small scope and release it before awaiting. Use tokio::sync::Mutex only if you truly must hold it across an await.
Practical Exercise
Build a visit counter
TaskCreate a GET /count endpoint that increments and returns a shared counter.
Requirements
- Store the counter in state using a Mutex
- Create the state outside the HttpServer closure
- Return the new value on every request
Need a hint?
Try solving the exercise in your local environment first. Once you have a solution, or if you get completely stuck, reveal the answer.
Real-World Application
Production services share a database connection pool, loaded configuration, a metrics registry and HTTP clients through web::Data. The pool is already thread safe, so it needs no Mutex.
Key Takeaways
- web::Data<T> is an Arc, so cloning it is cheap and all workers share one value.
- Register with .app_data(state.clone()) and read with a web::Data<T> handler argument.
- Mutating shared state needs Mutex, RwLock or atomics.
- Create shared state outside the HttpServer::new closure, otherwise each worker gets its own copy.
Finished reading?
Mark this lesson as done to track your progress and earn XP.