错误的两种类别
Rust 的错误哲学
Rust 将错误明确分为两类,并提供不同的处理机制:
Result<T, E> 枚举表示。这种设计的哲学是:不要用异常机制(try/catch)混淆这两类错误。可恢复错误应该是函数签名的一部分,调用方必须显式处理;不可恢复错误则直接让程序崩溃,避免在损坏的状态下继续运行。
panic!:不可恢复错误
何时触发 panic
fn main() {
// 显式 panic
panic!("这里出了问题!");
// 这些操作在 debug 模式下会 panic:
let v = vec![1, 2, 3];
v[99]; // 越界访问 → panic!
// unwrap() 在 None 或 Err 时 panic
let x: Option<i32> = None;
x.unwrap(); // panic: called `Option::unwrap()` on a `None` value
// expect() 类似 unwrap(),但可以自定义错误信息
x.expect("x 应该有值"); // panic: x 应该有值
}
在生产代码中应尽量避免 unwrap(),因为一旦值是 None/Err 就会 panic 导致服务崩溃。合理的使用场景:
1. 原型代码和学习示例(快速迭代)
2. 测试代码中(panic 本来就代表测试失败)
3. 当你有 100% 把握某个值不会是 None/Err 时,用 expect("不可能是 None,因为...") 加上原因说明
Result<T, E>:可恢复错误
Result 的定义与使用
// Result 的定义(来自标准库):
enum Result<T, E> {
Ok(T), // 操作成功,携带结果值
Err(E), // 操作失败,携带错误信息
}
use std::fs;
use std::io;
fn read_username_from_file() -> Result<String, io::Error> {
let result = fs::read_to_string("username.txt");
match result {
Ok(content) => Ok(content.trim().to_string()),
Err(e) => Err(e),
}
}
fn main() {
match read_username_from_file() {
Ok(name) => println!("用户名: {}", name),
Err(e) => println!("读取失败: {}", e),
}
}
? 运算符:优雅的错误传播
? 运算符是 Rust 中处理 Result 的语法糖:如果值是 Ok(v),则 ? 提取 v;如果是 Err(e),则将 Err(e) 立即从当前函数返回(相当于提前 return Err(e))。
use std::fs;
use std::io;
// 用 ? 运算符简化错误传播
fn read_username() -> Result<String, io::Error> {
let content = fs::read_to_string("username.txt")?;
// ↑ 如果 Err,立即 return Err
Ok(content.trim().to_string())
}
// 链式 ? 调用
fn read_and_parse() -> Result<i32, Box<dyn std::error::Error>> {
let content = fs::read_to_string("number.txt")?; // io::Error
let number: i32 = content.trim().parse()?; // ParseIntError
Ok(number * 2)
}
fn main() {
// main 函数也可以返回 Result!
if let Ok(num) = read_and_parse() {
println!("数字的两倍: {}", num);
}
}
? 运算符还会自动调用 From::from() 将错误类型转换为函数返回类型中声明的错误类型(前提是实现了 From trait)。这使得在一个函数中处理多种不同的错误类型变得简单。
自定义错误类型
手动实现 Error trait
use std::fmt;
#[derive(Debug)]
enum AppError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
InvalidInput(String),
}
// 实现 Display trait(用于用户友好的错误信息)
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "I/O 错误: {}", e),
AppError::Parse(e) => write!(f, "解析错误: {}", e),
AppError::InvalidInput(s) => write!(f, "无效输入: {}", s),
}
}
}
// 实现 Error trait
impl std::error::Error for AppError {
fn source(&self) -> Option<&dyn std::error::Error> {
match self {
AppError::Io(e) => Some(e),
AppError::Parse(e) => Some(e),
_ => None,
}
}
}
// 实现 From trait,支持 ? 运算符自动转换
impl From<std::io::Error> for AppError {
fn from(e: std::io::Error) -> Self {
AppError::Io(e)
}
}
impl From<std::num::ParseIntError> for AppError {
fn from(e: std::num::ParseIntError) -> Self {
AppError::Parse(e)
}
}
thiserror:用宏简化自定义错误
手动实现 Error trait 很繁琐。thiserror crate 提供过程宏,让你用几行代码定义完整的错误类型:
[dependencies]
thiserror = "1"
use thiserror::Error;
#[derive(Error, Debug)]
enum AppError {
#[error("I/O 错误: {0}")]
Io(#[from] std::io::Error),
#[error("解析错误: {0}")]
Parse(#[from] std::num::ParseIntError),
#[error("无效输入: {message}")]
InvalidInput { message: String },
#[error("未找到配置项 '{key}'")]
ConfigNotFound { key: String },
}
// thiserror 自动生成 Display、Error、From 实现!
anyhow:应用层的便捷错误处理
anyhow 适用于应用程序(而非库),它提供了一个通用的 anyhow::Error 类型,可以容纳任何错误:
[dependencies]
anyhow = "1"
use anyhow::{Context, Result};
fn process_config(path: &str) -> Result<String> {
// context() 为错误添加上下文信息
let content = std::fs::read_to_string(path)
.with_context(|| format!("无法读取配置文件 {}", path))?;
let value: i32 = content.trim().parse()
.context("配置值必须是整数")?;
Ok(format!("配置值的两倍: {}", value * 2))
}
fn main() -> Result<()> {
let result = process_config("config.txt")?;
println!("{}", result);
Ok(())
}
库(library):使用自定义错误类型(配合 thiserror),让调用方能够精确地 match 错误类型并决定如何处理。不要在库中使用 anyhow::Error,它会擦除类型信息。
应用程序(application):使用 anyhow,追求简洁,专注于给用户好的错误信息和调试体验,而不是对每种错误做精细处理。
Result 的常用方法
fn main() {
let ok: Result<i32, &str> = Ok(42);
let err: Result<i32, &str> = Err("出错了");
// 提取值(失败时 panic)
ok.unwrap(); // 42
ok.expect("msg"); // 42
// 提供默认值
err.unwrap_or(0); // 0
err.unwrap_or_default(); // 0(i32 的默认值)
err.unwrap_or_else(|e| { println!("错误: {}", e); -1 });
// 变换值
ok.map(|v| v * 2); // Ok(84)
err.map_err(|e| e.len()); // Err(4)(变换错误类型)
// 检查状态
ok.is_ok(); // true
err.is_err(); // true
// and_then:链式处理(只有 Ok 时才执行)
ok.and_then(|v| if v > 10 { Ok(v) } else { Err("太小") });
}