====== 第十章 错误处理 ====== ===== 10.1 Rust的错误处理哲学 ===== Rust将错误分为两类: * **可恢复错误**:用户可以处理的问题,如文件未找到 * **不可恢复错误**:bug,如数组越界访问 **对应机制:** * 可恢复错误 -> Result * 不可恢复错误 -> 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 { 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::().unwrap_or(0); // 解析失败返回0 // 或使用闭包 let n = s.parse::().unwrap_or_else(|_| { println!("解析失败,使用默认值"); 0 }); ===== 10.5 传播错误 ===== ==== 手动传播 ==== use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result { 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 { 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 { fs::read_to_string("hello.txt") } 更进一步简化: use std::io; fn read_username_from_file() -> Result { std::fs::read_to_string("hello.txt") } ===== 10.6 ?与Option ===== ?也可用于Option: fn last_char_of_first_line(text: &str) -> Option { 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> { 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 { 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 { 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, Box> { 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**:可恢复错误,Ok或Err * **unwrap/expect**:快捷处理,失败时panic * **?运算符**:传播错误的简洁语法 * **最佳实践**: * 可恢复错误用Result * 不可恢复错误用panic * 提供有用的错误信息 Rust的错误处理强制程序员处理所有可能的错误情况,使程序更健壮。