错误处理
Rust 将错误分为两类:可恢复错误(如文件未找到)和不可恢复错误(如数组越界)。对于前者使用 Result<T, E>,后者使用 panic!。
panic! 与不可恢复错误
Section titled “panic! 与不可恢复错误”当程序遇到无法处理的错误时,可以使用 panic! 宏终止程序。
显式调用 panic!
Section titled “显式调用 panic!”fn main() { panic!("程序崩溃了!");}输出:
thread 'main' panicked at '程序崩溃了!', src/main.rs:2:5隐式 panic
Section titled “隐式 panic”某些操作会导致 panic:
fn main() { let v = vec![1, 2, 3]; v[99]; // 越界访问,panic!}查看 panic 调用栈
Section titled “查看 panic 调用栈”设置环境变量查看完整调用栈:
RUST_BACKTRACE=1 cargo runpanic 的两种模式
Section titled “panic 的两种模式”在 Cargo.toml 中配置:
[profile.release]panic = 'abort' # 直接终止,不展开栈(二进制更小)默认是 unwind(展开栈,清理数据)。
Result 与可恢复错误
Section titled “Result 与可恢复错误”大多数错误应该是可恢复的,使用 Result 类型处理:
enum Result<T, E> { Ok(T), // 成功,包含结果值 Err(E), // 失败,包含错误信息}use std::fs::File;
fn main() { let f = File::open("hello.txt");
let f = match f { Ok(file) => file, Err(error) => { panic!("无法打开文件: {:?}", error); } };}匹配不同类型的错误
Section titled “匹配不同类型的错误”use std::fs::File;use std::io::ErrorKind;
fn main() { 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); } }, };}使用闭包更简洁:
use std::fs::File;use std::io::ErrorKind;
fn main() { let f = File::open("hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("hello.txt").unwrap_or_else(|error| { panic!("无法创建文件: {:?}", error); }) } else { panic!("无法打开文件: {:?}", error); } });}简化错误处理的方法
Section titled “简化错误处理的方法”unwrap
Section titled “unwrap”成功时返回 Ok 中的值,失败时 panic!:
use std::fs::File;
fn main() { let f = File::open("hello.txt").unwrap();}expect
Section titled “expect”与 unwrap 类似,但可以自定义错误信息:
use std::fs::File;
fn main() { let f = File::open("hello.txt") .expect("无法打开 hello.txt");}建议:在生产代码中优先使用 expect,因为它提供了更有意义的错误信息。
unwrap_or
Section titled “unwrap_or”失败时返回默认值:
fn main() { let port = std::env::var("PORT") .unwrap_or(String::from("8080"));
println!("端口: {}", port);}unwrap_or_else
Section titled “unwrap_or_else”失败时执行闭包:
fn main() { let port = std::env::var("PORT") .unwrap_or_else(|_| { println!("未设置 PORT,使用默认值"); String::from("8080") });}? 操作符传播错误
Section titled “? 操作符传播错误”? 操作符可以简化错误传播:
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")?; // 如果失败,提前返回 Err let mut s = String::new(); f.read_to_string(&mut s)?; // 如果失败,提前返回 Err Ok(s)}? 的作用:
- 如果
Result是Ok,返回Ok中的值 - 如果
Result是Err,从函数返回Err
use std::fs::File;use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> { let mut s = String::new(); File::open("hello.txt")?.read_to_string(&mut s)?; Ok(s)}更简洁的方式
Section titled “更简洁的方式”标准库提供了更简洁的函数:
use std::fs;use std::io;
fn read_username_from_file() -> Result<String, io::Error> { fs::read_to_string("hello.txt")}? 用于 Option
Section titled “? 用于 Option”? 也可以用于 Option:
fn first_char(text: &str) -> Option<char> { text.lines().next()?.chars().next()}? 在 main 中使用
Section titled “? 在 main 中使用”use std::error::Error;use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> { let f = File::open("hello.txt")?; Ok(())}自定义错误类型
Section titled “自定义错误类型”简单的错误枚举
Section titled “简单的错误枚举”#[derive(Debug)]enum AppError { IoError(std::io::Error), ParseError(std::num::ParseIntError), CustomError(String),}
fn do_something() -> Result<i32, AppError> { let content = std::fs::read_to_string("number.txt") .map_err(AppError::IoError)?;
let number: i32 = content.trim().parse() .map_err(AppError::ParseError)?;
if number < 0 { return Err(AppError::CustomError("数字不能为负".to_string())); }
Ok(number)}实现 Error trait
Section titled “实现 Error trait”use std::error::Error;use std::fmt;
#[derive(Debug)]struct MyError { message: String,}
impl fmt::Display for MyError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "错误: {}", self.message) }}
impl Error for MyError {}
fn main() -> Result<(), MyError> { Err(MyError { message: String::from("出错了"), })}使用 From trait 进行转换
Section titled “使用 From trait 进行转换”use std::io;use std::num::ParseIntError;
#[derive(Debug)]enum AppError { Io(io::Error), Parse(ParseIntError),}
impl From<io::Error> for AppError { fn from(error: io::Error) -> Self { AppError::Io(error) }}
impl From<ParseIntError> for AppError { fn from(error: ParseIntError) -> Self { AppError::Parse(error) }}
fn read_and_parse() -> Result<i32, AppError> { let content = std::fs::read_to_string("number.txt")?; // 自动转换 let number: i32 = content.trim().parse()?; // 自动转换 Ok(number)}何时 panic vs Result
Section titled “何时 panic vs Result”使用 panic! 的场景
Section titled “使用 panic! 的场景”- 示例和原型代码:快速验证想法
- 测试代码:断言失败时
- 确定不会失败:逻辑上不可能失败时
fn main() { // 我们知道这是有效的 IP 地址 let home: std::net::IpAddr = "127.0.0.1".parse().unwrap();}- 违反契约:调用者传入了无效参数
使用 Result 的场景
Section titled “使用 Result 的场景”- 预期可能失败的操作:文件操作、网络请求等
- 库代码:让调用者决定如何处理错误
- 可恢复的错误:可以采取替代措施
创建自定义类型进行验证
Section titled “创建自定义类型进行验证”pub struct Guess { value: i32,}
impl Guess { pub fn new(value: i32) -> Result<Guess, String> { if value < 1 || value > 100 { return Err(format!("猜测值必须在 1 到 100 之间,收到: {}", value)); } Ok(Guess { value }) }
pub fn value(&self) -> i32 { self.value }}练习 1:读取文件内容
Section titled “练习 1:读取文件内容”编写一个函数,读取文件内容,使用 ? 传播错误:
use std::fs;use std::io;
fn read_file(path: &str) -> Result<String, io::Error> { // 你的代码}
fn main() { match read_file("test.txt") { Ok(content) => println!("内容: {}", content), Err(e) => println!("错误: {}", e), }}练习 2:链式错误处理
Section titled “练习 2:链式错误处理”编写一个函数,读取文件 -> 解析为数字 -> 计算平方:
use std::fs;use std::io;use std::num::ParseIntError;
#[derive(Debug)]enum MyError { Io(io::Error), Parse(ParseIntError),}
fn read_and_square(path: &str) -> Result<i32, MyError> { // 你的代码}
fn main() { match read_and_square("number.txt") { Ok(result) => println!("结果: {}", result), Err(e) => println!("错误: {:?}", e), }}练习 3:自定义错误类型
Section titled “练习 3:自定义错误类型”为练习 2 的 MyError 实现 From trait,使 ? 能自动转换错误类型:
impl From<io::Error> for MyError { // 你的代码}
impl From<ParseIntError> for MyError { // 你的代码}练习 4:验证类型
Section titled “练习 4:验证类型”创建一个 Email 类型,验证邮箱格式(简单验证:包含 @):
struct Email { value: String,}
impl Email { fn new(email: &str) -> Result<Email, String> { // 你的代码 }
fn value(&self) -> &str { &self.value }}
fn main() { match Email::new("user@example.com") { Ok(email) => println!("有效邮箱: {}", email.value()), Err(e) => println!("无效: {}", e), }
match Email::new("invalid-email") { Ok(email) => println!("有效邮箱: {}", email.value()), Err(e) => println!("无效: {}", e), }}学完错误处理后,下一章我们将学习 Rust 的模块系统,了解如何组织代码。