错误处理¶
📚 章节概述¶
Rust 没有异常,而是使用 Result<T, E> 和 Option<T> 类型来处理可恢复错误,使用 panic! 宏处理不可恢复错误。本章将深入讲解 Rust 的错误处理机制。
🎯 学习目标¶
- 理解
panic!的使用场景 - 掌握
Result<T, E>类型的使用 - 学会错误传播和转换
- 理解
Option<T>与错误处理的关系 - 掌握错误处理的最佳实践
📖 不可恢复错误与 panic!¶
1.1 使用 panic!¶
panic! 宏用于处理不可恢复的错误:
1.2 panic! 的常见场景¶
数组越界¶
空指针解引用¶
1.3 backtrace¶
使用 backtrace 查看错误堆栈:
1.4 何时使用 panic!¶
适合使用 panic! 的情况: - 示例代码和原型 - 测试代码 - 逻辑错误(如除以零) - 安全性要求高的场景
Rust
// 测试代码
#[test]
fn test_addition() {
assert_eq!(2 + 2, 4);
}
// 逻辑错误
fn divide(a: f64, b: f64) -> f64 {
if b == 0.0 {
panic!("Division by zero!");
}
a / b
}
🔍 可恢复错误与 Result¶
2.1 Result 类型定义¶
2.2 使用 Result¶
Rust
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("Problem opening the file: {:?}", error)
}
};
}
2.3 匹配不同的错误¶
Rust
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!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error)
}
},
};
}
2.4 使用 unwrap 和 expect¶
Rust
use std::fs::File;
fn main() {
// unwrap: 成功时返回值,失败时 panic!
let f = File::open("hello.txt").unwrap(); // .unwrap() 取出Ok/Some的值,否则panic
// expect: 类似 unwrap,但可以指定错误信息
let f = File::open("hello.txt")
.expect("Failed to open hello.txt");
}
2.5 传播错误¶
使用 ? 运算符传播错误:
Rust
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username = String::new();
File::open("hello.txt")?.read_to_string(&mut username)?; // ? 运算符:出错时自动返回Err,简化错误传播
Ok(username)
}
等价于:
Rust
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username = String::new();
let mut file = File::open("hello.txt")?;
file.read_to_string(&mut username)?;
Ok(username)
}
2.6 链式调用¶
Rust
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut username = String::new();
File::open("hello.txt")?.read_to_string(&mut username)?;
Ok(username)
}
更简洁的版本:
Rust
use std::io;
fn read_username_from_file() -> Result<String, io::Error> {
std::fs::read_to_string("hello.txt")
}
🎯 Option 与错误处理¶
3.1 Option 类型¶
3.2 Option 的方法¶
unwrap 和 expect¶
Rust
fn main() {
let some_value = Some(5);
let none_value: Option<i32> = None;
println!("{}", some_value.unwrap()); // 5
// println!("{}", none_value.unwrap()); // panic!
println!("{}", none_value.unwrap_or(0)); // 0
// println!("{}", none_value.expect("No value")); // panic! with message
}
map 和 and_then¶
Rust
fn main() {
let some_value = Some(5);
let none_value: Option<i32> = None;
// map: 转换 Some 的值
let doubled = some_value.map(|x| x * 2);
println!("{:?}", doubled); // Some(10)
// and_then: 链式调用
let result = some_value.and_then(|x| if x > 0 { Some(x * 2) } else { None });
println!("{:?}", result); // Some(10)
}
🔄 错误转换¶
4.1 自定义错误类型¶
Rust
use std::fmt;
#[derive(Debug)]
enum MyError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MyError::Io(err) => write!(f, "IO error: {}", err),
MyError::Parse(err) => write!(f, "Parse error: {}", err),
}
}
}
impl std::error::Error for MyError {}
4.2 使用 thiserror 简化错误处理¶
Rust
use thiserror::Error;
#[derive(Error, Debug)]
enum MyError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error: {0}")]
Parse(#[from] std::num::ParseIntError),
}
4.3 使用 anyhow 简化错误处理¶
Rust
use anyhow::Result;
fn read_file() -> Result<String> {
let content = std::fs::read_to_string("config.txt")?;
Ok(content)
}
📝 错误处理最佳实践¶
5.1 定义清晰的错误类型¶
Rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("File not found: {0}")]
FileNotFound(String),
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
5.2 提供有用的错误信息¶
Rust
fn read_config(path: &str) -> Result<Config, ConfigError> {
let content = std::fs::read_to_string(path)
.map_err(|e| ConfigError::FileNotFound(path.to_string()))?;
Config::parse(&content)
}
5.3 使用上下文增强错误¶
Rust
use anyhow::{Context, Result};
fn read_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.context(format!("Failed to read config file: {}", path))?;
Config::parse(&content)
.context("Failed to parse config")
}
📝 练习题¶
练习 1:Result 基础¶
Rust
use std::fs::File;
use std::io::Read;
// TODO: 实现一个函数,读取文件内容
// 如果文件不存在,返回 Err
// 如果读取成功,返回 Ok(String)
fn read_file_content(filename: &str) -> Result<String, std::io::Error> {
// TODO: 实现
todo!()
}
fn main() {
match read_file_content("test.txt") {
Ok(content) => println!("Content: {}", content),
Err(e) => println!("Error: {}", e),
}
}
练习 2:错误传播¶
Rust
use std::fs::File;
use std::io::{self, Read};
// TODO: 实现一个函数,读取文件并解析为整数
// 使用 ? 运算符传播错误
fn read_and_parse(filename: &str) -> Result<i32, io::Error> {
// TODO: 实现
todo!()
}
fn main() {
match read_and_parse("number.txt") {
Ok(num) => println!("Number: {}", num),
Err(e) => println!("Error: {}", e),
}
}
练习 3:自定义错误¶
Rust
use std::fmt;
// TODO: 定义一个自定义错误类型
// 包含 IoError 和 ParseError 两种错误
// TODO: 实现 Display trait
fn main() {
// TODO: 测试自定义错误
}
练习 4:Option 处理¶
Rust
// TODO: 实现一个函数,从字符串中提取数字
// 如果字符串包含数字,返回 Some(i32)
// 否则返回 None
fn extract_number(s: &str) -> Option<i32> {
// TODO: 实现
todo!()
}
fn main() {
let inputs = ["abc", "123", "a1b2c3"];
for input in &inputs {
println!("{}: {:?}", input, extract_number(input));
}
}
💡 最佳实践¶
1. 使用 Result 而不是 panic!¶
Rust
// 好
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
// 避免
fn divide(a: f64, b: f64) -> f64 {
if b == 0.0 {
panic!("Division by zero!");
}
a / b
}
2. 使用 ? 传播错误¶
Rust
// 好
fn read_config() -> Result<Config, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string("config.txt")?;
Ok(serde_json::from_str(&content)?)
}
// 避免
fn read_config() -> Result<Config, io::Error> {
let content = fs::read_to_string("config.txt")?;
let config: Config = match serde_json::from_str(&content) {
Ok(c) => c,
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
};
Ok(config)
}
3. 使用 thiserror 定义错误¶
Rust
// 好
use thiserror::Error;
#[derive(Error, Debug)]
enum AppError {
#[error("Config error: {0}")]
Config(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
// 避免
enum AppError {
Config(String),
Io(std::io::Error),
}
4. 使用 anyhow 简化应用代码¶
Rust
// 好
use anyhow::Result;
fn run() -> Result<()> {
let config = load_config()?;
process(&config)?;
Ok(())
}
// 避免
fn run() -> Result<(), Box<dyn std::error::Error>> {
let config = load_config()?;
process(&config)?;
Ok(())
}
⚠️ 常见错误¶
1. 忘记处理错误¶
Rust
// 错误
fn main() {
let f = File::open("hello.txt"); // 忽略错误
}
// 正确
fn main() {
let f = File::open("hello.txt")
.expect("Failed to open file");
}
2. 错误类型不匹配¶
Rust
// 错误
fn read_file() -> Result<String, io::Error> {
let content = fs::read_to_string("config.txt")?;
let config: Config = serde_json::from_str(&content)?; // 编译错误
Ok(config.to_string())
}
// 正确
fn read_file() -> Result<String, Box<dyn std::error::Error>> {
let content = fs::read_to_string("config.txt")?;
let config: Config = serde_json::from_str(&content)?;
Ok(config.to_string())
}
3. 过度使用 unwrap¶
Rust
// 错误
fn main() {
let content = fs::read_to_string("config.txt").unwrap();
let config: Config = serde_json::from_str(&content).unwrap();
}
// 正确
fn main() -> Result<(), Box<dyn std::error::Error>> {
let content = fs::read_to_string("config.txt")?;
let config: Config = serde_json::from_str(&content)?;
Ok(())
}
📚 扩展阅读¶
🎯 本章小结¶
本章介绍了 Rust 的错误处理机制:
- ✅ 理解
panic!的使用场景 - ✅ 掌握
Result<T, E>类型的使用 - ✅ 学会错误传播和转换
- ✅ 理解
Option<T>与错误处理的关系 - ✅ 掌握错误处理的最佳实践
下一章: 我们将学习 Rust 的集合类型和迭代器,包括 Vec、HashMap、HashSet 等。