Rust错误处理Skill rust-error-handling

本技能教授在Rust中有效处理错误的方法,涵盖Result和Option类型、错误传播、自定义错误定义,以及使用thiserror和anyhow库简化错误管理,提升应用程序的健壮性、可维护性和SEO友好性。关键词包括Rust、错误处理、Result、Option、thiserror、anyhow、自定义错误、应用程序开发、可恢复错误。

后端开发 0 次安装 0 次浏览 更新于 3/25/2026

名称: rust-error-handling 用户可调用: false 描述: 当使用Rust进行错误处理时,涉及Result、Option、自定义错误、thiserror和anyhow库时使用。适用于Rust应用程序中的错误处理。 允许工具:

  • Bash
  • Read

Rust错误处理

掌握Rust的错误处理机制,使用Result、Option、自定义错误类型和流行错误处理库,构建健壮的应用程序。

Result和Option

Result类型用于可恢复错误:

// Result<T, E>用于可能失败的操作
fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("除零错误"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10.0, 2.0) {
        Ok(result) => println!("结果: {}", result),
        Err(e) => println!("错误: {}", e),
    }
}

Option类型用于可选值:

fn find_user(id: u32) -> Option<String> {
    if id == 1 {
        Some(String::from("Alice"))
    } else {
        None
    }
}

fn main() {
    match find_user(1) {
        Some(name) => println!("找到: {}", name),
        None => println!("用户未找到"),
    }
}

使用?进行错误传播

使用?运算符:

use std::fs::File;
use std::io::{self, Read};

fn read_file(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;  // 传播错误
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;  // 传播错误
    Ok(contents)
}

// 等效的无?运算符版本
fn read_file_explicit(path: &str) -> Result<String, io::Error> {
    let mut file = match File::open(path) {
        Ok(f) => f,
        Err(e) => return Err(e),
    };

    let mut contents = String::new();
    match file.read_to_string(&mut contents) {
        Ok(_) => Ok(contents),
        Err(e) => Err(e),
    }
}

?与Option一起使用:

fn get_first_char(text: &str) -> Option<char> {
    text.chars().next()
}

fn process_text(text: Option<&str>) -> Option<char> {
    let t = text?;  // 如果text为None则返回None
    get_first_char(t)
}

自定义错误类型

简单自定义错误:

use std::fmt;

#[derive(Debug)]
struct ParseError {
    message: String,
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "解析错误: {}", self.message)
    }
}

impl std::error::Error for ParseError {}

fn parse_number(s: &str) -> Result<i32, ParseError> {
    s.parse().map_err(|_| ParseError {
        message: format!("无法解析 '{}'", s),
    })
}

基于枚举的错误类型:

use std::fmt;
use std::io;

#[derive(Debug)]
enum AppError {
    Io(io::Error),
    Parse(String),
    NotFound(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::Io(e) => write!(f, "IO错误: {}", e),
            AppError::Parse(msg) => write!(f, "解析错误: {}", msg),
            AppError::NotFound(item) => write!(f, "未找到: {}", item),
        }
    }
}

impl std::error::Error for AppError {}

impl From<io::Error> for AppError {
    fn from(error: io::Error) -> Self {
        AppError::Io(error)
    }
}

fn process_file(path: &str) -> Result<String, AppError> {
    let content = std::fs::read_to_string(path)?;  // io::Error自动转换

    if content.is_empty() {
        Err(AppError::NotFound(path.to_string()))
    } else {
        Ok(content)
    }
}

thiserror库

安装thiserror:

cargo add thiserror

使用thiserror定义自定义错误:

use thiserror::Error;

