Rust代码示例——18.5.3.错误装箱

还有一种保留原始错误而写出简单代码的方式是将这些错误装箱。有些错误类型只能在运行时才能获知,而不能在编译时提前确定。

使用Box的标准库可以帮助我们实现将任意类型的错误装箱,通过From,将其封装在Box<Error>中。

use std::error;

use std::fmt;


// 类型别名现在使用Box<dyn error::Error>作为错误类型

type Result<T> = std::result::Result<T, Box<dyn error::Error>>;


#[derive(Debug, Clone)]

struct EmptyVec;


impl fmt::Display for EmptyVec {

    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

        write!(f, "invalid first item to double")

    }

}


// 为EmptyVec实现Error trait,这是将其转换为Box<dyn Error>的前提

impl error::Error for EmptyVec {}


fn double_first(vec: Vec<&str>) -> Result<i32> {

    vec.first()

        // 使用ok_or_else和闭包,将EmptyVec转换为Box<dyn Error>

        .ok_or_else(|| EmptyVec.into()) // Into trait将EmptyVec转换为Box<dyn Error>

        .and_then(|s| {

            s.parse::<i32>()

                // 将ParseIntError也转换为Box<dyn Error>

                .map_err(|e| e.into()) // Into trait将ParseIntError转换为Box<dyn Error>

                .map(|i| 2 * i)

        })

}


fn print(result: Result<i32>) {

    match result {

        Ok(n) => println!("The first doubled is {}", n),

        Err(e) => println!("Error: {}", e), // 这里会动态调用实际错误类型的Display实现

    }

}
运行结果
fn main() {

    let numbers = vec!["42", "93", "18"];

    let empty = vec![];

    let strings = vec!["tofu", "93", "18"];


    print(double_first(numbers));

    // 输出:The first doubled is 84

    // 正常流程,返回Ok(84)


    print(double_first(empty));

    // 输出:Error: invalid first item to double

    // 这是EmptyVec的错误消息


    print(double_first(strings));

    // 输出:Error: invalid digit found in string

    // 这是ParseIntError的错误消息(保留了原始错误信息!)

}
有优缺点分析
优点
  1. 保留了原始的错误信息:与之前自定义错误类型丢失信息不同,可以保留ParseIntError的详细错误信息。
  2. 代码简洁:不需要为每种错误定义复杂的转换逻辑
  3. 灵活性:可以处理任何实现了Error接口的错误类型
  4. 易于扩展:添加新的错误类型不需要修改函数签名
缺点
  1. 运行时开销:需要动态分发,有轻微的性能损失
  2. 类型信息丢失:编译时不知道具体的错误类型,只能在运行时通过向下转换来获取具体类型。
  3. 大小增长:Box需要额外的堆分配
技术细节
  1. .into()方法:得益于Rust的自动推导和Fromtrait的实现,EmptyVec.into()和e.into()会自动将具体错误类型转换为Box<dyn Error>。
  2. .ok_or_else:接受一个闭包,只在需要时(遇到None)执行,比ok_or更高效。
  3. trait对象:Box<dyn error::Error>是一个trait对象,可以在运行时包含任何实现了Error trait的类型。
总结

这段代码展示了Rust中一种更灵活的错误处理策略:使用trait对象来统一处理不同类型的错误。

核心价值:

  1. 在简单性和信息保留之间取得了很好的平衡
  2. 允许函数返回多种错误类型而无需定义复杂的自定义错误枚举
  3. 保持了错误的原始信息,便于调试和错误包好

适用场景:

  1. 应用程序的顶层错误处理
  2. 原型开发阶段,错误类型可能频繁变化
  3. 需要快速集成多种库,这些库可能返回不同的错误类型

权衡:

虽然牺牲了编译时类型安全和一些性能,但获得了更大的灵活性和开发便利性。这种模式在Rust生态系统中很常见,特别是在应用程序的开发中。

原文链接地址:Boxing errors - Rust By Example

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值