Rust 错误处理
Rust 的错误处理系统通过类型系统在编译期强制处理错误,避免运行时未捕获异常,是 Rust 安全性的核心特性之一。
目录
错误处理基础
Rust vs 其他语言
| 语言 | 错误处理方式 | 问题 |
|---|---|---|
| Java/Python | try-catch 异常 | 可能忘记捕获 |
| Go | 错误返回值 | 可能忘记检查 |
| C | 错误码 | 容易被忽略 |
| Rust | Result/Option + 类型系统 | 编译期强制处理 ✅ |
Rust 错误分类
// 1. 可恢复错误(Recoverable Errors)
// 使用 Result<T, E>
fn read_file(path: &str) -> Result<String, std::io::Error> {
std::fs::read_to_string(path)
}
// 2. 不可恢复错误(Unrecoverable Errors)
// 使用 panic!
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("除数不能为零!");
}
a / b
}
Result 类型
1. Result 定义
// 标准库定义
enum Result<T, E> {
Ok(T), // 成功,包含值
Err(E), // 失败,包含错误
}
2. 基本使用
use std::fs::File;
use std::io::Read;
fn read_username_from_file() -> Result<String, std::io::Error> {
let mut file = File::open("username.txt")?;
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}
fn main() {
match read_username_from_file() {
Ok(username) => println!("用户名: {}", username),
Err(e) => println!("错误: {}", e),
}
}
3. Result 方法速查
fn example() {
let result: Result<i32, &str> = Ok(42);
// ===== 检查状态 =====
assert!(result.is_ok());
assert!(!result.is_err());
// ===== 获取值 =====
// unwrap - 成功返回值,失败 panic
let value = result.unwrap(); // 42
// unwrap_or - 失败返回默认值
let value = result.unwrap_or(0); // 42
// unwrap_or_else - 失败执行闭包
let value = result.unwrap_or_else(|e| {
println!("错误: {}", e);
0
});
// expect - 带自定义消息的 unwrap
let value = result.expect("应该有值");
// unwrap_or_default - 使用类型的默认值
let value = result.unwrap_or_default(); // 0 (i32::default())
// ===== 转换 =====
// map - 转换 Ok 值
let doubled = result.map(|x| x * 2); // Ok(84)
// map_err - 转换 Err 值
let mapped_err = result.map_err(|e| format!("错误: {}", e));
// and_then (flatMap) - 链式调用
let chained = result.and_then(|x| Ok(x * 2));
// or_else - 错误恢复
let recovered = result.or_else(|_| Ok(0));
// ===== 组合 =====
// and - 两个都成功才成功
let r1: Result<i32, &str> = Ok(1);
let r2: Result<i32, &str> = Ok(2);
let combined = r1.and(r2); // Ok(2)
// or - 有一个成功就成功
let r1: Result<i32, &str> = Err("错误1");
let r2: Result<i32, &str> = Ok(2);
let combined = r1.or(r2); // Ok(2)
}
4. 实用模式
模式匹配
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("除数不能为零"))
} else {
Ok(a / b)
}
}
fn main() {
match divide(10, 2) {
Ok(result) => println!("结果: {}", result),
Err(e) => println!("错误: {}", e),
}
}
if let 简化
fn main() {
let result = divide(10, 0);
// 只关心成功情况
if let Ok(value) = result {
println!("结果: {}", value);
}
// 只关心失败情况
if let Err(e) = result {
println!("错误: {}", e);
}
}
链式调用
use std::fs::File;
use std::io::{self, Read};
fn read_file() -> Result<String, io::Error> {
File::open("data.txt")
.and_then(|mut file| {
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
})
.map(|s| s.trim().to_string())
}
Option 类型
1. Option 定义
// 标准库定义
enum Option<T> {
Some(T), // 有值
None, // 无值
}
2. 基本使用
fn find_user(id: u64) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
fn main() {
match find_user(1) {
Some(name) => println!("找到用户: {}", name),
None => println!("用户不存在"),
}
}
3. Option 方法速查
fn example() {
let some_value: Option<i32> = Some(42);
let none_value: Option<i32> = None;
// ===== 检查状态 =====
assert!(some_value.is_some());
assert!(some_value.is_none() == false);
// ===== 获取值 =====
// unwrap - 有值返回,None panic
let value = some_value.unwrap(); // 42
// unwrap_or - None 返回默认值
let value = none_value.unwrap_or(0); // 0
// unwrap_or_else - None 执行闭包
let value = none_value.unwrap_or_else(|| {
println!("计算默认值");
0
});
// expect - 带自定义消息
let value = some_value.expect("应该有值");
// ===== 转换 =====
// map - 转换 Some 值
let doubled = some_value.map(|x| x * 2); // Some(84)
// map_or - 提供默认值的 map
let doubled = none_value.map_or(0, |x| x * 2); // 0
// and_then - 链式调用
let chained = some_value.and_then(|x| Some(x * 2));
// filter - 过滤
let filtered = some_value.filter(|x| *x > 40); // Some(42)
// ===== 组合 =====
// and - 两个都有值才返回第二个
let a = Some(1);
let b = Some(2);
assert_eq!(a.and(b), Some(2));
// or - 有一个有值就返回
let a: Option<i32> = None;
let b = Some(2);
assert_eq!(a.or(b), Some(2));
// ===== 转换为 Result =====
let result: Result<i32, &str> = some_value.ok_or("没有值");
assert_eq!(result, Ok(42));
}
4. 实用模式
解构
fn main() {
let maybe_name = Some(String::from("Alice"));
// if let
if let Some(name) = maybe_name {
println!("你好, {}", name);
}
// while let
let mut stack = vec![Some(1), Some(2), Some(3), None];
while let Some(Some(value)) = stack.pop() {
println!("{}", value);
}
}
链式调用
fn get_user_email(user_id: u64) -> Option<String> {
find_user(user_id)
.and_then(|user| user.email)
.map(|email| email.to_lowercase())
.filter(|email| email.contains("@"))
}
? 操作符
1. 基本用法
use std::fs::File;
use std::io::{self, Read};
// 不使用 ? 操作符
fn read_file_old() -> Result<String, io::Error> {
let mut file = match File::open("data.txt") {
Ok(f) => f,
Err(e) => return Err(e),
};
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => Ok(contents),
Err(e) => Err(e),
}
}
// 使用 ? 操作符(简洁!)
fn read_file_new() -> Result<String, io::Error> {
let mut file = File::open("data.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
// 更简洁的版本
fn read_file_shortest() -> Result<String, io::Error> {
std::fs::read_to_string("data.txt")
}
2. ? 操作符工作原理
// 这段代码:
let result = some_function()?;
// 等价于:
let result = match some_function() {
Ok(value) => value,
Err(e) => return Err(e.into()), // 注意 .into() 自动转换
};
3. 在 Option 中使用 ?
fn add_last_two(numbers: &[i32]) -> Option<i32> {
let last = numbers.last()?; // None 则提前返回
let second_last = numbers.get(numbers.len() - 2)?;
Some(last + second_last)
}
fn main() {
assert_eq!(add_last_two(&[1, 2, 3]), Some(5));
assert_eq!(add_last_two(&[1]), None);
assert_eq!(add_last_two(&[]), None);
}
4. main 函数中使用 ?
use std::error::Error;
use std::fs::File;
// main 返回 Result
fn main() -> Result<(), Box<dyn Error>> {
let file = File::open("data.txt")?;
println!("文件打开成功");
Ok(())
}
// 或者简单的错误类型
fn main() -> Result<(), String> {
let data = read_data().map_err(|e| e.to_string())?;
process(data)?;
Ok(())
}
自定义错误类型
1. 简单枚举错误
#[derive(Debug)]
enum MathError {
DivisionByZero,
NegativeSquareRoot,
Overflow,
}
fn divide(a: i32, b: i32) -> Result<i32, MathError> {
if b == 0 {
Err(MathError::DivisionByZero)
} else {
Ok(a / b)
}
}
fn sqrt(n: i32) -> Result<f64, MathError> {
if n < 0 {
Err(MathError::NegativeSquareRoot)
} else {
Ok((n as f64).sqrt())
}
}
fn main() {
match divide(10, 0) {
Ok(result) => println!("结果: {}", result),
Err(MathError::DivisionByZero) => println!("错误: 除数不能为零"),
Err(e) => println!("其他错误: {:?}", e),
}
}
2. 带数据的错误
#[derive(Debug)]
enum ParseError {
InvalidFormat(String),
OutOfRange { value: i32, min: i32, max: i32 },
MissingField(&'static str),
}
fn parse_age(s: &str) -> Result<u32, ParseError> {
let age = s.parse::<i32>()
.map_err(|_| ParseError::InvalidFormat(s.to_string()))?;
if age < 0 || age > 150 {
return Err(ParseError::OutOfRange {
value: age,
min: 0,
max: 150,
});
}
Ok(age as u32)
}
fn main() {
match parse_age("200") {
Ok(age) => println!("年龄: {}", age),
Err(ParseError::OutOfRange { value, min, max }) => {
println!("年龄 {} 超出范围 {}-{}", value, min, max);
}
Err(e) => println!("错误: {:?}", e),
}
}
3. 实现 Error trait
use std::fmt;
use std::error::Error;
#[derive(Debug)]
struct MyError {
message: String,
code: i32,
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "错误 [{}]: {}", self.code, self.message)
}
}
impl Error for MyError {}
impl MyError {
fn new(message: &str, code: i32) -> Self {
MyError {
message: message.to_string(),
code,
}
}
}
fn do_something() -> Result<(), MyError> {
Err(MyError::new("操作失败", 500))
}
fn main() {
match do_something() {
Ok(_) => println!("成功"),
Err(e) => {
println!("发生错误: {}", e);
println!("错误码: {}", e.code);
}
}
}
4. 使用 thiserror(推荐)
use thiserror::Error;
#[derive(Error, Debug)]
enum DataStoreError {
#[error("数据未找到: {0}")]
NotFound(String),
#[error("数据库连接失败")]
ConnectionError(#[from] sqlx::Error),
#[error("序列化失败")]
SerializationError(#[from] serde_json::Error),
#[error("无效的输入: {field} = {value}")]
InvalidInput {
field: String,
value: String,
},
#[error("未知错误")]
Unknown,
}
fn find_user(id: i64) -> Result<User, DataStoreError> {
if id <= 0 {
return Err(DataStoreError::InvalidInput {
field: "id".to_string(),
value: id.to_string(),
});
}
// 自动转换错误
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = ?", id)
.fetch_one(pool)
.await?; // sqlx::Error 自动转换为 DataStoreError
Ok(user)
}
错误传播
1. 手动传播
fn read_config() -> Result<Config, std::io::Error> {
let content = read_file("config.toml")?;
let config = parse_config(&content)?;
Ok(config)
}
fn process_request() -> Result<Response, Box<dyn std::error::Error>> {
let config = read_config()?; // io::Error 自动转换
let data = fetch_data(&config)?; // 其他错误也能传播
Ok(Response::new(data))
}
2. 错误链
use std::error::Error;
use std::fmt;
#[derive(Debug)]
struct HighLevelError {
message: String,
source: Option<Box<dyn Error>>,
}
impl fmt::Display for HighLevelError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl Error for HighLevelError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source.as_ref().map(|e| e.as_ref())
}
}
fn low_level() -> Result<(), std::io::Error> {
std::fs::read_to_string("missing.txt")?;
Ok(())
}
fn high_level() -> Result<(), HighLevelError> {
low_level().map_err(|e| HighLevelError {
message: "高层操作失败".to_string(),
source: Some(Box::new(e)),
})?;
Ok(())
}
fn main() {
if let Err(e) = high_level() {
println!("错误: {}", e);
let mut current_error: &dyn Error = &e;
while let Some(source) = current_error.source() {
println!(" 原因: {}", source);
current_error = source;
}
}
}
错误转换
1. From trait 自动转换
use std::io;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(ParseIntError),
}
// 实现 From trait
impl From<io::Error> for AppError {
fn from(error: io::Error) -> Self {
AppError::Io(error)
}
}
impl From<ParseIntError> for AppError {
fn from(error: ParseIntError) -> Self {
AppError::Parse(error)
}
}
fn read_number() -> Result<i32, AppError> {
let content = std::fs::read_to_string("number.txt")?; // 自动转换
let number = content.trim().parse()?; // 自动转换
Ok(number)
}
2. map_err 手动转换
fn process() -> Result<String, String> {
let content = std::fs::read_to_string("data.txt")
.map_err(|e| format!("读取文件失败: {}", e))?;
let parsed: i32 = content.trim().parse()
.map_err(|e| format!("解析失败: {}", e))?;
Ok(format!("值: {}", parsed))
}
3. anyhow 简化错误处理
use anyhow::{Result, Context};
fn read_config() -> Result<Config> {
let content = std::fs::read_to_string("config.toml")
.context("读取配置文件失败")?;
let config: Config = toml::from_str(&content)
.context("解析配置失败")?;
Ok(config)
}
fn main() -> Result<()> {
let config = read_config()
.context("初始化配置时出错")?;
println!("配置加载成功");
Ok(())
}
常用错误处理库
1. thiserror - 定义错误类型
use thiserror::Error;
#[derive(Error, Debug)]
pub enum UserServiceError {
#[error("用户不存在: {0}")]
UserNotFound(i64),
#[error("用户名已存在: {0}")]
UsernameExists(String),
#[error("数据库错误")]
DatabaseError(#[from] sqlx::Error),
#[error("缓存错误")]
CacheError(#[from] redis::RedisError),
#[error("无效的邮箱格式: {0}")]
InvalidEmail(String),
}
2. anyhow - 应用层错误处理
use anyhow::{Result, Context, bail, ensure};
fn process_user(id: i64) -> Result<()> {
// ensure! 类似 assert
ensure!(id > 0, "ID 必须为正数");
let user = find_user(id)
.context(format!("查找用户 {} 失败", id))?;
// bail! 提前返回错误
if !user.is_active {
bail!("用户 {} 未激活", id);
}
Ok(())
}
3. eyre - anyhow 的增强版
use eyre::{Result, WrapErr};
fn load_config() -> Result<Config> {
let path = "config.toml";
let content = std::fs::read_to_string(path)
.wrap_err_with(|| format!("读取 {} 失败", path))?;
toml::from_str(&content)
.wrap_err("解析配置失败")
}
实战示例
示例 1:文件操作
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::Path;
fn read_file(path: &str) -> io::Result<String> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn write_file(path: &str, content: &str) -> io::Result<()> {
let mut file = File::create(path)?;
file.write_all(content.as_bytes())?;
Ok(())
}
fn copy_file(from: &str, to: &str) -> io::Result<()> {
let content = read_file(from)?;
write_file(to, &content)?;
Ok(())
}
fn main() -> io::Result<()> {
match copy_file("input.txt", "output.txt") {
Ok(_) => println!("文件复制成功"),
Err(e) => eprintln!("错误: {}", e),
}
Ok(())
}
示例 2:HTTP 请求
use reqwest;
use serde::Deserialize;
use thiserror::Error;
#[derive(Error, Debug)]
enum ApiError {
#[error("网络请求失败")]
RequestFailed(#[from] reqwest::Error),
#[error("JSON 解析失败")]
ParseError(#[from] serde_json::Error),
#[error("API 返回错误: {0}")]
ApiError(String),
}
#[derive(Deserialize)]
struct User {
id: i64,
name: String,
}
async fn fetch_user(id: i64) -> Result<User, ApiError> {
let url = format!("https://api.example.com/users/{}", id);
let response = reqwest::get(&url).await?;
if !response.status().is_success() {
return Err(ApiError::ApiError(
format!("HTTP {}", response.status())
));
}
let user = response.json::<User>().await?;
Ok(user)
}
#[tokio::main]
async fn main() -> Result<(), ApiError> {
let user = fetch_user(1).await?;
println!("用户: {}", user.name);
Ok(())
}
示例 3:数据库操作
use sqlx::{MySqlPool, FromRow};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DatabaseError {
#[error("用户不存在: {0}")]
UserNotFound(i64),
#[error("SQL 错误")]
SqlxError(#[from] sqlx::Error),
}
#[derive(FromRow)]
struct User {
id: i64,
username: String,
email: String,
}
async fn get_user(
pool: &MySqlPool,
id: i64,
) -> Result<User, DatabaseError> {
let user = sqlx::query_as!(
User,
"SELECT id, username, email FROM users WHERE id = ?",
id
)
.fetch_optional(pool)
.await?
.ok_or(DatabaseError::UserNotFound(id))?;
Ok(user)
}
async fn create_user(
pool: &MySqlPool,
username: &str,
email: &str,
) -> Result<i64, DatabaseError> {
let result = sqlx::query!(
"INSERT INTO users (username, email) VALUES (?, ?)",
username,
email
)
.execute(pool)
.await?;
Ok(result.0 as i64)
}
示例 4:本项目用户服务
// src/services/user_service.rs
use sqlx::MySqlPool;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum UserServiceError {
#[error("用户不存在: {0}")]
UserNotFound(i64),
#[error("用户名已存在: {0}")]
UsernameExists(String),
#[error("邮箱已存在: {0}")]
EmailExists(String),
#[error("数据库错误: {0}")]
DatabaseError(#[from] sqlx::Error),
#[error("缓存错误: {0}")]
CacheError(#[from] redis::RedisError),
#[error("密码哈希失败")]
HashError,
}
pub struct UserService {
pool: MySqlPool,
cache: RedisCache,
}
impl UserService {
pub fn new(pool: MySqlPool, cache: RedisCache) -> Self {
Self { pool, cache }
}
pub async fn create_user(
&self,
username: String,
email: String,
password: String,
) -> Result<User, UserServiceError> {
// 检查用户名是否存在
if self.username_exists(&username).await? {
return Err(UserServiceError::UsernameExists(username));
}
// 检查邮箱是否存在
if self.email_exists(&email).await? {
return Err(UserServiceError::EmailExists(email));
}
// 哈希密码
let password_hash = hash_password(&password)
.map_err(|_| UserServiceError::HashError)?;
// 插入数据库
let result = sqlx::query!(
r#"
INSERT INTO users (username, email, password_hash)
VALUES (?, ?, ?)
"#,
username,
email,
password_hash
)
.execute(&self.pool)
.await?;
let user_id = result.0 as i64;
// 获取完整用户信息
self.get_user(user_id).await?
.ok_or(UserServiceError::UserNotFound(user_id))
}
pub async fn get_user(
&self,
user_id: i64,
) -> Result<Option<User>, UserServiceError> {
let cache_key = format!("user:{}", user_id);
// 先查缓存
if let Some(cached) = self.cache.get(&cache_key).await? {
if let Ok(user) = serde_json::from_str::<User>(&cached) {
return Ok(Some(user));
}
}
// 查数据库
let user = sqlx::query_as!(
User,
r#"
SELECT id, username, email, password_hash, created_at, updated_at
FROM users
WHERE id = ?
"#,
user_id
)
.fetch_optional(&self.pool)
.await?;
// 写入缓存
if let Some(ref u) = user {
if let Ok(json) = serde_json::to_string(u) {
let _ = self.cache.set(&cache_key, &json, 3600).await;
}
}
Ok(user)
}
pub async fn delete_user(
&self,
user_id: i64,
) -> Result<bool, UserServiceError> {
let result = sqlx::query!(
"DELETE FROM users WHERE id = ?",
user_id
)
.execute(&self.pool)
.await?;
// 删除缓存
let cache_key = format!("user:{}", user_id);
let _ = self.cache.delete(&cache_key).await;
Ok(result.rows_affected() > 0)
}
async fn username_exists(&self, username: &str) -> Result<bool, sqlx::Error> {
let count: (i64,) = sqlx::query_as(
"SELECT COUNT(*) FROM users WHERE username = ?"
)
.bind(username)
.fetch_one(&self.pool)
.await?;
Ok(count.0 > 0)
}
async fn email_exists(&self, email: &str) -> Result<bool, sqlx::Error> {
let count: (i64,) = sqlx::query_as(
"SELECT COUNT(*) FROM users WHERE email = ?"
)
.bind(email)
.fetch_one(&self.pool)
.await?;
Ok(count.0 > 0)
}
}
示例 5:HTTP 控制器错误处理
// src/controllers/user_controller.rs
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
// 为服务错误实现 IntoResponse
impl IntoResponse for UserServiceError {
fn into_response(self) -> Response {
let (status, message) = match self {
UserServiceError::UserNotFound(id) => (
StatusCode::NOT_FOUND,
format!("用户 {} 不存在", id)
),
UserServiceError::UsernameExists(username) => (
StatusCode::CONFLICT,
format!("用户名 {} 已存在", username)
),
UserServiceError::EmailExists(email) => (
StatusCode::CONFLICT,
format!("邮箱 {} 已存在", email)
),
UserServiceError::HashError => (
StatusCode::INTERNAL_SERVER_ERROR,
"密码处理失败".to_string()
),
UserServiceError::DatabaseError(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
"数据库错误".to_string()
),
UserServiceError::CacheError(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
"缓存错误".to_string()
),
};
let body = Json(json!({
"error": message,
}));
(status, body).into_response()
}
}
pub async fn create_user(
State(service): State<Arc<UserService>>,
Json(payload): Json<CreateUserRequest>,
) -> Result<Json<User>, UserServiceError> {
let user = service.create_user(
payload.username,
payload.email,
payload.password,
).await?;
Ok(Json(user))
}
pub async fn get_user(
State(service): State<Arc<UserService>>,
Path(user_id): Path<i64>,
) -> Result<Json<User>, UserServiceError> {
let user = service.get_user(user_id).await?
.ok_or(UserServiceError::UserNotFound(user_id))?;
Ok(Json(user))
}
示例 6:组合多种错误
use thiserror::Error;
#[derive(Error, Debug)]
enum AppError {
#[error("IO 错误: {0}")]
Io(#[from] std::io::Error),
#[error("解析错误: {0}")]
Parse(#[from] std::num::ParseIntError),
#[error("HTTP 错误: {0}")]
Http(#[from] reqwest::Error),
#[error("数据库错误: {0}")]
Database(#[from] sqlx::Error),
#[error("业务逻辑错误: {0}")]
Business(String),
}
async fn complex_operation() -> Result<String, AppError> {
// IO 操作
let config = std::fs::read_to_string("config.txt")?;
// 解析操作
let port: u16 = config.trim().parse()?;
// HTTP 请求
let response = reqwest::get("https://api.example.com/data").await?;
let data = response.text().await?;
// 数据库操作
let db = get_db_pool().await;
sqlx::query("INSERT INTO logs (data) VALUES (?)")
.bind(&data)
.execute(&db)
.await?;
// 业务逻辑
if data.is_empty() {
return Err(AppError::Business("数据为空".to_string()));
}
Ok(data)
}
最佳实践
1. 错误类型选择
// ✅ 库代码:使用具体错误类型
pub enum MyLibError {
InvalidInput,
ConnectionFailed,
}
pub fn lib_function() -> Result<T, MyLibError> { }
// ✅ 应用代码:使用 anyhow
use anyhow::Result;
fn app_function() -> Result<T> { }
// ❌ 避免:过度使用 Box<dyn Error>
fn bad() -> Result<T, Box<dyn std::error::Error>> { }
2. 错误消息
// ✅ 好的错误消息
#[error("无法连接数据库 {host}:{port}")]
ConnectionFailed { host: String, port: u16 }
#[error("用户 {username} (ID: {id}) 不存在")]
UserNotFound { id: i64, username: String }
// ❌ 不好的错误消息
#[error("错误")]
Error
#[error("失败")]
Failed
3. 何时使用 unwrap/expect
// ✅ 可以使用的场景
// 1. 测试代码
#[test]
fn test_something() {
let value = some_function().unwrap();
assert_eq!(value, 42);
}
// 2. 确定不会失败
let number: i32 = "42".parse().expect("硬编码的数字");
// 3. 原型开发(但要留 TODO)
let config = load_config().expect("TODO: 添加错误处理");
// ❌ 避免在生产代码中使用
fn production_code() -> Result<()> {
let file = File::open("data.txt").unwrap(); // ❌ 不要这样
let file = File::open("data.txt")?; // ✅ 这样做
Ok(())
}
4. 错误传播策略
// ✅ 使用 ? 操作符
fn good() -> Result<String, io::Error> {
let content = std::fs::read_to_string("file.txt")?;
Ok(content.to_uppercase())
}
// ❌ 手动 match(冗长)
fn bad() -> Result<String, io::Error> {
match std::fs::read_to_string("file.txt") {
Ok(content) => Ok(content.to_uppercase()),
Err(e) => Err(e),
}
}
5. 添加上下文
use anyhow::{Context, Result};
fn process_config() -> Result<Config> {
let content = std::fs::read_to_string("config.toml")
.context("无法读取配置文件 config.toml")?;
let config: Config = toml::from_str(&content)
.context("配置文件格式错误")?;
validate_config(&config)
.context("配置验证失败")?;
Ok(config)
}
6. 日志记录
use tracing::{error, warn, info};
async fn handle_request(id: i64) -> Result<Response, AppError> {
match process_request(id).await {
Ok(response) => {
info!(request_id = id, "请求处理成功");
Ok(response)
}
Err(e) => {
error!(
request_id = id,
error = %e,
"请求处理失败"
);
Err(e)
}
}
}
7. 优雅降级
async fn get_user_with_fallback(id: i64) -> User {
match fetch_from_cache(id).await {
Ok(user) => user,
Err(e) => {
warn!("缓存获取失败: {}, 降级到数据库", e);
match fetch_from_db(id).await {
Ok(user) => user,
Err(e) => {
error!("数据库获取也失败: {}, 返回默认用户", e);
User::default()
}
}
}
}
}
8. 重试逻辑
use tokio::time::{sleep, Duration};
async fn retry_operation<F, T, E>(
mut operation: F,
max_retries: u32,
) -> Result<T, E>
where
F: FnMut() -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T, E>>>>,
{
let mut attempts = 0;
loop {
match operation().await {
Ok(result) => return Ok(result),
Err(e) => {
attempts += 1;
if attempts >= max_retries {
return Err(e);
}
let delay = Duration::from_secs(2u64.pow(attempts));
warn!("操作失败,{}秒后重试 ({}/{})", delay.as_secs(), attempts, max_retries);
sleep(delay).await;
}
}
}
}
总结
错误处理速查表
| 场景 | 使用 | 示例 |
|---|---|---|
| 可能失败的操作 | Result<T, E> | File::open() |
| 可能没有值 | Option<T> | vec.get(index) |
| 传播错误 | ? 操作符 | let x = f()?; |
| 库开发 | thiserror | 自定义错误类型 |
| 应用开发 | anyhow | 简化错误处理 |
| 不可恢复 | panic! | 断言失败 |
Result vs Option
// Result - 需要知道失败原因
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("除数不能为零".to_string())
} else {
Ok(a / b)
}
}
// Option - 只需要知道有没有值
fn find_user(id: i64) -> Option<User> {
// ...
}
关键原则
- 优先使用 Result/Option,避免 panic
- 使用 ? 简化错误传播
- 为库定义具体错误,为应用使用 anyhow
- 添加错误上下文,便于调试
- 记录错误日志,便于排查
- 生产代码避免 unwrap,除非确定安全
本项目错误处理模式
// 1. 定义服务错误
#[derive(Error, Debug)]
pub enum UserServiceError {
#[error("用户不存在: {0}")]
UserNotFound(i64),
#[error("数据库错误")]
DatabaseError(#[from] sqlx::Error),
}
// 2. 服务层返回 Result
impl UserService {
pub async fn get_user(&self, id: i64) -> Result<User, UserServiceError> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = ?", id)
.fetch_optional(&self.pool)
.await?
.ok_or(UserServiceError::UserNotFound(id))?;
Ok(user)
}
}
// 3. 控制器处理错误并转换为 HTTP 响应
impl IntoResponse for UserServiceError {
fn into_response(self) -> Response {
match self {
UserServiceError::UserNotFound(_) => {
(StatusCode::NOT_FOUND, self.to_string()).into_response()
}
UserServiceError::DatabaseError(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, "服务器错误").into_response()
}
}
}
}
延伸阅读
- The Rust Book - Error Handling
- thiserror Documentation
- anyhow Documentation
- Rust Error Handling Survey
- 本项目文档:
CLAUDE.md
记住:Rust 的错误处理看起来繁琐,但它强制你在编译期处理所有错误情况,避免运行时崩溃!