04
序列化、配置、日志、错误处理
serde, config-rs, tracing, clap
后端不只是写路由。配置怎么从文件+环境变量读?日志怎么结构化?命令行参数怎么解析?错误怎么分层?这一章把这些"胶水"讲清楚——它们决定了你的服务是能维护的产品,还是一堆脚本。
serde:Rust 的序列化标准
serde 是 Rust 生态最核心的 crate 之一——它定义了序列化/反序列化的抽象,具体格式由 serde_json、serde_yaml、serde_toml 提供。你只要给结构体 derive Serialize, Deserialize,剩下的事 serde 全包了。
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] // JSON 里用 camelCase,Rust 里用 snake_case
struct User {
id: u64,
username: String,
#[serde(default)] // 字段缺失时用 Default
email: String,
#[serde(skip_serializing_if = "Option::is_none")]
phone: Option<String>, // None 时不输出这个字段
}
let u = User { id: 1, username: "tom".into(), email: "".into(), phone: None };
let json = serde_json::to_string_pretty(&u)?;
println!("{json}");
{
"id": 1,
"username": "tom",
"email": ""
}
配置管理:config-rs + dotenvy
对标 Spring Boot 的 @ConfigurationProperties:config-rs 支持多层配置(默认值 + 配置文件 + 环境变量 + 命令行),dotenvy 负责读 .env 文件。
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
struct AppConfig {
database_url: String,
port: u16,
log_level: String,
}
let cfg = config::Config::builder()
.add_source(config::File::with_name("config/default")) // 默认文件
.add_source(config::Environment::default().separator("__")) // 环境变量 APP__PORT
.build()?;
let cfg: AppConfig = cfg.try_deserialize()?;
println!("监听端口 {}", cfg.port);
日志:tracing 是 Rust 后端的事实标准
别再用 println! 打日志了。tracing 是 Tokio 团队出的结构化日志库,支持 span(一段操作的上下文)、结构化字段、异步不阻塞。它和 tracing-subscriber 配合,就是 Rust 版的 Logback + MDC。
use tracing::{info, warn, error, instrument};
// 初始化:RUST_LOG=info,sqlx=warn 控制级别
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
// #[instrument] 自动把函数参数记入 span
#[instrument]
async fn login(user: &str) {
info!(user, "用户开始登录");
if user.is_empty() {
warn!("空用户名");
}
error!("登录失败示例");
}
INFO login{user=tom}: 用户开始登录
WARN login{user=tom}: 空用户名
错误分层:anyhow vs thiserror
| 库 | 用在哪 | 特点 |
|---|---|---|
anyhow | 应用层(main、handler) | 一个 anyhow::Error 装所有错误,灵活,带上下文 .context("...") |
thiserror | 库层(定义错误枚举) | derive Error,给你的库定义结构化错误类型,调用方能 match |
口诀:库作者用 thiserror 定义枚举,应用作者用 anyhow 随便装。Axum 的 AppError 用 thiserror,main 函数返回 anyhow::Result<()>。
命令行参数:clap
use clap::Parser;
#[derive(Parser, Debug)]
#[command(name = "myapp", version, about = "Rust 后端服务")]
struct Cli {
// --host 0.0.0.0
#[arg(long, default_value = "0.0.0.0")]
host: String,
// --port 8080
#[arg(long, default_value_t = 3000)]
port: u16,
// 子命令:migrate / serve
#[command(subcommand)]
cmd: Cmd,
}
#[derive(clap::Subcommand, Debug)]
enum Cmd {
Serve,
Migrate,
}
记
本章小结
① 序列化用 serde + derive,配置用 config-rs 多层叠加。
② 日志用 tracing,错误用 thiserror(库)+ anyhow(应用),CLI 用 clap derive。