改进rust代码的35种具体方法-类型(三)

接上一篇的的文章

之前阐述了enum的优点,并展示了match表达式如何迫使程序员考虑所有可能性;本项探讨了您应该避免match表达式的情况——至少明确地。

还介绍了Rust标准库提供的两个无处不在的enum

  • Option<T>表示(T型)值可能存在也可能不存在
  • Result<T, E>,当返回值(类型为T)的操作可能无法成功,而可能返回错误(类型为E)时。

对于这些特定的enum显式使用match通常会导致代码没有它需要的那么紧凑,而且不是惯用的Rust。

match不必要的第一种情况是,只有值是相关的,并且没有值(以及任何相关错误)可以忽略。

    struct S {
        field: Option<i32>,
    }

    let s = S { field: Some(42) };
    match &s.field {
        Some(i) => println!("field is {}", i),
        None => {}
    }

在这种情况下,if let表达式短一行,更重要的是,更清晰:

    if let Some(i) = &s.field {
        println!("field is {}", i);
    }

然而,大多数时候,缺少值和相关的错误将是程序员必须处理的事情。设计软件来应对故障路径是困难的,其中大部分是基本的复杂性,任何句法支持都无法帮助——特别是决定如果操作失败会发生什么。

在某些情况下,正确的决定是执行鸵鸟动作,并明确不应对失败。用明确的match做这件事将是不必要的冗长:

    let result = std::fs::File::open("/etc/passwd");
    let f = match result {
        Ok(f) => f,
        Err(_e) => panic!("Failed to open /etc/passwd!"),
    };

Option和Result都提供了一对方法来提取它们的内部值和panic!如果它不存在:打开包装并等待。后者允许对失败时的错误消息进行个性化处理,但无论哪种情况,生成的代码都更短、更简单——错误处理委托给.unwrap() 后缀 (但仍然存在)。

比如读取文件:

    let f = std::fs::File::open("/etc/passwd").unwrap();

但是要清楚:这些帮助函数仍然会出错!所以选择使用它们和选择恐慌是一样的。

然而,在许多情况下,错误处理的正确决定是将决定推迟给其他人。在编写库时尤其如此,其中代码可用于库作者无法预见的各种不同环境中。为了使其他人的工作更轻松,更喜欢Result而不是Option,即使这可能涉及不同错误类型之间的转换。

Result还有一个[#must_use]属性,可以引导库用户朝着正确的方向前进——如果使用返回Result的代码忽略它,编译器将生成警告:

warning: unused `Result` that must be used
  --> transform/src/main.rs:32:5
   |
32 |     f.set_len(0); // Truncate the file
   |     ^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_must_use)]` on by default
   = note: this `Result` may be an `Err` variant, which should be handled

明确使用match允许错误传播,但以一些可见的样板:

    pub fn find_user(username: &str) -> Result<UserId, std::io::Error> {
        let f = match std::fs::File::open("/etc/passwd") {
            Ok(f) => f,
            Err(e) => return Err(e),
        };
        // ...
    }

减少样板的关键成分是Rust的问号运算符?。这块句法糖负责在单个字符中匹配Err臂和return Err(...)表达式:

    pub fn find_user(username: &str) -> Result<UserId, std::io::Error> {
        let f = std::fs::File::open("/etc/passwd")?;
        // ...
    }

Rust的新手有时会发现这令人不安:乍一看很难发现问号,导致对代码可能如何工作感到不安。然而,即使只有一个字符,类型系统仍然在工作,确保涵盖相关类型中表达的所有可能性——让程序员专注于主线代码路径,而不会分心。

此外,这些明显的方法调用通常没有成本:它们都是标记为#[inline]的通用函数,因此生成的代码通常会编译为与手动版本相同的机器代码。

这两个因素加在一起意味着您应该更喜欢OptionResult变换而不是显式match表达式

在前面的示例中,错误类型排列:内部和外部方法都表示错误为std::io::Error。情况往往并非如此;一个函数可能会从各种不同的子库中积累错误,每个子库都使用不同的错误类型。

一般错误映射在之后中讨论;目前,请注意手动映射:

    pub fn find_user(username: &str) -> Result<UserId, String> {
        let f = match std::fs::File::open("/etc/passwd") {
            Ok(f) => f,
            Err(e) => {
                return Err(format!("Failed to open password file: {:?}", e))
            }
        };
        // ...
    }

可以用.map_err()转换更简洁、更惯用地表达:

    pub fn find_user(username: &str) -> Result<UserId, String> {
        let f = std::fs::File::open("/etc/passwd")
            .map_err(|e| format!("Failed to open password file: {:?}", e))?;
        // ...
    }

更好的是,即使这样也可能没有必要——如果可以通过From标准特征的实现从内部错误类型创建外部错误类型,那么编译器将自动执行转换,而无需调用.map_err()

这类转变更广泛地推广。问号运算符是一个大锤子;使用OptionResult类型的转换方法,将它们操纵到可以成为钉子的位置。

标准库提供了各种各样的转换方法来实现这一点,如下图所示。与之后写的文章一致,方法会引起恐慌!用红色突出显示。

                 

图表中没有涵盖的一个常见情况是处理参考资料。例如,考虑一个可选地包含一些数据的结构。

    struct InputData {
        payload: Option<Vec<u8>>,
    }

如果天真地尝试引用,此struct上试图将有效负载传递给带有签名(&[u8]) -> Vec<u8>的加密函数的方法会失败:

    impl InputData {
        pub fn encrypted(&self) -> Vec<u8> {
            encrypt(&self.payload.unwrap_or(vec![]))
        }
    }

error[E0507]: cannot move out of `self.payload` which is behind a shared reference
  --> transform/src/main.rs:62:22
   |
62 |             encrypt(&self.payload.unwrap_or(vec![]))
   |                      ^^^^^^^^^^^^ move occurs because `self.payload` has type `Option<Vec<u8>>`, which does not implement the `Copy` trait
   |
help: consider borrowing the `Option`'s content
   |
62 |             encrypt(&self.payload.as_ref().unwrap_or(vec![]))
   |                                  +++++++++

错误消息准确地描述了使代码工作所需的内容,即Option上的as_ref()方法1。此方法将引用转换为Option转换为引用Option

        pub fn encrypted(&self) -> Vec<u8> {
            encrypt(self.payload.as_ref().unwrap_or(&vec![]))
        }

总结一下:

  • 习惯OptionResult的转换,更喜欢Result而不是Option
    • 当转换涉及引用时,根据需要使用.as_ref()
  • 优先使用它们进行显式match操作。
  • 特别是,使用它们将结果类型转换为形式,其中?操作员适用。

  • 20
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值