错误处理
Rust 将错误分为两大类: 可恢复的(recoverable)和不可恢复的(unrecoverable)错误
- 不可恢复错误: 使用
panic!宏,程序遇到不可恢复的错误时停止执行 - 可恢复错误: 使用
Result<T, E>类型处理,允许函数返回错误但继续运行
用 panic! 处理不可恢复的错误
panic! 宏用于处理程序无法继续运行的情况.当触发 panic 时,程序会停止执行.
panic 的行为控制
- 出现 panic 时,程序默认会开始展开(unwinding),逐层清理资源
- 在
Cargo.toml的[profile]部分增加panic = 'abort',可以由展开切换为终止,程序立即退出,可以使最终生成的二进制文件变小
配置示例
在 release 模式下由展开切换为终止
toml
[profile.release]
panic = 'abort'调试 panic 信息
设置 RUST_BACKTRACE 环境变量查看详细的错误堆栈信息:
RUST_BACKTRACE=1: 显示堆栈跟踪RUST_BACKTRACE=full: 显示完整堆栈信息,包括标准库调用
bash
RUST_BACKTRACE=1 cargo runpanic! 宏的使用
rust
fn main() {
panic!("程序崩溃了!这是一个不可恢复的错误");
}用 Result 处理可恢复的错误
Result<T, E>
- 表示要么成功,要么失败,可以携带信息
- 和
Option<T>类似,只有 2 个变体:Ok(T)或Err(E)
rust
enum Result<T, E> {
Ok(T),
Err(E),
}最简单的错误处理方式
对于简单场景,直接使用 match 或 if let 处理:
rust
use std::fs::File;
fn main() {
match File::open("hello.txt") {
Ok(file) => println!("文件打开成功"),
Err(error) => println!("打开失败: {}", error),
}
}失败时 panic 的快捷方式: unwrap 和 expect
- 只能使用在
Result或Option类型上 unwrap(): 是Err<E>时直接 panicexpect(&str): 可以带自定义错误信息,是Err<E>时直接 panic
rust
use std::fs::File;
fn main() {
let get_file = File::open("hello.txt");
dbg!(get_file.unwrap());
dbg!(get_file.expect("找不到文件"));
}传播错误的快捷方式: ? 运算符
- 当你在函数中使用
?时,是在说: "如果这里出错了,我不想在这个函数里处理它,请把它传给调用我的那个人" - 相当于"甩锅",把错误传递给上一层调用者
- 只能用于返回值是
Result或Option的函数
rust
use std::fs::File;
use std::io::{self, Read};
// 最底层的函数 B: 负责读文件
fn read_config() -> Result<String, io::Error> {
// 如果文件不存在,? 会立即让函数返回 Err,不再执行后面的代码
let mut f = File::open("config.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
// 中间层函数 A: 直接把 B 的错误传给上层
fn process_config() -> Result<usize, io::Error> {
let content = read_config()?; // 这里也用 ?,继续往上传
Ok(content.len())
}
// 最高层 main: 最终在这里处理错误
fn main() {
match process_config() {
Ok(size) => println!("文件大小: {}", size),
Err(e) => println!("报错了!错误原因是: {}", e), // 最终在这里处理
}
}? 运算符自动类型转换
?运算符最强大之处在于甩锅时会自动调用From::from进行错误类型转换,这解决了多种错误类型的统一返回问题.- 这解决了一个痛点: 如果一个函数里可能发生多种类型的错误,返回值该写哪一个的问题
- 简单理解就是
Result的错误类型返回必须保持一致,或者被包含,如果错误范围足够大就可以返回任意错误类型(Box<dyn std::error::Error>)
只能用于函数返回值是
ResultorOption的函数里面
?实际上是一个解包 (Unwrap) 操作:
- 如果是
Some(v)/Ok(v),?的表达式结果就是v.- 如果是
None/Err(E),它会立即执行return None/Err(E).和TS不一样的是:使用
?.如果值不存在并不会影响下一步操作,而rust直接停止(return)执行下一步
使用 Trait Object 进行类型转换
- 使用
Box<dyn std::error::Error>,最简单,生产常用,它可以接收任何实现了Error特性的错误类型.
rust
use std::error::Error;
use std::fs::File;
use std::io::Read;
fn get_number_from_file() -> Result<i32, Box<dyn Error>> {
let mut s = String::new();
// ? 发现 io::Error 不等于 Box<dyn Error>
// 于是自动调用了 io::Error::into() 将其转换并装箱
File::open("num.txt")?.read_to_string(&mut s)?;
// ? 同样将 ParseIntError 自动转换为了 Box<dyn Error>
let num: i32 = s.trim().parse()?;
Ok(num)
}自定义错误枚举
手动为错误类型实现 From trait,使 ? 能精准转换:
rust
use std::io;
use std::num::ParseIntError;
enum MyError {
Io(io::Error),
Parse(ParseIntError),
}
// 手动实现从 io::Error 到 MyError 的转换
impl From<io::Error> for MyError {
fn from(err: io::Error) -> MyError {
MyError::Io(err)
}
}
impl From<ParseIntError> for MyError {
fn from(err: ParseIntError) -> MyError {
MyError::Parse(err)
}
}
// 这样在函数里就能统一返回 Result<T, MyError>,? 运算符会自动干活
fn get_number() -> Result<i32, MyError> {
let mut s = String::new();
File::open("num.txt")?.read_to_string(&mut s)?;
let num: i32 = s.trim().parse()?;
Ok(num)
}Result<T, E> 的常用方法
| 方法 | 作用 |
|---|---|
is_ok() | 判断是否是 Ok 变体,返回 true 或 false |
is_err() | 判断是否是 Err 变体,返回 true 或 false |
ok() | 将 Ok(v) 转换为 Some(v), 将 Err(e) 转换为 None |
err() | 将 Err(e) 转换为 Some(e),将 Ok(v) 转换为 None |
unwrap() | 取出 Ok 中的值;若是 Err 则 panic. |
unwrap_or(def) | 有值取值;若是 Err 则返回 def. |
unwrap_or_else(f) | 若是 Err(e),执行闭包 f(e);根据错误生成默认值. |
expect(msg) | 同 unwrap,但 panic 时显示自定义 msg 和错误内容. |
Option 和 Result 的对比
错误处理
| 工具 | 在 Option<T> 中 | 在 Result<T, E> 中 |
|---|---|---|
unwrap() | 取出 Some 中的值;若是 None 则 panic. | 取出 Ok 中的值;若是 Err 则 panic 并打印错误. |
unwrap_or(def) | 有值取值;若是 None 则返回 def. | 有值取值;若是 Err 则返回 def. |
unwrap_or_else(f) | 若是 None,执行闭包 f() 并返回其结果(延迟求值). | 若是 Err(e),执行闭包 f(e);根据错误生成默认值. |
expect(msg) | 同 unwrap,但 panic 时显示自定义 msg. | 同 unwrap,但 panic 时显示 msg 和错误内容. |
? 运算符 | Some 则解包;None 则函数提前返回 None. | Ok 则解包;Err 则函数提前返回 Err(E). |
? 运算符的类型匹配要求
- 函数返回
Option时,只能对Option类型使用? - 函数返回
Result时,只能对Result类型使用? - 混合使用时需要先转换:
option_val.ok_or("错误信息")?
总结: unwrap/expect 用于报错;unwrap_or 用于提供默认值;? 用于错误传播.
Option 和 Result 互转
- 核心逻辑很简单:
None对应Err,Some对应Ok - 由于
Result多了一个"错误信息",所以转换时通常需要你补全或丢弃那个错误信息
Option → Result (最常用)
当你有一个可选值,但如果它不存在就想当作一个错误上报时使用.
ok_or(err): 如果是 None,则返回Err(err);如果是 Some(v),则返回Ok(v)ok_or_else(f): 只有在 None 时才执行闭包f()计算错误消息,性能更好(延迟求值)
rust
let opt: Option<i32> = None;
// 转换成 Result
let res = opt.ok_or("错误: 值不存在");
// 结果: Err("错误: 值不存在")
let res_lazy = opt.ok_or_else(|| format!("{} 找不到", "ID"));
// 结果: Err("ID 找不到")Result → Option
当你只关心成功的值,想忽略具体的错误原因时使用
ok(): 把Ok(v)变成Some(v),把Err(e)直接丢弃变成Noneerr(): 反过来,把错误提取出来变成Some(e),成功的值反而变None
rust
let res: Result<i32, &str> = Err("IO Error");
// 只想要成功的值
let opt = res.ok();
// 结果: None (错误被丢弃了)
// 只想要错误信息
let err_opt = res.err();
// 结果: Some("IO Error")综合例子
rust
use std::fs::File;
fn main() {
let x = File::open("./src/a.rs").expect("没有找到文件");
// dbg!(&x);
let y = add();
dbg!(&y);
if let Option::Some(ok) = y {
dbg!(ok);
}
}
fn add() -> Option<File> {
let x = Some(1 + 1)?; // 函数期望返回Option,这里也是Option类型,可以使用
Some(x);
let y = File::open("./src/a.rs").ok()?; // 函数期望返回Option,使用ok()函数将Result转换为Option类型
Some(y) // 返回 Option<File>类型
}