Loading pageā¦
Rust walkthroughs
Loading pageā¦
http::Extensions::insert enable type-safe request/response metadata storage?http::Extensions::insert stores arbitrary values in a type-safe map where the type itself serves as the key, allowing you to attach metadata to requests and responses without defining wrapper types or using string-keyed hashmaps. The Extensions type uses std::any::TypeId internally to associate each stored value with its type, enabling O(1) retrieval with compile-time type guaranteesāyou can insert a value of type T and later retrieve it as Option<&T> without runtime casts or string key collisions. This pattern enables middleware to attach typed data to requests, handlers to pass information to downstream components, and applications to layer metadata without tight coupling.
use http::Extensions;
fn main() {
let mut extensions = Extensions::new();
// Insert a value - the type becomes the key
extensions.insert(42u32);
// Retrieve by type
if let Some(value) = extensions.get::<u32>() {
println!("Got u32: {}", value); // 42
}
// Type mismatch returns None
assert!(extensions.get::<i32>().is_none());
assert!(extensions.get::<String>().is_none());
}insert stores a value; get retrieves it using the type as the implicit key.
use http::Extensions;
fn main() {
let mut extensions = Extensions::new();
// Each type gets its own slot
extensions.insert(100u32);
extensions.insert("hello".to_string());
extensions.insert(vec
![1, 2, 3]);
extensions.insert(true);
// All can coexist without key conflicts
println!("u32: {:?}", extensions.get::<u32>());
// Some(100)
println!("String: {:?}", extensions.get::<String>());
// Some("hello")
println!("Vec<i32>: {:?}", extensions.get::<Vec<i32>>());
// Some([1, 2, 3])
println!("bool: {:?}", extensions.get::<bool>());
// Some(true)
}Each type has a unique slot; no string key collisions.
use http::{Request, Extensions};
#[derive(Debug)]
struct User {
id: u64,
name: String,
}
#[derive(Debug)]
struct AuthToken {
token: String,
expires_at: u64,
}
fn main() {
let mut request: Request<()> = Request::default();
// Extensions are part of every Request
let extensions = request.extensions_mut();
// Middleware can attach typed metadata
extensions.insert(User {
id: 42,
name: "Alice".to_string(),
});
extensions.insert(AuthToken {
token: "secret-token".to_string(),
expires_at: 1234567890,
});
// Handler retrieves typed data
let extensions = request.extensions();
if let Some(user) = extensions.get::<User>() {
println!("Authenticated user: {:?}", user);
}
if let Some(token) = extensions.get::<AuthToken>() {
println!("Token expires: {}", token.expires_at);
}
}Request has built-in Extensions for attaching request-scoped data.
use http::{Response, Extensions};
use std::time::Instant;
#[derive(Debug)]
struct Timing {
start: Instant,
database_ms: u64,
processing_ms: u64,
}
#[derive(Debug)]
struct CorrelationId(String);
fn main() {
let mut response: Response<String> = Response::new("Hello".to_string());
// Attach metadata to response
let extensions = response.extensions_mut();
extensions.insert(Timing {
start: Instant::now(),
database_ms: 15,
processing_ms: 5,
});
extensions.insert(CorrelationId("abc-123".to_string()));
// Later, logging middleware reads timing
let ext = response.extensions();
if let Some(timing) = ext.get::<Timing>() {
println!("DB: {}ms, Processing: {}ms",
timing.database_ms, timing.processing_ms);
}
if let Some(id) = ext.get::<CorrelationId>() {
println!("Correlation ID: {}", id.0);
}
}Response also includes Extensions for response-scoped metadata.
use http::Extensions;
// Contrast with string-keyed approach
use std::collections::HashMap;
fn main() {
// String-keyed: no type safety, runtime errors
let mut map: HashMap<&'static str, Box<dyn std::any::Any>> = HashMap::new();
map.insert("user_id", Box::new(42u64));
// Runtime type errors possible
// let id: &String = map["user_id"].downcast_ref::<String>().unwrap();
// Would panic at runtime
// Extensions: compile-time type safety
let mut extensions = Extensions::new();
extensions.insert(42u64);
// Type mismatch is caught by type system
// extensions.get::<String>() returns None safely
// No panic, just Option handling
let id: Option<&u64> = extensions.get::<u64>();
println!("User ID: {:?}", id); // Some(42)
// Wrong type returns None, doesn't panic
let wrong: Option<&String> = extensions.get::<String>();
println!("Wrong type: {:?}", wrong); // None
}Type-based keys eliminate string key typos and runtime type mismatches.
use http::Extensions;
fn main() {
let mut extensions = Extensions::new();
extensions.insert(10u32);
println!("First insert: {:?}", extensions.get::<u32>()); // Some(10)
// Insert same type - overwrites previous value
extensions.insert(20u32);
println!("Second insert: {:?}", extensions.get::<u32>()); // Some(20)
// insert returns the previous value if any
let old = extensions.insert(30u32);
println!("Replaced: {:?}", old); // Some(20)
println!("Current: {:?}", extensions.get::<u32>()); // Some(30)
}insert replaces existing values of the same type; returns the previous value.
use http::Extensions;
fn main() {
let mut extensions = Extensions::new();
extensions.insert(42u32);
extensions.insert("hello".to_string());
// Remove returns the value
let removed: Option<u32> = extensions.remove();
println!("Removed: {:?}", removed); // Some(42)
println!("After remove: {:?}", extensions.get::<u32>()); // None
// String still present
println!("String: {:?}", extensions.get::<String>()); // Some("hello")
// GetMut for mutable access
extensions.insert(vec
![1, 2, 3]);
if let Some(vec) = extensions.get_mut::<Vec<i32>>() {
vec.push(4);
}
println!("Modified vec: {:?}", extensions.get::<Vec<i32>>()); // Some([1, 2, 3, 4])
}remove takes ownership; get_mut allows modification.
use http::Extensions;
// Use newtypes to namespace similar types
#[derive(Debug)]
struct UserId(u64);
#[derive(Debug)]
struct SessionId(String);
#[derive(Debug)]
struct RequestId(String);
#[derive(Debug)]
struct OrganizationId(u64);
fn main() {
let mut extensions = Extensions::new();
// All IDs are distinct types
extensions.insert(UserId(42));
extensions.insert(SessionId("sess-123".to_string()));
extensions.insert(RequestId("req-456".to_string()));
extensions.insert(OrganizationId(1));
// No confusion between different ID types
let user: Option<&UserId> = extensions.get();
let session: Option<&SessionId> = extensions.get();
let request: Option<&RequestId> = extensions.get();
let org: Option<&OrganizationId> = extensions.get();
println!("User: {:?}", user);
println!("Session: {:?}", session);
println!("Request: {:?}", request);
println!("Org: {:?}", org);
}Newtypes prevent confusion between values with the same underlying type.
use http::{Request, Extensions};
#[derive(Debug)]
struct AuthenticatedUser {
id: u64,
roles: Vec<String>,
}
#[derive(Debug)]
struct RequestStartTime(std::time::Instant);
// Authentication middleware
fn auth_middleware(mut request: Request<String>) -> Request<String> {
// Simulate authentication
let auth_header = request.headers()
.get("Authorization")
.and_then(|v| v.to_str().ok());
if let Some(_token) = auth_header {
// Attach authenticated user
request.extensions_mut().insert(AuthenticatedUser {
id: 42,
roles: vec
!["admin".to_string(), "user".to_string()],
});
}
request.extensions_mut().insert(RequestStartTime(std::time::Instant::now()));
request
}
// Handler uses extensions
fn handler(request: Request<String>) -> String {
let ext = request.extensions();
// Check authentication
if ext.get::<AuthenticatedUser>().is_none() {
return "Unauthorized".to_string();
}
let user = ext.get::<AuthenticatedUser>().unwrap();
let start = ext.get::<RequestStartTime>().unwrap();
format!("Hello user {}! Roles: {:?}", user.id, user.roles)
}
fn main() {
let mut request = Request::default();
request.headers_mut().insert("Authorization", "Bearer token".parse().unwrap());
let request = auth_middleware(request);
let response = handler(request);
println!("Response: {}", response);
}Middleware attaches typed data; handlers retrieve it safely.
use http::Extensions;
fn main() {
let mut extensions = Extensions::new();
extensions.insert(1u32);
extensions.insert(2u64);
extensions.insert("test".to_string());
println!("Before clear: {}", extensions.len()); // 3
extensions.clear();
println!("After clear: {}", extensions.len()); // 0
assert!(extensions.get::<u32>().is_none());
}clear() removes all stored values.
use http::Extensions;
#[derive(Debug, Clone)]
struct AppState {
db_pool: String,
cache: String,
}
#[derive(Debug)]
struct User {
id: u64,
name: String,
}
#[derive(Debug)]
struct Permissions {
can_read: bool,
can_write: bool,
}
// Simulated request processing
struct Request {
extensions: Extensions,
path: String,
}
impl Request {
fn new(path: &str) -> Self {
Self {
extensions: Extensions::new(),
path: path.to_string(),
}
}
fn extensions(&self) -> &Extensions {
&self.extensions
}
fn extensions_mut(&mut self) -> &mut Extensions {
&mut self.extensions
}
}
fn main() {
let mut request = Request::new("/api/users/42");
// Layer 1: Add app state
request.extensions_mut().insert(AppState {
db_pool: "postgres://localhost".to_string(),
cache: "redis://localhost".to_string(),
});
// Layer 2: Authentication
request.extensions_mut().insert(User {
id: 42,
name: "Alice".to_string(),
});
// Layer 3: Authorization
request.extensions_mut().insert(Permissions {
can_read: true,
can_write: false,
});
// Handler
let ext = request.extensions();
let user = ext.get::<User>().expect("user required");
let perms = ext.get::<Permissions>().expect("permissions required");
if !perms.can_write {
println!("User {} denied write access", user.name);
}
}Layers add typed data progressively; handlers depend on specific types.
use http::Extensions;
use std::collections::HashMap;
// Wrap external types that don't support extensions
struct DatabaseConnection {
pool_id: u64,
}
// Can't add fields to DatabaseConnection, but can attach to Extensions
fn main() {
let mut extensions = Extensions::new();
// Store connection metadata alongside the connection
extensions.insert(DatabaseConnection { pool_id: 1 });
// Additional metadata
extensions.insert(HashMap::<String, String>::new());
extensions.insert(false); // Transaction started?
// Use the connection
if let Some(conn) = extensions.get::<DatabaseConnection>() {
println!("Using connection pool {}", conn.pool_id);
}
// Set transaction flag
if let Some(started) = extensions.get_mut::<bool>() {
*started = true;
}
// Add to metadata map
if let Some(meta) = extensions.get_mut::<HashMap<String, String>>() {
meta.insert("query_count".to_string(), "5".to_string());
}
}Extensions adds metadata to types that don't support additional fields.
use http::Extensions;
use std::sync::Arc;
// Extensions is not Sync by default
// For sharing across threads, wrap stored values appropriately
#[derive(Debug)]
struct SharedState {
counter: Arc<std::sync::atomic::AtomicU64>,
}
fn main() {
let mut extensions = Extensions::new();
// Store Arc for thread-safe sharing
let state = Arc::new(SharedState {
counter: Arc::new(std::sync::atomic::AtomicU64::new(0)),
});
extensions.insert(state.clone());
// Extensions itself can be moved between threads
let ext_clone = state;
// Use Arc for thread-safe state in extensions
std::thread::spawn(move || {
ext_clone.counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
});
}Store Arc<T> for thread-safe sharing; Extensions itself is not thread-safe.
use http::Extensions;
// Two values of same type - only last is kept
fn main() {
let mut extensions = Extensions::new();
#[derive(Debug)]
struct Config {
name: String,
}
// Multiple configs - same type
extensions.insert(Config { name: "database".to_string() });
extensions.insert(Config { name: "cache".to_string() });
// Only the last insert is retained
let config = extensions.get::<Config>();
println!("Config: {:?}", config); // Some(Config { name: "cache" })
// Use different types for different values
#[derive(Debug)]
struct DatabaseConfig { url: String }
#[derive(Debug)]
struct CacheConfig { url: String }
let mut ext2 = Extensions::new();
ext2.insert(DatabaseConfig { url: "postgres://...".to_string() });
ext2.insert(CacheConfig { url: "redis://...".to_string() });
// Both can coexist
println!("DB: {:?}", ext2.get::<DatabaseConfig>());
println!("Cache: {:?}", ext2.get::<CacheConfig>());
}Same type overwrites; use distinct types for distinct values.
use http::Extensions;
#[derive(Debug)]
struct Authenticated;
#[derive(Debug)]
struct AdminUser;
fn main() {
let mut extensions = Extensions::new();
// Use presence as a flag
extensions.insert(Authenticated);
// Check authentication
if extensions.get::<Authenticated>().is_some() {
println!("User is authenticated");
// Check additional flags
if extensions.get::<AdminUser>().is_some() {
println!("User is admin");
} else {
println!("User is not admin");
}
}
// Remove to de-authenticate
extensions.remove::<Authenticated>();
if extensions.get::<Authenticated>().is_none() {
println!("User logged out");
}
}Use unit structs as flags; presence indicates state.
use http::Extensions;
#[derive(Debug)]
struct RequestData {
id: u64,
}
fn process_request(extensions: &mut Extensions) {
// Extensions live as long as the request
extensions.insert(RequestData { id: 42 });
// Reading
if let Some(data) = extensions.get::<RequestData>() {
println!("Processing request {}", data.id);
}
// Extensions cleared when request completes
}
fn main() {
let mut extensions = Extensions::new();
process_request(&mut extensions);
// Extensions still exist, data preserved
println!("After processing: {:?}", extensions.get::<RequestData>());
// Drop extensions when done
drop(extensions);
}Extensions lives as long as its owner; no global state pollution.
Key methods:
| Method | Purpose | Returns |
|--------|---------|---------|
| insert::<T>(value) | Store a value by type | Option<T> (old value) |
| get::<T>() | Get reference by type | Option<&T> |
| get_mut::<T>() | Get mutable reference | Option<&mut T> |
| remove::<T>() | Remove and return value | Option<T> |
| clear() | Remove all values | () |
| len() | Count stored values | usize |
Type-based storage patterns:
| Pattern | Use Case | |---------|----------| | Unit structs | Boolean flags | | Newtype wrappers | Namespaced IDs | | Arc | Thread-safe shared state | | Config structs | Layered configuration | | Middleware data | Request/response metadata |
Key insight: http::Extensions::insert implements the type map pattern where each type T can have exactly one associated value stored at a timeāthis eliminates the brittleness of string-keyed maps (no typos, no runtime casting failures) while providing O(1) lookup by type ID. The type serves as both key and value constraint: calling get::<User>() returns Option<&User> with compile-time type safety, and the Option pattern handles missing values gracefully without panicking. This design is particularly powerful in HTTP middleware architectures where multiple layers need to attach data to requests/responses without tight couplingāauthentication middleware inserts AuthenticatedUser, authorization inserts Permissions, timing middleware inserts RequestStartTime, and each downstream handler retrieves only what it needs. The pattern scales naturally: adding new metadata types requires defining a new struct and inserting/retrieving it, with no changes to existing code. The trade-off is that only one value per type can be stored; use newtypes to namespace similar types (e.g., DatabaseConfig and CacheConfig instead of two Config values).