rust:第十章错误处理
目录
第十章 错误处理
10.1 Rust的错误处理哲学
Rust将错误分为两类:
- 可恢复错误:用户可以处理的问题,如文件未找到
- 不可恢复错误:bug,如数组越界访问
对应机制:
- 可恢复错误 → Result<T, E>
- 不可恢复错误 → panic!
10.2 不可恢复错误与panic!
什么是panic
当代码panic时,程序会:
1. 打印错误信息 2. 展开(unwind)调用栈,清理数据 3. 退出程序
触发panic
显式调用:
panic!("crash and burn");
隐式触发(bug):
let v = vec![1, 2, 3]; v[99]; // panic!索引越界
panic时的行为
默认情况下,panic会展开调用栈:
1. Rust沿着调用栈回溯 2. 清理每个函数中的数据 3. 打印错误信息并退出
设置panic为中止(abort):
在Cargo.toml中:
[profile.release] panic = 'abort'
这样panic时不会清理,直接退出,可减小二进制文件大小。
使用panic!宏
fn main() { let guess: i32 = "not a number".parse().expect("需要一个数字!"); // 或者 // let guess: i32 = "not a number".parse().unwrap(); }
环境变量控制backtrace
设置环境变量查看完整的调用栈:
RUST_BACKTRACE=1 cargo run
使用full获取更详细的信息:
RUST_BACKTRACE=full cargo run
10.3 可恢复错误与Result
Result枚举
enum Result<T, E> { Ok(T), Err(E), }
使用Result:
use std::fs::File; let f = File::open("hello.txt"); let f = match f { Ok(file) => file, Err(error) => { println!("打开文件失败:{:?}", error); panic!("程序退出"); } };
匹配不同错误
use std::fs::File; use std::io::ErrorKind; let f = File::open("hello.txt"); let f = match f { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") { Ok(fc) => fc, Err(e) => panic!("创建文件失败:{:?}", e), }, other_error => { panic!("打开文件失败:{:?}", other_error) } }, };
10.4 快捷方法
unwrap
Ok时返回值,Err时panic:
let f = File::open("hello.txt").unwrap();
expect
unwrap的增强版,可自定义panic信息:
let f = File::open("hello.txt") .expect("hello.txt应该存在");
建议:在真实项目中优先使用expect,提供有用的错误信息。
unwrap_or和unwrap_or_else
提供默认值:
let s = "123"; let n = s.parse::<i32>().unwrap_or(0); // 解析失败返回0 // 或使用闭包 let n = s.parse::<i32>().unwrap_or_else(|_| { println!("解析失败,使用默认值"); 0 });
10.5 传播错误
手动传播
use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt"); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } }
?运算符
?是传播错误的简写:
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) }
?的行为:
- Ok时解开值
- Err时提前返回Err
?与match等价
let mut f = match File::open("hello.txt") { Ok(file) => file, Err(e) => return Err(e), }; // 等价于 let mut f = File::open("hello.txt")?;
链式调用?
use std::fs; use std::io; fn read_username_from_file() -> Result<String, io::Error> { fs::read_to_string("hello.txt") }
更进一步简化:
use std::io; fn read_username_from_file() -> Result<String, io::Error> { std::fs::read_to_string("hello.txt") }
10.6 ?与Option
?也可用于Option:
fn last_char_of_first_line(text: &str) -> Option<char> { text.lines().next()?.chars().last() } fn main() { assert_eq!( last_char_of_first_line("Hello, world\nHow are you"), Some('d') ); assert_eq!(last_char_of_first_line(""), None); assert_eq!(last_char_of_first_line("\nhi"), None); }
混合使用Result和Option:
use std::error::Error; fn parse_and_double(s: Option<&str>) -> Result<i32, Box<dyn Error>> { let s = s.ok_or("没有输入")?; let n: i32 = s.parse()?; Ok(n * 2) }
10.7 错误处理最佳实践
何时使用panic
应该panic的情况:
- 示例代码、原型代码、测试
- 你确定不可能发生的情况(如unreachable!())
- 损坏的状态无法恢复
// 示例:unwrap在测试中可以接受 #[test] fn test_something() { let result = some_operation().unwrap(); assert_eq!(result, expected); } // 示例:不可能发生的情况 match some_enum { A => ..., B => ..., _ => unreachable!("枚举只有A和B两个变体"), }
不应该panic的情况:
- 无效的用户输入
- 外部资源不可用
- 网络连接失败
错误信息
提供有用的错误信息:
// 好的做法 let config = Config::build(&args) .expect("解析参数失败"); // 更好的做法:传播错误让用户处理 let config = Config::build(&args)?;
练习题
练习题10.1:安全除法
fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("除数不能为零")) } else { Ok(a / b) } } fn main() { match divide(10.0, 2.0) { Ok(result) => println!("结果:{}", result), Err(msg) => println!("错误:{}", msg), } match divide(10.0, 0.0) { Ok(result) => println!("结果:{}", result), Err(msg) => println!("错误:{}", msg), } }
练习题10.2:读取配置文件
use std::fs; use std::io; fn read_config(filename: &str) -> Result<String, io::Error> { fs::read_to_string(filename) } fn main() { match read_config("config.txt") { Ok(content) => println!("配置内容:\n{}", content), Err(e) => println!("读取配置失败:{}", e), } }
练习题10.3:级联错误处理
use std::fs::File; use std::io::{self, BufRead, BufReader}; fn read_numbers(filename: &str) -> Result<Vec<i32>, Box<dyn std::error::Error>> { let file = File::open(filename)?; let reader = BufReader::new(file); let mut numbers = Vec::new(); for line in reader.lines() { let line = line?; let num: i32 = line.parse()?; numbers.push(num); } Ok(numbers) } fn main() { match read_numbers("numbers.txt") { Ok(nums) => println!("数字:{:?}", nums), Err(e) => println!("错误:{}", e), } }
本章小结
本章学习了Rust的错误处理机制:
- panic!:不可恢复错误,程序终止
- Result<T, E>:可恢复错误,Ok或Err
- unwrap/expect:快捷处理,失败时panic
- ?运算符:传播错误的简洁语法
- 最佳实践:
- 可恢复错误用Result
- 不可恢复错误用panic
- 提供有用的错误信息
Rust的错误处理强制程序员处理所有可能的错误情况,使程序更健壮。
rust/第十章错误处理.txt · 最后更改: 由 127.0.0.1
