基础篇-Option

  • 木头
  • 更新于 2023-02-24 14:09
  • 阅读 1941

Rust处理空值

Option 是标准库定义的另一个枚举。Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。

例如,如果请求一个非空列表的第一项,会得到一个值,如果请求一个空的列表,就什么也不会得到。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。

编程语言的设计经常要考虑包含哪些功能,但考虑排除哪些功能也很重要。Rust 并没有很多其他语言中有的空值功能。空值(Null )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。

为此,Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是 Option<T>,而且它定义于标准库中,如下:

enum Option<T> {
    None,
    Some(T),
}

Option<T> 枚举是如此有用以至于它甚至被包含在了 prelude之中,你不需要将其显式引入作用域。另外,它的成员也是如此,可以不需要 Option::前缀来直接使用SomeNone。即便如此 Option<T>也仍是常规的枚举,Some(T)None 仍是 Option<T> 的成员。

&lt;T>语法是一个我们还未讲到的Rust 功能。它是一个泛型类型参数。目前,所有你需要知道的就是 <T> 意味着 Option 枚举的 Some 成员可以包含任意类型的数据,同时每一个用于T位置的具体类型使得Option&lt;T>整体作为不同的类型。这里是一些包含数字类型和字符串类型 Option 值的例子:

let some_number = Some(5);
let some_char = Some('e');

// 空值
let absent_number: Option&lt;i32> = None;

some_number 的类型是Option&lt;i32>some_char 的类型是 Option&lt;char>,这(与 some_number)是一个不同的类型。因为我们在 Some 成员中指定了值,Rust 可以推断其类型。对于 absent_numberRust 需要我们指定 Option 整体的类型,因为编译器只通过None 值无法推断出 Some成员保存的值的类型。这里我们告诉Rust希望 absent_numberOption&lt;i32> 类型的。

当有一个 Some 值时,我们就知道存在一个值,而这个值保存在 Some中。当有个None 值时,在某种意义上,它跟空值具有相同的意义:并没有一个有效的值。那么,Option&lt;T> 为什么就比空值要好呢?

简而言之,因为 Option&lt;T>T(这里 T 可以是任何类型)是不同的类型,编译器不允许像一个肯定有效的值那样使用 Option&lt;T>。例如,这段代码不能编译,因为它尝试将 Option&lt;i8>i8 相加:

fn main() {
    let x: i8 = 5;
    let y: Option&lt;i8> = Some(5);

    let sum = x + y;
}

如果运行这些代码,将得到类似这样的错误信息:

$ cargo run
   Compiling variables v0.1.0 (/projects/variables)
error[E0277]: cannot add `Option&lt;i8>` to `i8`
 --> src/main.rs:5:17
  |
5 |     let sum = x + y;
  |                 ^ no implementation for `i8 + Option&lt;i8>`
  |
  = help: the trait `Add&lt;Option&lt;i8>>` is not implemented for `i8`
  = help: the following other types implement trait `Add&lt;Rhs>`:
            &lt;&'a i8 as Add&lt;i8>>
            &lt;&i8 as Add&lt;&i8>>
            &lt;i8 as Add&lt;&i8>>
            &lt;i8 as Add>

For more information about this error, try `rustc --explain E0277`.
error: could not compile `variables` due to previous error

错误信息意味着 Rust 不知道该如何将 Option&lt;i8>i8 相加,因为它们的类型不同。当在 Rust中拥有一个像 i8 这样类型的值时,编译器确保它总是有一个有效的值。我们可以自信使用而无需做空值检查。只有当使用 Option&lt;i8>(或者任何用到的类型)的时候需要担心可能没有值,而编译器会确保我们在使用值之前处理了为空的情况。

匹配 Option<T<T>>

比如我们想要编写一个函数,它获取一个Option&lt;i32> ,如果其中含有一个值,将其加一。如果其中没有值,函数应该返回 None值,而不尝试执行任何操作:

fn plus_one(x: Option&lt;i32>) -> Option&lt;i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

fn main() {
    let six = plus_one(Some(5));
    let none = plus_one(None);

    println!("six = {:?} , none = {:?}", six, none);
}

运行,输出:

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/variables`
six = Some(6) , none = None

if let 控制流

if let 语法让我们以一种不那么冗长的方式结合 iflet,来处理只匹配一个模式的值而忽略其他模式的情况:

fn plus_one(x: Option&lt;i32>) -> Option&lt;i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

fn main() {
    // 只处理有值的情况
    if let Some(v) = plus_one(Some(1)) {
        println!("v = {}", v);
    }

    // 考虑None的情况
    if let Some(v) = plus_one(None) {
        println!("v = {}", v);
    } else {
        println!("None");
    }
}

if let语法获取通过等号分隔的一个模式和一个表达式。它的工作方式与 match相同,这里的表达式对应 match而模式则对应第一个分支。在这个例子中,模式是 Some(v)v 绑定为 Some中的值。接着可以在 if let 代码块中使用 v 了,就跟在对应的 match 分支中一样。模式不匹配时 if let 块中的代码不会执行。

使用 if let 意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去 match 强制要求的穷尽性检查。matchif let之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。

  • 原创
  • 学分: 2
  • 分类: Rust
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
125 订阅 31 篇文章

0 条评论

请先 登录 后评论
木头
木头
0xC020...10cf
江湖只有他的大名,没有他的介绍。