You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.9 KiB

Enums and Pattern Matching

枚举是 Rust 中的一种类型,也称为 enums。枚举允许你通过枚举变量的可能变体来定义一个类型。首先,我们将定义并使用枚举来展示枚举如何在数据中编码意义。

Option 是 Rust 中内置的枚举类型。Option 表示一个值可以是 T 类型的值,也可以是什么都没有。它有两个可能的变体:Some(T) 和 None

定义枚举

枚举使你能够表示一个值是一组可能的值之一。关键字为enum。

image-20230106142149652

直接使用枚举

直接将数据附加到枚举的变体,故不再需要结构体。

我们定义的每个枚举变体的名称也成为一个构造枚举实例的函数。 也就是说,IpAddr::V4()是一个带有 String 参数并返回 IpAddr 类型实例的函数调用。我们在定义枚举时自动获得了这个构造函数。

image-20230106145517117

使用枚举而不是结构体还有另一个优点:每个变体都可以具有不同类型和数量的关联数据。 版本四型 IP 地址将始终具有四个数字组件,其值将在 0 到 255 之间。 如果我们想将 V4 地址存储为四个 u8 值,但仍想使用一个 String 值表示 V6 地址,则无法使用结构体(硬用会很麻烦)。 枚举很容易处理这种情况:

image-20230106153120695

标准库提供的IpAddr(地址

#![allow(unused)]
fn main() {
	struct Ipv4Addr {
    	// --snip--
	}

	struct Ipv6Addr {
    	// --snip--
	}

	enum IpAddr {
    	V4(Ipv4Addr),
    	V6(Ipv6Addr),
	}
}

  1. #![allow(unused)] 是 Rust 的一个编译器属性。这个属性告诉编译器在编译时忽略未使用的变量、函数等的警告。

  2. 此代码说明您可以将任何类型的数据放入枚举变体中:例如,字符串、数字类型或结构。你甚至可以包括另一个枚举

  3. 即使标准库包含的IpAddr,我们仍然可以创建和使用我们自己的定义而不会发生冲突,因为我们没有将标准库的定义纳入我们的范围

Message枚举类型

这个枚举有四种不同类型的变体:

  • Quit根本没有与之关联的数据。
  • Move像结构一样命名字段。
  • Write包括一个String.
  • ChangeColor包括三个i32值。

image-20230106161000110

只是枚举不使用 struct关键字并且所有变体都在类型下组合在一起Message 。以下结构可以包含与前面的枚举变体相同的数据:

struct QuitMessage; // unit struct
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct

fn main() {}

枚举和结构之间还有一个相似之处:正如我们能够使用 定义结构上的方法一样impl(有点像多态了)。枚举在某些情况下可能是结构体的有用替代品,特别是当你想定义一个只能有一个固定集合值的类型时。 但是,枚举并不总是最佳选择。 在表示更复杂的数据结构时,结构体通常更灵活,并且更为适当。 使用最适合解决的问题的数据结构通常是一个好主意。

image-20230106165212175

Option枚举

Option 标准库中的枚举的。Option 类型编码了一种非常常见的场景,即值可能是某些东西,也可能是没有任何东西。

Rust 没有许多其他语言都有的 null 功能。rust没有空值,但是它有一个枚举,可以编码值存在或缺失的概念。 这个枚举是Option,

好处是:如果请求包含项目的列表中的第一个项目,则将获得一个值。如果请求空列表的第一个项目,则将获得 nothing。在类型系统中表达这个概念意味着编译器可以检查您是否处理了所有应该处理的情况;这个功能可以防止其他编程语言中非常常见的错误。

Option标准库定义。是一个泛型类型参数,可以保存任何类型的一个数据。

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

image-20230108210445242

此例子中Option具体类型。

image-20230108213055337

Option 和 null 的一个很重要的区别在于,Option 是在编译时进行类型检查的。这意味着如果你尝试将一个 Option 当做 T 来使用,你会在编译时得到一个错误。这是因为 Option 和 T 是不同的类型,所以编译器会提醒你处理这种情况。但是在其他语言中,null 可以随意转换成其他类型,如果你尝试将 null 当做某个类型使用,那么你可能会在运行时得到一个错误,这就是 Tony Hoare 所说的“十亿美元的错误”。Option 可以在编译时帮助你避免这种错误。

image-20230108213602623

Rust 中无法将 i8 和 Option 相加,因为它们是不同的类型。当我们在 Rust 中有一个 i8 类型的值时,编译器会确保我们始终拥有一个有效值。我们可以放心地使用该值,而无需在使用之前检查 null。只有当我们有一个 Option(或我们正在处理的任何类型的值)时,我们才需要担心可能没有值,编译器会确保我们在使用该值之前处理该情况

执行 T 操作之前,你必须将 Option 转换为 T。通常,这可以帮助捕获 null 最常见的问题之一:假设某物不是 null,但实际上是 null。

消除假设非 null 值的风险有助于让你对代码更有信心。为了有一个可能为 null 的值,你必须通过将该值的类型设置为 Option 显式选择。然后,当你使用该值时,你必须显式处理值为 null 的情况。所有类型不是 Option 的值都可以安全地假设该值不为 null。这是 Rust 为了限制 null 的普遍性并提高 Rust 代码安全性而做出的一个有意的设计决策

  1. 可以使用 match 表达式来处理 Option 的每个变体。

  2. unwrap来尝试获取 Option 的内部值,如果是 None 变体,unwrap 会 panic

  3. expect 与 unwrap 类似,但允许你指定当 Option 为 None 变体时 panic 时显示的错误信息。

image-20230108214914594