Rust 异步编程入门:用 Tokio + Axum 写一个 HTTP 服务
前两篇聊了 Rust 为什么值得学、怎么用 Rust 做小工具和桌面应用。这篇把镜头拉到后端:用 Rust 写一个真正的 HTTP 服务。
Rust 做后端最大的吸引力不是“能跑”,而是稳定、可控、资源占用低。一个 Rust 服务启动后内存可能只有十几兆,没有 GC 停顿,错误在编译期就被拦下来,部署也只需要一个二进制文件。
异步 Rust 是什么?
简单说,异步就是“一个线程可以同时等待很多任务”。
比如服务器同时收到 100 个请求,同步写法可能需要 100 个线程;异步写法则可以让一个线程在等待数据库、网络、文件 IO 时,先去处理其他请求。
Rust 标准库本身不提供异步运行时,所以实际开发中通常会用 Tokio。Tokio 是 Rust 生态里最成熟的异步运行时,负责调度任务、处理 IO、管理线程池。
而 Axum 是建立在 Tokio 生态上的 Web 框架,类型设计非常 Rust:路由、中间件、错误处理都尽量利用类型系统,写起来简洁,出错时编译器也会给出明确提示。
为什么推荐 Tokio + Axum?
Tokio 和 Axum 的组合有几个明显优势:
- 生态成熟:Tokio 被大量生产级项目使用,文档和社区都很完善。
- 类型友好:Axum 的路由和处理器天然适配 Rust 的类型系统,参数提取、状态共享、错误返回都很直观。
- 性能稳定:没有 GC,请求延迟更可预测,适合高并发场景。
- 部署简单:编译成单个二进制,扔到服务器上就能跑。
它当然也有学习成本。你需要理解 async/await、Future、Send + Sync、生命周期和所有权在异步环境里的表现。但这些概念一旦打通,写出来的后端代码会非常扎实。
第一个 Axum 服务
先初始化项目:
cargo new hello_axum
cd hello_axum
cargo add axum tokio --features tokio/full
然后写一个最简单的服务:
use axum::{routing::get, Router};
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "Hello, Rust!" }));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("Listening on http://0.0.0.0:3000");
axum::serve(listener, app).await.unwrap();
}
运行后访问 http://localhost:3000,就能看到返回内容。这个例子很小,但已经包含了 Rust 异步服务的核心结构:Router + Handler + Listener。
路由和参数提取
Axum 的参数提取非常舒服。比如要写一个根据 ID 查询用户的接口:
use axum::{extract::Path, routing::get, Json, Router};
use serde::Serialize;
#[derive(Serialize)]
struct User {
id: u64,
name: String,
}
async fn get_user(Path(id): Path<u64>) -> Json<User> {
Json(User {
id,
name: format!("User {id}"),
})
}
let app = Router::new().route("/users/:id", get(get_user));
这里没有手动解析 URL,也没有到处做字符串转换。Axum 会根据类型自动提取参数,如果类型不匹配,请求会直接返回 404 或 400。
共享状态
真实服务通常需要共享数据库连接、配置或缓存。Axum 提供了 State 机制:
use axum::{extract::State, routing::get, Json, Router};
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
app_name: String,
}
async fn info(State(state): State<Arc<AppState>>) -> Json<AppState> {
Json(state.as_ref().clone())
}
let state = Arc::new(AppState {
app_name: "hello_axum".into(),
});
let app = Router::new()
.route("/info", get(info))
.with_state(state);
Arc 保证状态可以在线程间共享,Clone 让每个路由处理器都能方便地拿到副本。这个模式在 Axum 项目里非常常见。
错误处理要提前想清楚
Rust 后端开发里,错误处理几乎一定会遇到。Axum 本身提供了 IntoResponse,你可以把自定义错误类型转换成 HTTP 响应。
一个比较实用的做法是定义自己的 AppError:
use axum::{http::StatusCode, response::IntoResponse};
enum AppError {
NotFound,
Internal,
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
match self {
AppError::NotFound => (StatusCode::NOT_FOUND, "not found").into_response(),
AppError::Internal => (StatusCode::INTERNAL_SERVER_ERROR, "internal error").into_response(),
}
}
}
这样业务逻辑里就可以返回 Result<T, AppError>,而不是到处 unwrap()。
从练习到真实项目
一个完整的 Rust 后端项目通常还会涉及:
- 数据库:SQLx、SeaORM、Diesel
- 配置管理:
dotenvy、config - 日志与监控:
tracing、tracing-subscriber - 认证授权:JWT、Session、OAuth
- API 文档:utoipa、OpenAPI
- 测试:
axum::TestClient、集成测试 - 部署:Docker、静态编译、CI/CD
Tokio 和 Axum 的生态已经足够支撑这些需求,很多团队也确实把它们用于生产环境。
异步 Rust 的学习建议
不要一开始就追求“高性能压测”。更稳的路径是:
- 先跑通一个返回 JSON 的 HTTP 服务
- 加上路径参数和查询参数
- 引入
State共享配置 - 定义自己的错误类型
- 接入数据库或本地文件存储
- 加上日志和简单测试
- 最后再考虑连接池、缓存、并发优化
Rust 异步编程确实比同步代码复杂一点,但它的回报也很清晰:更少运行时意外,更可控的资源使用,更明确的错误边界。
如果你已经写过一些 Rust 小工具,或者对后端、云原生、高性能服务感兴趣,Tokio + Axum 会是一个非常好的下一步。
- 点赞
- 收藏
- 关注作者
评论(0)