2.6 错误处理
错误处理是编程中不可避免的一部分,Rust
提供了强大的错误处理机制,使开发者能够编写健壮、可靠的代码。Rust 的错误处理机制基于 Result 和 Option 枚举,并结合 match 表达式、? 运算符和 panic! 宏来实现。
2.6.1 Result 枚举
Result 枚举用于表示可能成功或失败的操作,其定义如下:
enum Result<T, E> {
Ok(T),
Err(E),
}
T是操作成功时返回的值的类型。E是操作失败时返回的错误的类型。
1. 处理 Result:
可以使用 match 表达式来处理 Result 枚举:
fn divide(numerator: i32, denominator: i32) -> Result<i32, String> {
if denominator == 0 {
Err(String::from("division by zero"))
} else {
Ok(numerator / denominator)
}
}
fn main() {
let result = divide(10, 2);
match result {
Ok(value) => println!("Result: {}", value),
Err(error) => println!("Error: {}", error),
}
}
2. ? 运算符:
? 运算符用于简化 Result 枚举的处理,如果 Result 是 Ok,则返回 Ok 中的值,如果是 Err,则提前返回错误:
fn divide(numerator: i32, denominator: i32) -> Result<i32, String> {
if denominator == 0 {
Err(String::from("division by zero"))
} else {
Ok(numerator / denominator)
}
}
fn calculate() -> Result<i32, String> {
let result1 = divide(10, 2)?;
let result2 = divide(result1, 0)?;
Ok(result2)
}
fn main() {
match calculate() {
Ok(value) => println!("Result: {}", value),
Err(error) => println!("Error: {}", error),
}
}
2.6.2 Option 枚举
Option 枚举用于表示可能为空的值,其定义如下:
enum Option<T> {
Some(T),
None,
}
T是值存在时的类型。
1. 处理 Option:
可以使用 match 表达式来处理 Option 枚举:
fn find(haystack: &str, needle: char) -> Option<usize> {
for (index, char) in haystack.chars().enumerate() {
if char == needle {
return Some(index);
}
}
None
}
fn main() {
let haystack = "hello";
let needle = 'l';
match find(haystack, needle) {
Some(index) => println!("Found at index: {}", index),
None => println!("Not found"),
}
}
2. unwrap 和 expect 方法:
unwrap 和 expect 方法用于从 Option 中提取值,如果 Option 是 None,则会引发 panic:
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.expect("Expected a value")); // panic with custom message
}
2.6.3 panic! 宏
panic! 宏用于在程序遇到不可恢复的错误时终止程序执行:
fn main() {
panic!("crash and burn");
}
2.6.4 自定义错误类型
可以定义自定义错误类型来表示特定的错误情况:
#[derive(Debug)]
enum MyError {
DivisionByZero,
NegativeNumber,
}
fn divide(numerator: i32, denominator: i32) -> Result<i32, MyError> {
if denominator == 0 {
Err(MyError::DivisionByZero)
} else if numerator < 0 || denominator < 0 {
Err(MyError::NegativeNumber)
} else {
Ok(numerator / denominator)
}
}
fn main() {
let result = divide(10, -2);
match result {
Ok(value) => println!("Result: {}", value),
Err(error) => println!("Error: {:?}", error),
}
}
2.6.5 ? 运算符与 From trait
? 运算符之所以能自动转换错误类型,是因为 Rust 要求目标错误类型实现了 From<E> trait。
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
Parse(ParseIntError),
InvalidRange,
}
impl From<ParseIntError> for AppError {
fn from(err: ParseIntError) -> Self {
AppError::Parse(err)
}
}
fn parse_and_validate(s: &str) -> Result<i32, AppError> {
let n: i32 = s.parse()?; // ParseIntError 自动转成 AppError
if n < 0 || n > 100 {
return Err(AppError::InvalidRange);
}
Ok(n)
}
2.6.6 组合子方法
Result 和 Option 提供丰富的组合子,避免显式 match:
| 方法 | 说明 | 示例 |
|---|---|---|
map | Ok/Some 时转换值 | r.map(|x| x * 2) |
and_then | 链式调用可能失败的操作 | r.and_then(|x| divide(10, x)) |
unwrap_or | 失败时提供默认值 | r.unwrap_or(0) |
unwrap_or_else | 失败时惰性计算默认值 | r.unwrap_or_else(|| expensive_default()) |
ok_or | Option → Result | opt.ok_or("missing")? |
map_err | 转换错误类型 | r.map_err(|e| format!("error: {}", e)) |
2.6.7 错误传播实战
可以使用 ? 运算符将错误传播到调用函数。在多步骤业务逻辑中,? 的链式传播尤为清晰:
fn calculate() -> Result<i32, MyError> {
let result1 = divide(10, 2)?;
let result2 = divide(result1, -2)?;
Ok(result2)
}
fn main() {
match calculate() {
Ok(value) => println!("Result: {}", value),
Err(error) => println!("Error: {:?}", error),
}
}
2.6.8 thiserror 与 anyhow 生态
生产代码中,手写错误类型较繁琐,社区提供了宏库快速定义:
// 使用 thiserror 声明式定义错误
use thiserror::Error;
#[derive(Error, Debug)]
enum AppError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("parse error: {0}")]
Parse(#[from] std::num::ParseIntError),
#[error("invalid config: {message}")]
InvalidConfig { message: String },
}
// 使用 anyhow 快速上下文感知错误
use anyhow::{Context, Result};
fn read_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("读取配置文件失败: {}", path))?;
let config: Config = serde_json::from_str(&content)
.context("配置文件JSON格式错误")?;
Ok(config)
}
选型建议:库/框架代码用 thiserror(精确错误类型),应用/CLI 用 anyhow(快速开发)。
2.6.6 代码实例
1. 处理 Result:
fn divide(numerator: i32, denominator: i32) -> Result<i32, String> {
if denominator == 0 {
Err(String::from("division by zero"))
} else {
Ok(numerator / denominator)
}
}
fn main() {
let result = divide(10, 2);
match result {
Ok(value) => println!("Result: {}", value),
Err(error) => println!("Error: {}", error),
}
}
2. 使用 ? 运算符:
fn divide(numerator: i32, denominator: i32) -> Result<i32, String> {
if denominator == 0 {
Err(String::from("division by zero"))
} else {
Ok(numerator / denominator)
}
}
fn calculate() -> Result<i32, String> {
let result1 = divide(10, 2)?;
let result2 = divide(result1, 0)?;
Ok(result2)
}
fn main() {
match calculate() {
Ok(value) => println!("Result: {}", value),
Err(error) => println!("Error: {}", error),
}
}
3. 处理 Option:
fn find(haystack: &str, needle: char) -> Option<usize> {
for (index, char) in haystack.chars().enumerate() {
if char == needle {
return Some(index);
}
}
None
}
fn main() {
let haystack = "hello";
let needle = 'l';
match find(haystack, needle) {
Some(index) => println!("Found at index: {}", index),
None => println!("Not found"),
}
}
4. 自定义错误类型:
#[derive(Debug)]
enum MyError {
DivisionByZero,
NegativeNumber,
}
fn divide(numerator: i32, denominator: i32) -> Result<i32, MyError> {
if denominator == 0 {
Err(MyError::DivisionByZero)
} else if numerator < 0 || denominator < 0 {
Err(MyError::NegativeNumber)
} else {
Ok(numerator / denominator)
}
}
fn main() {
let result = divide(10, -2);
match result {
Ok(value) => println!("Result: {}", value),
Err(error) => println!("Error: {:?}", error),
}
}
5. 错误传播:
fn calculate() -> Result<i32, MyError> {
let result1 = divide(10, 2)?;
let result2 = divide(result1, -2)?;
Ok(result2)
}
fn main() {
match calculate() {
Ok(value) => println!("Result: {}", value),
Err(error) => println!("Error: {:?}", error),
}
}
2.6.7 总结
Rust 提供了强大的错误处理机制,使开发者能够编写健壮、可靠的代码。Result 和 Option 枚举用于表示可能失败或为空的操作,match 表达式、? 运算符和 panic! 宏用于处理错误。掌握 Rust 的错误处理机制,可以编写出更安全、更可靠的 Rust 代码。
以下是一些学习 Rust 错误处理的资源:
- Rust 官方文档: https://doc.rust-lang.org/book/ch09-00-error-handling.html
- Rust by Example: https://doc.rust-lang.org/rust-by-example/error.html
- The Rust Programming Language Book: https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。