01. panic! 和不可恢复错误
panic! 触发不可恢复错误,程序展开栈(回退)并崩溃。Rust 分为两种 panic 策略:unwind(展开栈,清理资源)和 abort(直接退出,不清理)。开发期常用 unwrap() 和 expect(),它们遇到 None 或 Err 就 panic。产品代码应尽量用 Result 传播错误而不是 unwrap。
rust
fn main() {
// 直接 panic
// panic!("致命的错误");
let v = vec![1, 2, 3];
// unwrap: 如果是 None/Err 就 panic
let first = v.get(0).unwrap();
// expect: 带自定义信息的 unwrap
let second = v.get(1).expect("索引 1 不存在");
// 更好的写法:用 match 或 ? 传播
match v.get(5) {
Some(val) => println!("{}", val),
None => println!("元素不存在"),
}
}库代码里永远不要 unwrap,让调用方决定怎么处理。应用代码的 unwrap 也要考虑能否换成更好的错误处理。
02. Result 和错误传播
Result<T, E> 是 Rust 标准错误处理方式,定义在标准库预引入里。Ok 代表成功携带值,Err 代表失败携带错误信息。? 操作符是 Rust 错误处理的核心:Ok 时提取值,Err 时提前返回错误。注意 ? 会自动调用 From trait 转换错误类型。
rust
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
// ? 链式调用
fn read_username_short() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
// 更短:标准库一行搞定
fn read_username_shortest() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}? 运算符可以在返回 Result 或 Option 的函数中使用,而且会自动调用 into() 转换错误类型。
03. 自定义错误类型
复杂项目需要自定义错误类型。用枚举包裹各种可能的错误,实现 Display 和 Debug trait。thiserror 库可以自动派生 Display 和 From 实现,减少模板代码。用 #[from] 属性让 ? 运算符自动转换子错误类型。
rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataError {
#[error("数据库错误: {0}")]
Database(#[from] sqlx::Error),
#[error("未找到数据: {0}")]
NotFound(String),
#[error("验证失败: {field} - {message}")]
Validation { field: String, message: String },
}
fn find_user(id: i32) -> Result<User, DataError> {
let user = db.query("...")? // sqlx::Error 自动转 DataError::Database
.ok_or(DataError::NotFound(format!("用户 {} 不存在", id)))?;
Ok(user)
}thiserror 让自定义错误几乎零代码,只需要定义枚举变体和错误信息模板。
04. anyhow 用于应用级错误处理
anyhow 提供了一个统一的错误类型 anyhow::Error,适合应用层(不是库),不需要定义复杂的错误枚举。Context trait 给 Result 加上下文信息,with_context 添加描述,链式附加上下文。bail! 宏相当于 return Err。适合快速开发和脚本。
rust
use anyhow::{Context, Result, bail};
fn read_config() -> Result<String> {
let path = "config.toml";
let content = std::fs::read_to_string(path)
.with_context(|| format!("无法读取配置文件 {}", path))?;
Ok(content)
}
fn validate_age(age: i32) -> Result<()> {
if age < 0 {
bail!("年龄不能为负数: {}", age);
}
if age > 150 {
bail!("年龄太大了: {}", age);
}
Ok(())
}
fn main() -> Result<()> {
let config = read_config()?;
println!("{}", config);
Ok(())
}库用 thiserror,应用用 anyhow,这是 Rust 社区的流行实践。
05. Option 和 Result 的便捷方法
Result 和 Option 提供了丰富的组合方法链式处理。ok_or 把 Option 转 Result,ok 反之。map/map_err 变换成功和失败值。and_then/or_else 链式组合。unwrap_or 提供默认值,unwrap_or_else 惰性计算默认值。这些方法让错误处理代码更函数式更简洁。
rust
fn main() {
let opt = Some(3);
let val = opt.unwrap_or(0); // 3
let val = None.unwrap_or_else(|| expensive_default()); // 惰性
let doubled = opt.map(|x| x * 2); // Some(6)
let filtered = opt.filter(|&x| x > 5); // None
let res: Result<i32, &str> = Ok(10);
let val = res.unwrap_or(0); // 10
let mapped = res.map(|x| x * 2); // Ok(20)
let err_mapped = res.map_err(|e| format!("!{}", e)); // Ok(10)
// 链式处理
let user_id = Some(42);
let username = user_id
.and_then(|id| find_user(id))
.map(|u| u.name)
.unwrap_or_else(|| "未知用户".to_string());
}unwrap_or_else 比 unwrap_or 好,因为默认值只在需要时才计算,不会做无用功。
知识测验
第 1/4 题正确 0
Rust 中 unwrap() 的作用?
下一节
下一节 Trait 与泛型