#[derive(Error, Debug)]
enum DataError {
    #[error("IO错误: {0}")]
    Io(#[from] std::io::Error),

    #[error("解析错误: {0}")]
    Parse(String),

    #[error("验证失败: {field} 无效")]
    Validation { field: String },

    #[error("未找到: {0}")]
    NotFound(String),
}

fn validate_user(name: &str) -> Result<(), DataError> {
    if name.is_empty() {
        return Err(DataError::Validation {
            field: "name".to_string(),
        });
    }
    Ok(())
}

fn load_data(path: &str) -> Result<String, DataError> {
    let data = std::fs::read_to_string(path)?;  // 自动转换io::Error

    if data.is_empty() {
        return Err(DataError::NotFound(path.to_string()));
    }

    Ok(data)
}

thiserror与源错误:

use thiserror::Error;
use std::io;

#[derive(Error, Debug)]
enum ConfigError {
    #[error("读取配置文件失败")]
    ReadError {
        #[source]
        source: io::Error,
    },

    #[error("配置格式无效")]
    ParseError {
        #[source]
        source: serde_json::Error,
    },
}

anyhow库

安装anyhow:

cargo add anyhow

使用anyhow处理应用程序错误:

use anyhow::{Result, Context, anyhow, bail};

fn read_config(path: &str) -> Result<String> {
    let content = std::fs::read_to_string(path)
        .context("读取配置文件失败")?;

    if content.is_empty() {
        bail!("配置文件为空");
    }

    Ok(content)
}

fn process_data(value: i32) -> Result<i32> {
    if value < 0 {
        return Err(anyhow!("值必须为正数, 得到 {}", value));
    }
    Ok(value * 2)
}

fn main() -> Result<()> {
    let config = read_config("config.toml")
        .context("加载配置失败")?;

    let value = process_data(42)?;

    println!("值: {}", value);
    Ok(())
}

anyhow上下文链:

use anyhow::{Result, Context};

fn load_user(id: u32) -> Result<String> {
    fetch_from_database(id)
        .context("数据库查询失败")?
        .parse()
        .context(format!("解析用户 {} 失败", id))
}

fn fetch_from_database(id: u32) -> Result<String> {
    // 实现
    Ok(format!("user_{}", id))
}

错误转换

在错误类型之间转换:

use std::io;
use std::num::ParseIntError;

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 process() -> Result<i32, AppError> {
    let content = std::fs::read_to_string("file.txt")?;
    let number: i32 = content.trim().parse()?;
    Ok(number)
}

unwrap和expect

何时使用unwrap和expect:

fn unwrap_examples() {
    // unwrap: 用通用消息恐慌
    let value = Some(42).unwrap();

    // expect: 用自定义消息恐慌
    let value = Some(42).expect("值应存在");

    // 仅在以下情况使用:
    // 1. 测试
    // 2. 原型
    // 3. 确信不会恐慌时

    // 更好: 处理错误
    if let Some(value) = get_value() {
        println!("{}", value);
    }
}

fn get_value() -> Option<i32> {
    Some(42)
}

Result组合器

使用Result方法:

fn combinators() -> Result<i32, String> {
    // map: 转换Ok值
    let result = Ok(5).map(|x| x * 2);  // Ok(10)

    // map_err: 转换Err值
    let result = Err("错误").map_err(|e| format!("错误: {}", e));

    // and_then (flatMap): 链式操作
    let result = Ok(5)
        .and_then(|x| Ok(x * 2))
        .and_then(|x| Ok(x + 1));  // Ok(11)

    // or_else: 提供错误时的替代
    let result = Err("错误")
        .or_else(|_| Ok(42));  // Ok(42)

    // unwrap_or: 提供错误时的默认值
    let value = Err("错误").unwrap_or(42);  // 42

    // unwrap_or_else: 计算错误时的默认值
    let value = Err("错误").unwrap_or_else(|_| 42);  // 42

    Ok(value)
}

Option组合器

使用Option方法:

fn option_combinators() {
    // map: 转换Some值
    let result = Some(5).map(|x| x * 2);  // Some(10)

    // and_then (flatMap): 链式操作
    let result = Some(5)
        .and_then(|x| Some(x * 2))
        .and_then(|x| Some(x + 1));  // Some(11)

    // or: 提供替代
    let result = None.or(Some(42));  // Some(42)

    // unwrap_or: 提供默认值
    let value = None.unwrap_or(42);  // 42

    // filter: 仅当谓词为真时保留
    let result = Some(5).filter(|x| x > &3);  // Some(5)
    let result = Some(2).filter(|x| x > &3);  // None

    // ok_or: 将Option转换为Result
    let result: Result<i32, &str> = Some(5).ok_or("错误");  // Ok(5)
}

模式匹配

使用match进行全面的错误处理:

use std::fs::File;
use std::io::ErrorKind;

fn open_file(path: &str) -> File {
    let file = match File::open(path) {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => {
                match File::create(path) {
                    Ok(file) => file,
                    Err(e) => panic!("创建文件失败: {:?}", e),
                }
            }
            ErrorKind::PermissionDenied => {
                panic!("权限被拒绝: {}", path);
            }
            other_error => {
                panic!("打开文件失败: {:?}", other_error);
            }
        },
    };

