《深入Rust系统编程》2.6 错误处理

错误处理是编程中不可避免的一部分,Rust 提供了强大的错误处理机制,使开发者能够编写健壮、可靠的代码。Rust 的错误处理机制基于 Result 和 Option 枚举,并结合 match 表达式、? 运算符和 panic! 宏来实现。

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:

方法说明示例
mapOk/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_orOption → Resultopt.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」更多文章

  1. 性能剖析与优化
  2. 测试与基准:criterion 与 proptest
  3. Serde 与序列化生态