10. 异常处理
10.1 简介
在程序运行过程中,可能会遇到各种异常情况,例如非法数据、文件不存在、网络连接失败、权限不足等问题。为了保证程序的稳定运行,需要主动检测这些异常情况,并采取相应的处理措施。不同于许多传统编程语言通过异常(Exception)机制隐式抛出和捕获错误,Rust 将错误处理视为程序控制流程的一部分,通过类型系统显式表示可能失败的操作,并根据错误是否具有恢复可能,将错误分为可恢复错误和不可恢复错误两类。其中,可恢复错误通常通过 Option<T> 和 Result<T, E> 类型进行表示和处理;对于无法继续执行或不应被恢复的错误,Rust 提供了 panic! 宏,用于终止当前线程的执行。
10.2 Option
Option 用于表示值可能不存在的场景,例如查找元素失败、获取数据时键不存在等。其本质是一个枚举,包含两种状态:Some(T) 表示值存在,并携带类型为 T 的数据;None 表示值不存在,不携带任何数据。调用者可以根据返回值判断值是否存在,也可以将 Option 继续向上层传递,由调用链的上游决定如何处理。
fn find_index(numbers: &[i32], target: i32) -> Option<usize> {
for (index, &number) in numbers.iter().enumerate() {
if number == target {
return Some(index);
}
}
return None;
}
fn main() {
let numbers = [10, 20, 30, 40];
// 根据返回结果判断值是否存在,不存在则提示用户
match find_index(&numbers, 30) {
Some(index) => println!("找到元素,索引为 {}", index),
None => println!("未找到元素"),
}
match find_index(&numbers, 50) {
Some(index) => println!("找到元素,索引为 {}", index),
None => println!("未找到元素"),
}
}
shell> cargo run
找到元素,索引为 2
未找到元素
除了显式返回 None 外,还可以通过 ? 运算符自动传播 Option。当表达式结果为 Some 时,? 会提取其中的值继续执行;当结果为 None 时,会立即结束当前函数并返回 None。? 运算符同样适用于 Result,用于自动传播错误。
fn find_index(numbers: &[i32], target: i32) -> Option<usize> {
// 使用 ? 运算符传播 Option
// 如果 position() 返回 Some(index),则提取索引继续执行
// 如果返回 None,则立即结束当前函数,并返回 None
let index = numbers.iter().position(|&number| number == target)?;
Some(index)
}
fn main() {
let numbers = [10, 20, 30, 40];
// 由调用者处理异常
match find_index(&numbers, 30) {
Some(index) => println!("找到元素,索引为 {}", index),
None => println!("未找到元素"),
}
}
shell> cargo run
找到元素,索引为 2
另外,Option 提供了大量的操作函数,用于判断值是否存在、获取内部值、转换数据、组合多个 Option 以及处理引用转换等场景,使开发者无需频繁使用 match 手动匹配 Some 和 None,即可完成常见的空值处理逻辑。
fn find_index(numbers: &[i32], target: i32) -> Option<usize> {
let index = numbers.iter().position(|&number| number == target)?;
Some(index)
}
fn main() {
let numbers = [10, 20, 30, 40];
let option = find_index(&numbers, 30);
println!("判断类型:is_some={}", option.is_some());
println!("判断类型:is_none={}", option.is_none());
// 值为 None 时会 panic 结束程序
println!("获取值:unwrap={}", option.unwrap());
// 值为 None 时会返回默认值 0
println!("获取值:unwrap_or={}", None.unwrap_or(0));
// 值为 None 时会返回闭包中返回的值 100
println!("获取值:unwrap_or_else={}", None.unwrap_or_else(|| 100));
// 值为 None 时会 panic 结束程序, 并提示输出指定消息
println!("获取值:expect={}", option.expect("数据不存在"));
let mut value = Some(10);
// 取出值,并将原 Option 设置为 None
println!("取出值: take={:?}, {:?}", value.take(), value);
println!("替换值: replace={:?}, {:?}", value.replace(20), value);
println!("嵌套展开:flatten={:?}", Some(Some(10)).flatten());
println!("转换值类型:map={:?}", option.map(|x| x as f64));
// 值为 None 时返回默认值 0
println!("转换值类型:map_or={}", None.map_or(0_f64, |x: i32| x as f64));
// 值为 None 时返回闭包中返回的值 100
println!("转换值类型:map_or_else={}", None.map_or_else(|| 100, |x: i32| x * 2));
// 条件过滤,成立返回 Some,否则返回 None
println!("条件过滤:filter={:?}", Some(10).filter(|x| *x == 0));
println!("条件过滤:filter={:?}", Some(10).filter(|x| *x >= 0));
println!("转换为可变:as_mut={:?}", Some(1).as_mut());
// 转换为值引用,返回 Some(&String)
println!("转换为引用:as_ref={:?}", Some(String::from("Rust")).as_ref());
// 转换为解引用后的引用,返回 Some(&str)
println!("转换为解引用后的引用:as_deref={:?}", Some(String::from("Rust")).as_deref());
// 两个 Option 都为 Some 时返回第二个,否则都返回 None
println!("组合调用:and: {:?}", Some(1).and(Some(2)));
println!("组合调用:and: {:?}", Some(1).and(None::<i32>));
println!("组合调用:and: {:?}", None::<i32>.and(Some(2)));
println!("组合调用:and: {:?}", None::<i32>.and(None::<i32>));
println!("组合调用:and_then={:?}", Some(1).and_then(|v| Some(v + 1)));
println!("组合调用:and_then={:?}", Some(1).and_then(|_| None::<i32>));
println!("组合调用:and_then={:?}", None::<i32>.and_then(|v| Some(v + 1)));
println!("组合调用:and_then={:?}", None::<i32>.and_then(|_| None::<i32>));
// 前面 Option 为 Some 返回前面的,否则返回后面的 Option
println!("组合调用:or: {:?}", Some(1).or(Some(2)));
println!("组合调用:or: {:?}", Some(1).or(None::<i32>));
println!("组合调用:or: {:?}", None::<i32>.or(Some(2)));
println!("组合调用:or: {:?}", None::<i32>.or(None::<i32>));
println!("组合调用:or_else={:?}", Some(1).or_else(|| Some(2)));
println!("组合调用:or_else={:?}", Some(1).or_else(|| None::<i32>));
println!("组合调用:or_else={:?}", None::<i32>.or_else(|| Some(2)));
println!("组合调用:or_else={:?}", None::<i32>.or_else(|| None::<i32>));
// 与迭代器结合
Some(1).iter()
.map(|v| v.to_string())
.for_each(|v| println!("与迭代器结合: iter={v}"));
let names = vec![1, 2];
let extra = Some(3);
let other = None::<i32>;
names.into_iter()
.chain(extra.into_iter())
.chain(other.into_iter()) // None 转为空迭代器,会被直接过滤
.for_each(|item| println!("与迭代器结合:into_iter={item}"));
}
shell> cargo run
判断类型:is_some=true
判断类型:is_none=false
获取值:unwrap=2
获取值:unwrap_or=0
获取值:unwrap_or_else=100
获取值:expect=2
取出值: take=Some(10), None
替换值: replace=None, Some(20)
嵌套展开:flatten=Some(10)
转换值类型:map=Some(2.0)
转换值类型:map_or=0
转换值类型:map_or_else=100
条件过滤:filter=None
条件过滤:filter=Some(10)
转换为可变:as_mut=Some(1)
转换为引用:as_ref=Some("Rust")
转换为解引用后的引用:as_deref=Some("Rust")
组合调用:and: Some(2)
组合调用:and: None
组合调用:and: None
组合调用:and: None
组合调用:and_then=Some(2)
组合调用:and_then=None
组合调用:and_then=None
组合调用:and_then=None
组合调用:or: Some(1)
组合调用:or: Some(1)
组合调用:or: Some(2)
组合调用:or: None
组合调用:or_else=Some(1)
组合调用:or_else=Some(1)
组合调用:or_else=Some(2)
组合调用:or_else=None
与迭代器结合: iter=1
与迭代器结合:into_iter=1
与迭代器结合:into_iter=2
与迭代器结合:into_iter=3
10.3 Result
Result 用于表示操作可能成功或失败的情况,例如文件读取失败、网络连接失败、数据解析失败等场景。其本质是一个枚举,包含两种可能状态:Ok(T) 表示操作成功,并携带类型为 T 的数据;Err(E) 表示操作失败,并携带类型为 E 的错误信息。调用者可以根据返回结果判断操作是否成功,也可以将 Result 继续向上传递,由上层调用者决定如何处理错误。
fn divide(a: i32, b: i32) -> Result<i32, &'static str> {
if b == 0 {
// 失败返回时可以携带错误信息
return Err("除数不能为零");
}
return Ok(a / b);
}
fn main() {
// 根据返回结果判断操作是否成功,失败则处理错误
match divide(10, 2) {
Ok(result) => println!("计算成功,结果为 {}", result),
Err(error) => println!("计算失败,{}", error),
}
match divide(10, 0) {
Ok(result) => println!("计算成功,结果为 {}", result),
Err(error) => println!("计算失败,{}", error),
}
}
shell> cargo run
计算成功,结果为 5
计算失败,除数不能为零
在一些场景中,可能会存在多种原因导致的错误,为了方便调用者区分不同类型的错误,并针对不同错误采取相应的处理方式,通常会使用枚举为每种错误场景定义专用的错误类型。
#[derive(Debug)]
enum DivideError {
// 除数为零错误
DivisionByZero(&'static str),
// 参数非法错误
InvalidArgument(&'static str),
}
fn divide(a: i32, b: i32) -> Result<i32, DivideError> {
// 检查参数是否合法
if a < 0 || b < 0 {
return Err(DivideError::InvalidArgument("参数非法,不允许负数运算"));
}
// 检查除数是否为零
if b == 0 {
return Err(DivideError::DivisionByZero("除数不能为零"));
}
return Ok(a / b);
}
fn main() {
match divide(10, 0) {
Ok(result) => println!("计算成功,结果为 {}", result),
Err(DivideError::DivisionByZero(message)) => {
println!("除法失败:{}", message);
}
Err(DivideError::InvalidArgument(message)) => {
println!("参数错误:{}", message);
}
}
match divide(-10, 2) {
Ok(result) => println!("计算成功,结果为 {}", result),
Err(DivideError::DivisionByZero(message)) => {
println!("除法失败:{}", message);
}
Err(DivideError::InvalidArgument(message)) => {
println!("参数错误:{}", message);
}
}
}
shell> cargo run
除法失败:除数不能为零
参数错误:参数非法,不允许负数运算
在复杂的项目中,不同函数、不同模块往往各自定义了独立的错误枚举类型。为了统一管理这些分散的错误,Rust 定义了 std::error::Error trait,为不同来源的错误提供统一的类型接口,使其能够以一致的方式进行描述、传递和处理。错误枚举类型只需实现该 trait,即可纳入 Rust 的标准错误处理体系。该 trait 继承 Debug 和 Display trait,其中,Display trait 用于提供面向用户的错误描述信息,Debug trait 用于输出调试信息。
use std::error::Error;
use std::fmt;
#[derive(Debug)]
struct User {
id: u32,
name: String,
balance: u32,
is_active: bool,
}
// 用户相关错误
#[derive(Debug)]
enum UserError {
NotFound,
Frozen,
}
impl fmt::Display for UserError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
UserError::NotFound => write!(f, "用户不存在"),
UserError::Frozen => write!(f, "用户已被冻结"),
}
}
}
// 实现 Error trait
impl Error for UserError {}
// 转账相关错误
#[derive(Debug)]
enum TransferError {
InsufficientBalance,
InvalidAmount,
}
impl fmt::Display for TransferError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TransferError::InsufficientBalance => {
write!(f, "余额不足")
}
TransferError::InvalidAmount => {
write!(f, "转账金额非法")
}
}
}
}
impl Error for TransferError {}
// 转账操作,将不同模块产生的错误统一抽象为 Error trait 对象返回
fn transfer_money(from_user: &mut User, to_user: &mut User, amount: u32, )
-> Result<(), Box<dyn Error>> {
// 检查用户状态
if !from_user.is_active || !to_user.is_active {
return Err(Box::new(UserError::Frozen));
}
// 检查用户是否存在
if from_user.id == 0 || to_user.id == 0 {
return Err(Box::new(UserError::NotFound));
}
// 检查金额
if amount == 0 {
return Err(Box::new(TransferError::InvalidAmount));
}
// 检查余额
if from_user.balance < amount {
return Err(Box::new(TransferError::InsufficientBalance));
}
// 执行转账
from_user.balance -= amount;
to_user.balance += amount;
println!("转账成功: {} -> {}, 金额: {}", from_user.name, to_user.name, amount);
Ok(())
}
fn main() {
let mut user1 = User {
id: 1,
name: String::from("张三"),
balance: 100,
is_active: true,
};
let mut user2 = User {
id: 2,
name: String::from("李四"),
balance: 100,
is_active: true,
};
let mut user3 = User {
id: 3,
name: String::from("王五"),
balance: 100,
is_active: false,
};
match transfer_money(&mut user1, &mut user2, 50) {
Ok(_) => println!("交易完成"),
Err(e) => println!("交易失败: {}", e),
}
match transfer_money(&mut user1, &mut user2, 0) {
Ok(_) => println!("交易完成"),
Err(e) => println!("交易失败: {}", e),
}
match transfer_money(&mut user1, &mut user3, 50) {
Ok(_) => println!("交易完成"),
Err(e) => println!("交易失败: {}", e),
}
}
shell> cargo run
转账成功: 张三 -> 李四, 金额: 50
交易完成
交易失败: 转账金额非法
交易失败: 用户已被冻结
除了显式返回 Err 外,也可以通过 ? 运算符自动传播 Result。当表达式结果为 Ok 时,? 会提取其中的值并继续执行;当结果为 Err 时,会立即结束当前函数,并将错误返回给调用者。通过 ? 运算符可以避免编写大量错误判断代码,将错误交由上层调用者统一处理。
此外,Rust 允许 main 函数返回 Result 类型,使程序入口也能够参与错误传播。当 main 返回 Ok 时,表示程序正常结束;当返回 Err 时,运行时会自动输出错误信息,并以失败状态结束程序。
fn divide(a: i32, b: i32) -> Result<i32, &'static str> {
if b == 0 {
// 失败返回时可以携带错误信息
return Err("除数不能为零");
}
return Ok(a / b);
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let result = divide(10, 2)?;
println!("结果是 {:?}", result);
let result = divide(10, 0)?;
println!("结果是 {:?}", result);
Ok(())
}
shell> cargo run
结果是 5
Error: "除数不能为零"
Result 与 Option 类型一样,同样提供了丰富的操作函数,用于判断结果状态、获取内部值、转换类型以及链式组合调用等操作,避免开发者频繁使用 match 手动匹配 Ok 和 Err,从而更加简洁地完成常见的错误传播与处理逻辑。
fn divide(a: i32, b: i32) -> Result<i32, &'static str> {
return match b != 0 {
true => Ok(a / b),
false => Err("除数不能为零")
};
}
fn main() {
let ok_result = divide(10, 2);
let err_result = divide(10, 0);
println!("判断类型:is_ok={}", ok_result.is_ok());
println!("判断类型:is_err={}", err_result.is_err());
// Ok 时返回内部值,Err 时 panic
println!("获取值:unwrap={}", divide(10, 2).unwrap());
// Err 时返回默认值
println!("获取值:unwrap_or={}", err_result.unwrap_or(0));
// Err 时返回闭包中返回的值
println!("获取值:unwrap_or_else={}", divide(10, 0).unwrap_or_else(|_| 100));
// Err 时 panic,并提示输出指定消息
println!("获取值:expect={}", divide(10, 2).expect("计算失败"));
// 获取错误值,Ok 时 panic
println!("获取错误:unwrap_err={:?}", divide(10, 0).unwrap_err());
// 获取错误值,Ok 时 panic,并提示输出指定消息
println!("获取错误:expect_err={:?}", divide(10, 0).expect_err("应该报错,但没有报错"));
// 转换成功值类型
println!("转换值类型:map={:?}", divide(10, 2).map(|x| x as f64));
// Err 时返回默认值
println!("转换值类型:map_or={}", divide(10, 0).map_or(0_f64, |x| x as f64));
// Err 时返回闭包中返回的值
println!("转换值类型:map_or_else={}", divide(10, 0).map_or_else(|_| 100_f64, |x| x as f64));
// 转换错误类型(Result 专用)
println!("转换错误类型:map_err={:?}", divide(10, 0).map_err(|e| e.to_string()));
// 转换为引用
println!("转换为引用:as_ref={:?}", divide(10, 2).as_ref());
// 转换为可变引用
let mut value = divide(10, 2);
println!("转换为可变:as_mut={:?}", value.as_mut());
// 转换为 Option
println!("转为 Option:ok={:?}", divide(10, 2).ok());
println!("转为 Option:err={:?}", divide(10, 0).err());
// 两个 Result 都为 Ok 时返回第二个,否则返回 Err
println!("组合调用:and={:?}", divide(10, 2).and(Ok::<i32, &str>(20)));
println!("组合调用:and_then={:?}", divide(10, 2).and_then(|v| Ok(v + 1)));
println!("组合调用:and_then={:?}", divide(10, 2).and_then(|_| Err::<i32, &str>("计算失败")));
// 前面 Result 为 Ok 返回前面的,否则返回后面的 Result
println!("组合调用:or={:?}", divide(10, 0).or(Ok::<i32, &str>(200)));
println!("组合调用:or_else={:?}", divide(10, 0).or_else(|_| Ok::<i32, &str>(100)));
// 与迭代器结合
let results = [Ok(1), Err("错误"), Ok(2), Ok(3)];
results.into_iter()
.filter_map(Result::ok)
.for_each(|item| println!("与迭代器结合:ok={item}"));
}
shell> cargo run
判断类型:is_ok=true
判断类型:is_err=true
获取值:unwrap=5
获取值:unwrap_or=0
获取值:unwrap_or_else=100
获取值:expect=5
获取错误:unwrap_err="除数不能为零"
获取错误:expect_err="除数不能为零"
转换值类型:map=Ok(5.0)
转换值类型:map_or=0
转换值类型:map_or_else=100
转换错误类型:map_err=Err("除数不能为零")
转换为引用:as_ref=Ok(5)
转换为可变:as_mut=Ok(5)
转为 Option:ok=Some(5)
转为 Option:err=Some("除数不能为零")
组合调用:and=Ok(20)
组合调用:and_then=Ok(6)
组合调用:and_then=Err("计算失败")
组合调用:or=Ok(200)
组合调用:or_else=Ok(100)
与迭代器结合:ok=1
与迭代器结合:ok=2
与迭代器结合:ok=3
10.4 panic! 宏
对于不可恢复错误,Rust 提供了 panic! 宏。当程序进入无法保证继续正确运行的状态时,例如内部逻辑出现严重错误、关键配置缺失,或者程序无法继续执行时等情况,可以调用 panic! 主动终止当前执行流程。panic! 会输出错误信息,并默认执行调用栈展开(stack unwinding),在展开过程中运行必要的资源清理逻辑,随后终止发生错误的线程(见多线程章节),避免程序在不确定状态下继续运行。此外,还可以通过编译配置将 panic 行为修改为直接终止进程(abort),跳过调用栈展开过程,从而减少额外开销。
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("除数不能为零");
}
return a / b;
}
fn main() {
let result = divide(10, 2);
println!("结果是: {}", result);
let result = divide(10, 0);
println!("结果是: {}", result);
}
[package]
name = "app"
version = "0.1.0"
edition = "2024"
[dependencies]
[profile.release]
panic = "abort" # panic 时直接终止程序(abort),以减少额外开销
shell> cargo run
结果是: 5
thread 'main' (4717) panicked at src/main.rs:3:9:
除数不能为零
stack backtrace:
0: __rustc::rust_begin_unwind at /rustc/.../library/std/src/panicking.rs:689:5
1: core::panicking::panic_fmt at /rustc/.../library/core/src/panicking.rs:80:14
2: app::divide at ./src/main.rs:3:9
3: app::main at ./src/main.rs:12:18
4: core::ops::function::FnOnce::call_once at rustc/.../core/src/ops/function.rs:250:5
shell> cargo run --release
结果是: 5
thread 'main' (5160) panicked at src/main.rs:3:9:
除数不能为零
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Aborted (core dumped)