    file
}

if let处理简单情况:

fn simple_match(result: Result<i32, String>) {
    // 仅处理成功情况
    if let Ok(value) = result {
        println!("得到值: {}", value);
    }

    // 仅处理错误情况
    if let Err(e) = result {
        eprintln!("错误: {}", e);
    }
}

Panic与Result

何时使用panic:

// 用于不可恢复错误或bug
fn get_element(index: usize) -> i32 {
    let data = vec![1, 2, 3];

    // 如果索引越界则恐慌(程序员错误)
    data[index]
}

// 使用Result处理预期错误
fn safe_get_element(index: usize) -> Option<i32> {
    let data = vec![1, 2, 3];
    data.get(index).copied()
}

// 自定义恐慌消息
fn validate_config(value: i32) {
    if value < 0 {
        panic!("配置值必须为正数, 得到 {}", value);
    }
}

// 条件恐慌
fn debug_only_panic(condition: bool) {
    debug_assert!(condition, "这仅在调试构建中恐慌");
}

测试中的错误处理

测试错误条件:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_success() {
        let result = divide(10.0, 2.0);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), 5.0);
    }

    #[test]
    fn test_error() {
        let result = divide(10.0, 0.0);
        assert!(result.is_err());
    }

    #[test]
    #[should_panic(expected = "Division by zero")]
    fn test_panic() {
        panic!("Division by zero");
    }

    #[test]
    fn test_with_question_mark() -> Result<(), String> {
        let result = divide(10.0, 2.0)?;
        assert_eq!(result, 5.0);
        Ok(())
    }
}

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("除零错误"))
    } else {
        Ok(a / b)
    }
}

何时使用此技能

使用rust-error-handling当您需要:

  • 使用Result处理可恢复错误
  • 使用Option处理可选值
  • 为您的领域创建自定义错误类型
  • 使用thiserror定义库错误类型
  • 使用anyhow处理应用程序级错误
  • 使用?运算符传播错误
  • 在不同错误类型之间转换
  • 为错误提供上下文
  • 实现全面的错误处理
  • 编写健壮的调试错误消息

最佳实践

  • 使用Result处理可恢复错误,panic处理不可恢复错误
  • 在应用程序中使用anyhow::Context提供上下文
  • 为库错误类型使用thiserror
  • 为自定义错误实现Display和Error trait
  • 使用?运算符进行错误传播
  • 避免在生产代码中使用unwrap/expect
  • 返回错误而不是记录后继续
  • 使错误消息具有可操作性和描述性
  • 使用类型系统在编译时防止错误
  • 在函数文档中记录预期错误

常见陷阱

  • 过度使用unwrap()导致生产环境恐慌
  • 错误消息中未提供足够上下文
  • 不一致地混合panic和Result
  • 创建过于泛化的错误类型(如String)
  • 未实现From进行错误转换
  • 使用let _ = result忽略错误
  • 在更适用Option时使用Result
  • 在match中未处理所有错误变体
  • 创建难以使用的错误类型
  • 忘记将错误传播到调用栈上层

资源