|
|
@ -14,10 +14,10 @@ enum PokerSuit {
|
|
|
|
|
|
|
|
|
|
|
|
再回到之前创建的`PokerSuit`,扑克总共有四种花色,而这里我们枚举出所有的可能值,这也正是`枚举`名称的由来。
|
|
|
|
再回到之前创建的`PokerSuit`,扑克总共有四种花色,而这里我们枚举出所有的可能值,这也正是`枚举`名称的由来。
|
|
|
|
|
|
|
|
|
|
|
|
任何一张扑克,它的花色肯定会落在四种花色中,而且也只会落在其中一个花色上,这种特性非常适合枚举的使用,因为**`枚举值`**只可能是其中一个成员。抽象来看,四种花色尽管是不同的花色,但是它们都是扑克花色这个概念,因此当某个函数处理扑克花色时,可以把它们当作相同的类型进行传参。
|
|
|
|
任何一张扑克,它的花色肯定会落在四种花色中,而且也只会落在其中一个花色上,这种特性非常适合枚举的使用,因为**枚举值**只可能是其中一个成员。抽象来看,四种花色尽管是不同的花色,但是它们都是扑克花色这个概念,因此当某个函数处理扑克花色时,可以把它们当作相同的类型进行传参。
|
|
|
|
|
|
|
|
|
|
|
|
细心的读者应该注意到,我们对之前的`枚举类型`和`枚举值`进行了重点标注,这是因为对于新人来说容易混淆相应的概念,总而言之:
|
|
|
|
细心的读者应该注意到,我们对之前的`枚举类型`和`枚举值`进行了重点标注,这是因为对于新人来说容易混淆相应的概念,总而言之:
|
|
|
|
**枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实现。**
|
|
|
|
**枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。**
|
|
|
|
|
|
|
|
|
|
|
|
## 枚举值
|
|
|
|
## 枚举值
|
|
|
|
现在来创建`PokerSuit`枚举类型的两个成员实例:
|
|
|
|
现在来创建`PokerSuit`枚举类型的两个成员实例:
|
|
|
@ -89,7 +89,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
|
|
直接将数据信息关联到枚举成员上,直接省去近一半的代码,这种实现漂亮不?
|
|
|
|
直接将数据信息关联到枚举成员上,直接省去近一半的代码,这种实现漂亮不?
|
|
|
|
|
|
|
|
|
|
|
|
而且不仅仅如此,同一个枚举类型下的不同成员还能持有不同的类型,例如让部分花色打印1-13的字样,另外花色打印上A-K的字样:
|
|
|
|
而且不仅仅如此,同一个枚举类型下的不同成员还能持有不同的类型,例如让部分花色打印`1-13`的字样,另外花色打印上`A-K`的字样:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
enum PokerCard {
|
|
|
|
enum PokerCard {
|
|
|
|
Clubs(u8),
|
|
|
|
Clubs(u8),
|
|
|
@ -124,7 +124,7 @@ enum IpAddr {
|
|
|
|
```
|
|
|
|
```
|
|
|
|
该例子跟我们之前的扑克牌很像,只不过枚举成员包含的类型更复杂了,变成了结构体:分别通过`Ipv4Addr`和`Ipv4Addr`来定义两种不同的IP数据。
|
|
|
|
该例子跟我们之前的扑克牌很像,只不过枚举成员包含的类型更复杂了,变成了结构体:分别通过`Ipv4Addr`和`Ipv4Addr`来定义两种不同的IP数据。
|
|
|
|
|
|
|
|
|
|
|
|
从这些例子可以看出,**任何类型的数据都可以放入枚举成员中**: 例例如字符串、数值、结构体甚至另一个枚举。
|
|
|
|
从这些例子可以看出,**任何类型的数据都可以放入枚举成员中**: 例如字符串、数值、结构体甚至另一个枚举。
|
|
|
|
|
|
|
|
|
|
|
|
增加一些挑战?先看以下代码:
|
|
|
|
增加一些挑战?先看以下代码:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
@ -165,9 +165,9 @@ struct ChangeColorMessage(i32, i32, i32); // 元组结构体
|
|
|
|
|
|
|
|
|
|
|
|
## 同一化类型
|
|
|
|
## 同一化类型
|
|
|
|
|
|
|
|
|
|
|
|
最后,再用一个实际项目中的设计考虑,来结束枚举类型的语法学习。
|
|
|
|
最后,再用一个实际项目中的简化片段,来结束枚举类型的语法学习。
|
|
|
|
|
|
|
|
|
|
|
|
例如我们有一个web服务,需要接受用户的长连接,假设连接有两种:TcpStream和TlsStream,但是我们希望对这两个连接的处理流程相同,也就是用同一个函数来处理这两个连接,代码如下:
|
|
|
|
例如我们有一个web服务,需要接受用户的长连接,假设连接有两种:`TcpStream`和`TlsStream`,但是我们希望对这两个连接的处理流程相同,也就是用同一个函数来处理这两个连接,代码如下:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
func new (stream: TcpStream) {
|
|
|
|
func new (stream: TcpStream) {
|
|
|
|
let mut s = stream;
|
|
|
|
let mut s = stream;
|
|
|
@ -191,14 +191,15 @@ enum Websocket {
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Option枚举用于处理空值
|
|
|
|
## Option枚举用于处理空值
|
|
|
|
在其它编程语言中,往往都有一个`null`关键字,该关键字用于表明一个变量当前的值为空(不是零值,例如整形的零值是0),也就是不存在值。当你对这些`null`进行操作时,例如调用一个方法,就会直接抛出异常,导致程序的崩溃,因此我们在编程时需要格外的小心去处理这些`null`空值。
|
|
|
|
在其它编程语言中,往往都有一个`null`关键字,该关键字用于表明一个变量当前的值为空(不是零值,例如整形的零值是0),也就是不存在值。当你对这些`null`进行操作时,例如调用一个方法,就会直接抛出null异常,导致程序的崩溃,因此我们在编程时需要格外的小心去处理这些`null`空值。
|
|
|
|
|
|
|
|
|
|
|
|
> Tony Hoare,null的发明者,曾经说过有非常有名的话
|
|
|
|
> Tony Hoare,null的发明者,曾经说过有非常有名的话
|
|
|
|
>
|
|
|
|
>
|
|
|
|
> 我称之为我十亿美元的错误。当时,我在使用一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的使用都应该是绝对安全的。不过在设计过程中,我未能抵抗住诱惑,引入了空引用的概念,因为它非常容易实现。就是因为这个决策,引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数十亿美元的苦痛和伤害。
|
|
|
|
> 我称之为我十亿美元的错误。当时,我在使用一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的使用都应该是绝对安全的。不过在设计过程中,我未能抵抗住诱惑,引入了空引用的概念,因为它非常容易实现。就是因为这个决策,引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数十亿美元的苦痛和伤害。
|
|
|
|
|
|
|
|
|
|
|
|
然后空值的表达依然非常有意义,因为空值表示当前时刻变量的值是缺失的。因此,Rust吸取了众多教训,决定抛弃`null`,而改为使用`Option`枚举变量来表述这种结果:
|
|
|
|
尽管如此,空值的表达依然非常有意义,因为空值表示当前时刻变量的值是缺失的。有鉴于此,Rust吸取了众多教训,决定抛弃`null`,而改为使用`Option`枚举变量来表述这种结果。
|
|
|
|
**一个变量要么有值:`Some(T)`, 要么为空: `None`,定义如下:
|
|
|
|
|
|
|
|
|
|
|
|
`Option`枚举包含两个成员,一个成员表示含有值:`Some(T)`, 另一个表示没有值: `None`,定义如下:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
enum Option<T> {
|
|
|
|
enum Option<T> {
|
|
|
|
Some(T),
|
|
|
|
Some(T),
|
|
|
@ -208,7 +209,7 @@ enum Option<T> {
|
|
|
|
|
|
|
|
|
|
|
|
其中`T`是泛型参数,`Some(T)`表示该枚举成员的数据类型是`T`, 换句话说,`Some`可以包含任何类型的数据。
|
|
|
|
其中`T`是泛型参数,`Some(T)`表示该枚举成员的数据类型是`T`, 换句话说,`Some`可以包含任何类型的数据。
|
|
|
|
|
|
|
|
|
|
|
|
`Option<T>` 枚举是如此有用以至于它甚至被包含在了`prelude`(Rust会将最常用的类型、函数等提前引入进来,避免我们再手动引入)之中,你不需要将其显式引入作用域。另外,它的成员也是如此,可以不需要`Option::`前缀来直接使用`Some` 和 `None`。即便如此`Option<T>` 也仍是常规的枚举,`Some(T)` 和 `None` 仍是 `Option<T>` 的成员。
|
|
|
|
`Option<T>` 枚举是如此有用以至于它甚至被包含在了`prelude`(Rust会将最常用的类型、函数等提前引入进来,避免我们再手动引入)之中,你不需要将其显式引入作用域。另外,它的成员也是如此,无需使用`Option::`前缀就可直接使用`Some` 和 `None`。总之,不能因为`Some(T)`和`None`中没有`Option::`的身影,就否认它们是`Option`下的卧龙凤雏。
|
|
|
|
|
|
|
|
|
|
|
|
再来看以下代码:
|
|
|
|
再来看以下代码:
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
@ -255,7 +256,7 @@ not satisfied
|
|
|
|
|
|
|
|
|
|
|
|
总的来说,为了使用 `Option<T>` 值,需要编写处理每个成员的代码。你想要一些代码只当拥有 `Some(T)` 值时运行,允许这些代码使用其中的 `T`。也希望一些代码在值为 `None` 时运行,这些代码并没有一个可用的 `T` 值。`match` 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。
|
|
|
|
总的来说,为了使用 `Option<T>` 值,需要编写处理每个成员的代码。你想要一些代码只当拥有 `Some(T)` 值时运行,允许这些代码使用其中的 `T`。也希望一些代码在值为 `None` 时运行,这些代码并没有一个可用的 `T` 值。`match` 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。
|
|
|
|
|
|
|
|
|
|
|
|
这里先简单看一下`match`的大致模样,在[模式匹配](../match-pattern.md)中,我们会详细讲解:
|
|
|
|
这里先简单看一下`match`的大致模样,在[模式匹配](../match-pattern/intro.md)中,我们会详细讲解:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
fn plus_one(x: Option<i32>) -> Option<i32> {
|
|
|
|
fn plus_one(x: Option<i32>) -> Option<i32> {
|
|
|
|