Rust从入门到实战系列五十八:Option 枚举和其相对于空值的优势

这一部分会分析一个 Option 的案例,Option 是标准库定义的另一个枚举。Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。例如,如果请求一个包含项的列表的第一个值,会得到一个值,如果请求一个空的列表,就什么也不会得到。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。
编程语言的设计经常要考虑包含哪些功能,但考虑排除哪些功能也很重要。Rust 并没有很多其他语言中有的空值功能。空值(Null )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。

I call it my billion-dollar mistake. At that time, I was designing the first comprehensive type
system for references in an object-oriented language. My goal was to ensure that all use of
references should be absolutely safe, with checking performed automatically by the compiler.
But I couldn’t resist the temptation to put in a null reference, simply because it was so easy
to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which
have probably caused a billion dollars of pain and damage in the last forty years.

我称之为我十亿美元的错误。当时,我在为一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的使用都应该是绝对安全的。不过我未能
抵抗住引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数十亿美元的苦痛和伤害。
空值的问题在于当你尝试像一个非空值那样使用一个空值,会出现某种形式的错误。因为空和非空的属性无处不在,非常容易出现这类错误。
然而,空值尝试表达的概念仍然是有意义的:空值是一个因为某种原因目前无效或缺失的值。
问题不在于概念而在于具体的实现。为此,Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是 Option,而且它定义于标准库中,如下:

None,
Some(T),
}

Option 枚举是如此有用以至于它甚至被包含在了 prelude 之中,你不需要将其显式引入作用域。另外,它的成员也是如此,可以不需要 Option:: 前缀来直接使用 Some 和 None。即便如此 Option 也仍是常规的枚举,Some(T) 和 None 仍是 Option 的成员。
语法是一个我们还未讲到的 Rust 功能。它是一个泛型类型参数,第十章会更详细的讲解泛型。目前,所有你需要知道的就是 意味着 Option 枚举的 Some 成员可以包含任意类型的数据,同时每一个用于 T 位置的具体类型使得 Option 整体作为不同的类型。这里是一些包含数字类型和字符串类型Option 值的例子:

let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
# }

some_number 的类型是 Option。some_string 的类型是 Option<&str>,这(与 some_number)是一个不同的类型。因为我们在 Some 成员中指定了值,Rust 可以推断其类型。对于 absent_number,Rust 需要我们指定 Option 整体的类型,因为编译器只通过 None 值无法推断出 Some 成员保存的值的类型。这里我们告诉 Rust 希望 absent_number 是 Option 类型的。
当有一个 Some 值时,我们就知道存在一个值,而这个值保存在 Some 中。当有个 None 值时,在某种意义上,它跟空值具有相同的意义:并没有一个有效的值。那么,Option 为什么就比空值要好呢?
简而言之,因为 Option 和 T(这里 T 可以是任何类型)是不同的类型,编译器不允许像一个肯定有效的值那样使用 Option。例如,这段代码不能编译,因为它尝试将 Option 与 i8 相加:

let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
# }

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

Compiling enums v0.1.0 (file:///projects/enums)
error[E0277]: cannot add `Option<i8>` to `i8`
--> src/main.rs:5:17
|
5 | let sum = x + y;
| ^ no implementation for `i8 + Option<i8>`
|
= help: the trait `Add<Option<i8>>` is not implemented for `i8`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `enums` due to previous error

很好!事实上,错误信息意味着 Rust 不知道该如何将 Option 与 i8 相加,因为它们的类型不同。当在 Rust 中拥有一个像 i8 这样类型的值时,编译器确保它总是有一个有效的值。我们可以自信使用而无需做空值检查。只有当使用 Option(或者任何用到的类型)的时候需要担心可能没有值,而编译器会确保我们在使用值之前处理了为空的情况。
换句话说,在对 Option 进行 T 的运算之前必须将其转换为 T。通常这能帮助我们捕获到空值最常见的问题之一:假设某值不为空但实际上为空的情况。
消除了错误地假设一个非空值的风险,会让你对代码更加有信心。为了拥有一个可能为空的值,你必须要显式的将其放入对应类型的 Option 中。接着,当使用这个值时,必须明确的处理值为空的情况。
只要一个值不是 Option 类型,你就 可以安全的认定它的值不为空。这是 Rust 的一个经过深思熟虑的设计决策,来限制空值的泛滥以增加 Rust 代码的安全性。
那么当有一个 Option 的值时,如何从 Some 成员中取出 T 的值来使用它呢?Option 枚举拥有大量用于各种情况的方法:你可以查看它的文档。熟悉 Option 的方法将对你的 Rust 之旅非常有用。
总的来说,为了使用 Option 值,需要编写处理每个成员的代码。你想要一些代码只当拥有 Some(T)值时运行,允许这些代码使用其中的 T。也希望一些代码在值为 None 时运行,这些代码并没有一个可用的 T 值。match 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值