Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
169: State Sharing in Axum Applications
Imagine you're running a professional kitchen. You have five different chefs (your request handlers) all working on different orders simultaneously. Now, these chefs all need access to the same master recipe book and a shared spice rack. It wouldn't make sense for every single chef to carry their own identical copy of the recipe book or their own personal set of 50 spices; it's a waste of space and a nightmare to keep them all updated. Instead, you put the book and the spices in one central location where everyone can reach them.
In Axum, your "shared spice rack" is the State. But there's a catch: because Axum is asynchronous and multi-threaded, it's like having chefs who might try to grab the same spice jar at the exact same millisecond. Rust's compiler will stop you right there unless you provide a way to handle that concurrent access safely.
Defining the Shared Resource
To get started, you need a struct that holds everything your handlers need. I usually recommend wrapping this state in an Arc (Atomic Reference Counted pointer). Since Axum clones the state for every request, Arc allows all those clones to point to the same memory location rather than duplicating the data.
use std::sync::{Arc, Mutex};
use axum::{extract::State, routing::get, Router};
struct AppState {
// We use Mutex because we want to change this value during runtime
request_count: Mutex,
app_name: String,
}
type SharedState = Arc;
Notice that app_name is just a String, but request_count is wrapped in a Mutex. If the data is read-only, you don't need the mutex. But the moment you want to increment a counter or update a cache, you need that lock to prevent data races.
Wiring the State into the Router
Once your state is defined, you have to tell your router about it. You don't pass the state into the handlers manually; instead, you use the with_state method on your Router. This effectively "injects" the spice rack into the kitchen.
#[tokio::main]
async fn main() {
let shared_state = Arc::new(AppState {
request_count: Mutex::new(0),
app_name: "Mentorship API".to_string(),
});
let app = Router::new()
.route("/", get(handler))
.with_state(shared_state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Extracting State in Your Handlers
Now for the part where the "chefs" actually use the tools. Axum uses an extractor called State. You simply add it as an argument to your handler function, and Axum handles the heavy lifting of pulling the correct reference out of the router's state.
async fn handler(State(state): State) -> String {
// Lock the mutex to get access to the counter
let mut count = state.request_count.lock().unwrap();
*count += 1;
format!("Welcome to {}! You are visitor number {}.", state.app_name, count)
}
I've seen a lot of developers struggle here by forgetting that State<T> requires T to be Clone. That's exactly why we used Arc. Cloning an Arc doesn't clone the data inside; it just bumps the reference count, making it an extremely cheap operation that satisfies Axum's requirements.
A Note on Granularity
One piece of advice from the trenches: don't put everything in one giant Mutex. If you have ten different variables in one Mutex<AppState>, only one handler can access any of those variables at a time. This creates a bottleneck. Instead, put the Mutex around the specific fields that need to change, just like I did with request_count above. This allows one handler to read the app_name while another is updating the counter.
📋 Practical Task
Exercise: Building a Shared API Key Validator
Your goal is to create a small Axum application that manages a list of "authorized" API keys in shared state. Instead of a hard-coded list, the keys should be stored in a HashSet inside the state, allowing the application to track which keys are currently valid.
Requirements:
- Create an
AppStatestruct containing aHashSet<String>wrapped in aRwLock(since you'll be reading keys more often than writing them). - Implement a GET route
/validate?key=your_keythat checks if the provided key exists in the shared state. - Implement a POST route
/add_keythat takes a string and adds a new key to the shared state. - Use
Arcto share the state across these two handlers. - Ensure the
/validatehandler returns a200 OKif the key is found and a403 Forbiddenif it is not.
There are no comments for now.