Rust 枚举 Enum

文章目录

  • Rust 枚举 Enum
    • 枚举值
    • Option 枚举用于处理空值

Rust 枚举 Enum

枚举 (enum 或 enumeration) 允许你通过列举可能的成员来定义一个枚举类型;枚举出所有的可能值,例如扑克牌花色:

enum PokerSuit {
  Clubs,
  Spades,
  Diamonds,
  Hearts,
}

因为枚举值只可能是其中某一个成员,可以把它们当作相同的类型进行传参。

枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。

枚举值

现在来创建 PokerSuit 枚举类型的两个成员实例:

let heart = PokerSuit::Hearts;
let diamond = PokerSuit::Diamonds;

我们通过 :: 操作符来访问 PokerSuit 下的具体成员,从代码可以清晰看出,heart 和 diamond 都是 PokerSuit 枚举类型的,接着可以定义一个函数来使用它们:

fn main() {
    let heart = PokerSuit::Hearts;
    let diamond = PokerSuit::Diamonds;

    print_suit(heart);
    print_suit(diamond);
}

fn print_suit(card: PokerSuit) {
    println!("{:?}",card);
}

print_suit函数的参数类型是 PokerSuit,因此我们可以把 heartdiamond 传给它,虽然 heart 是基于 PokerSuit 下的 Hearts 成员实例化的,但是它是货真价实的 PokerSuit 枚举类型。

接下来,我们想让扑克牌变得更加实用,那么需要给每张牌赋予一个值:A(1)-K(13),这样再加上花色,就是一张真实的扑克牌了,例如红心 A。

先用结构体来实现:

enum PokerSuit {
    Clubs,
    Spades,
    Diamonds,
    Hearts,
}

struct PokerCard {
    suit: PokerSuit,
    value: u8
}

fn main() {
   let c1 = PokerCard {
       suit: PokerSuit::Clubs,
       value: 1,
   };
   let c2 = PokerCard {
       suit: PokerSuit::Diamonds,
       value: 12,
   };
}

这段代码很好的完成了它的使命,通过结构体 PokerCard 来代表一张牌,结构体的 suit 字段表示牌的花色,类型是 PokerSuit 枚举类型,value 字段代表扑克牌的数值。

简洁方式来实现:

enum PokerCard {
    Clubs(u8),
    Spades(u8),
    Diamonds(u8),
    Hearts(u8),
}

fn main() {
   let c1 = PokerCard::Spades(5);
   let c2 = PokerCard::Diamonds(13);
}

直接将数据信息关联到枚举成员上

不仅如此,同一个枚举类型下的不同成员还能持有不同的数据类型,例如让某些花色打印 1-13 的字样,另外的花色打印上 A-K 的字样:

enum PokerCard {
    Clubs(u8),
    Spades(u8),
    Diamonds(char),
    Hearts(char),
}

fn main() {
   let c1 = PokerCard::Spades(5);
   let c2 = PokerCard::Diamonds('A');
}

何类型的数据都可以放入枚举成员中: 例如字符串、数值、结构体甚至另一个枚举

与结构体相比
由于每个结构体都有自己的类型,因此我们无法在需要同一类型的地方进行使用,例如某个函数它的功能是接受消息并进行发送,那么用枚举的方式,就可以接收不同的消息,但是用结构体,该函数无法接受 4 个不同的结构体作为参数。

而且从代码规范角度来看,枚举的实现更简洁,代码内聚性更强,不像结构体的实现,分散在各个地方。

Option 枚举用于处理空值

在其它编程语言中,往往都有一个 null 关键字,该关键字用于表明一个变量当前的值为空(不是零值,例如整形的零值是 0),也就是不存在值。当你对这些 null 进行操作时,例如调用一个方法,就会直接抛出null 异常,导致程序的崩溃,因此我们在编程时需要格外的小心去处理这些 null 空值。

尽管如此,空值的表达依然非常有意义,因为空值表示当前时刻变量的值是缺失的。有鉴于此,Rust 吸取了众多教训,决定抛弃 null,而改为使用 Option 枚举变量来表述这种结果。

Option 枚举包含两个成员,一个成员表示含有值:Some(T), 另一个表示没有值:`Non,定义如下:

enum Option {
    Some(T),
    None,
}

其中 T 是泛型参数,Some(T)表示该枚举成员的数据类型是 T,换句话说,Some 可以包含任何类型的数据。

Option 枚举是如此有用以至于它被包含在了 prelude(prelude 属于 Rust 标准库,Rust 会将最常用的类型、函数等提前引入其中,省得我们再手动引入)之中,你不需要将其显式引入作用域。另外,它的成员 Some 和 None 也是如此,无需使用 Option:: 前缀就可直接使用 Some 和 None。总之,不能因为 Some(T) 和 None 中没有 Option:: 的身影,就否认它们是 Option 下的卧龙凤雏。

再来看以下代码:

let some_number = Some(5);
let some_string = Some("a string");

let absent_number: Option = None;

如果使用 None 而不是 Some,需要告诉 Rust Option 是什么类型的,因为编译器只通过 None 值无法推断出 Some 成员保存的值的类型。

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

简而言之,因为 Option 和 T(这里 T 可以是任何类型)是不同的类型,例如,这段代码不能编译,因为它尝试将 Option(Option) 与 i8(T) 相加:

let x: i8 = 5;
let y: Option = Some(5);

let sum = x + y;

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

error[E0277]: the trait bound `i8: std::ops::Add>` is
not satisfied
 -->
  |
5 |     let sum = x + y;
  |                 ^ no implementation for `i8 + std::option::Option`
  |

很好!事实上,错误信息意味着 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 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。

这里先简单看一下 match 的大致模样,在模式匹配中,我们会详细讲解:

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

let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);

plus_one 通过 match 来处理不同 Option 的情况。

你可能感兴趣的:(Rust,rust,开发语言,后端)