Axum框架共享状态因生命周期问题失效的技术咨询
解决Axum App State中的生命周期与所有权问题
错误原因分析
你遇到的两个错误本质是同一个问题:
- Axum的
.with_state()要求传入的状态必须满足'static生命周期(服务器会持续运行,状态需要存活到服务器停止),但你用&mut String作为引用,它的生命周期仅局限于main函数中name变量的存活期,服务器启动后name会被销毁,导致引用悬空。 - 另外,
Arc用于共享所有权,但你在其中存放了持有可变引用的结构体——可变引用本身只能有一个所有者,这和Arc的共享语义完全矛盾,根本无法在多线程的Axum服务器中安全使用。
解决方案
要实现可复用的App State,核心是让状态满足Send + Sync + 'static,同时安全共享可变数据(如果需要的话)。针对你的测试场景和实际数据库连接需求,分两种情况处理:
1. 测试场景:共享可变字符串
把可变字符串放到线程安全的容器中,用Arc<Mutex<T>>实现多线程下的安全共享与修改:
#![allow(incomplete_features)] #![feature(async_fn_in_trait)] use axum::{routing::post, Router}; use std::{net::SocketAddr, sync::{Arc, Mutex}}; mod modules; use modules::auth::handlers::login; // 移除不必要的生命周期,让AppState满足'static要求 pub struct AppState { user_credentials_repository: UserCredentialsRepository, } pub struct UserCredentialsRepository { // 用Arc<Mutex>包裹可变字符串,实现线程安全的共享 shared_name: Arc<Mutex<String>>, } impl UserCredentialsRepository { pub fn new(shared_name: Arc<Mutex<String>>) -> Self { Self { shared_name } } // 示例:修改字符串的方法 pub async fn update_name(&self, new_name: String) { let mut name = self.shared_name.lock().unwrap(); *name = new_name; } } #[tokio::main] async fn main() { let name = Arc::new(Mutex::new(String::from("Tom"))); let user_credentials_repository = UserCredentialsRepository::new(name.clone()); let shared_state = Arc::new(AppState { user_credentials_repository, }); let app = Router::new() .route("/login", post(login)) .with_state(shared_state); let addr = SocketAddr::from(([127, 0, 0, 1], 7777)); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); }
2. 实际需求:复用数据库连接/会话
大部分数据库客户端(比如sqlx、Diesel的连接池)本身就是线程安全的,不需要额外的Mutex,直接用Arc包裹放到AppState即可。以sqlx的PostgreSQL连接池为例:
use axum::{routing::post, Router}; use sqlx::PgPool; use std::{net::SocketAddr, sync::Arc}; pub struct AppState { db_pool: Arc<PgPool>, } #[tokio::main] async fn main() { // 初始化数据库连接池 let db_pool = PgPool::connect("postgres://user:pass@localhost/db").await.unwrap(); let shared_state = Arc::new(AppState { db_pool: Arc::new(db_pool), }); let app = Router::new() .route("/login", post(login)) .with_state(shared_state); let addr = SocketAddr::from(([127, 0, 0, 1], 7777)); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); }
处理器中使用状态的示例:
pub async fn login(State(state): State<Arc<AppState>>) -> impl IntoResponse { // 从连接池获取连接 let conn = state.db_pool.acquire().await.unwrap(); // 执行数据库操作... "Login success" }
关键要点
- Axum的State必须满足
Send + Sync + 'static,所以不要在状态中持有短生命周期的引用,尽量持有所有权或用Arc包裹线程安全的类型。 - 如果需要共享可变数据,用
Arc<Mutex<T>>(独占访问)或Arc<RwLock<T>>(读写分离),确保多线程下的安全。 - 数据库连接池本身已经是线程安全的,直接用
Arc包裹即可,不需要额外的同步容器。
内容的提问来源于stack exchange,提问作者Gilbert Nwaiwu
相关产品推荐
相关产品推荐

