枚举是 Rust 中的一种类型,也称为 enums。枚举允许你通过枚举变量的可能变体来定义一个类型。首先,我们将定义并使用枚举来展示枚举如何在数据中编码意义。
Option 是 Rust 中内置的枚举类型。Option<T> 表示一个值可以是 T 类型的值,也可以是什么都没有。它有两个可能的变体:Some(T) 和 None
定义枚举
枚举使你能够表示值是一组可能的值之一。关键字为enum。
直接使用枚举
直接将数据附加到枚举的变体,故不再需要结构体。
我们定义的每个枚举变体的名称也成为一个构造枚举实例的函数。 也就是说,IpAddr::V4()是一个带有 String 参数并返回 IpAddr 类型实例的函数调用。我们在定义枚举时自动获得了这个构造函数。
使用枚举而不是结构体还有另一个优点:每个变体都可以具有不同类型和数量的关联数据。 版本四型 IP 地址将始终具有四个数字组件,其值将在 0 到 255 之间。 如果我们想将 V4 地址存储为四个 u8 值,但仍想使用一个 String 值表示 V6 地址,则无法使用结构体(硬用会很麻烦)。 枚举很容易处理这种情况:
标准库提供的IpAddr()
fn main() {
struct Ipv4Addr {
// --snip--
}
struct Ipv6Addr {
// --snip--
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
}
-
#![allow(unused)] 是 Rust 的一个编译器属性。这个属性告诉编译器在编译时忽略未使用的变量、函数等的警告。
-
此代码说明您可以将任何类型的数据放入枚举变体中:例如,字符串、数字类型或结构。你甚至可以包括另一个枚举!
-
即使标准库包含的
IpAddr
,我们仍然可以创建和使用我们自己的定义而不会发生冲突,因为我们没有将标准库的定义纳入我们的范围。
Message枚举类型
这个枚举有四种不同类型的变体:
-
Quit
根本没有与之关联的数据。 -
Move
像结构一样命名字段。 -
Write
包括一个String
. -
ChangeColor
包括三个i32
值。
只是枚举不使用 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
(有点像多态了)。枚举在某些情况下可能是结构体的有用替代品,特别是当你想定义一个只能有一个固定集合值的类型时。 但是,枚举并不总是最佳选择。 在表示更复杂的数据结构时,结构体通常更灵活,并且更为适当。 使用最适合解决的问题的数据结构通常是一个好主意。
Option枚举
Option 标准库中的枚举的。Option 类型编码了一种非常常见的场景,即值可能是某些东西,也可能是没有任何东西。
Rust 没有许多其他语言都有的 null 功能。rust没有空值,但是它有一个枚举,可以编码值存在或缺失的概念。 这个枚举是Option<T>,
好处是:如果请求包含项目的列表中的第一个项目,则将获得一个值。如果请求空列表的第一个项目,则将获得 nothing。在类型系统中表达这个概念意味着编译器可以检查您是否处理了所有应该处理的情况;这个功能可以防止其他编程语言中非常常见的错误。
Option<T>标准库定义。<T>是一个泛型类型参数,可以保存任何类型的一个数据。
pub enum Option<T> {
None,
Some(T),
}
此例子中Option<T>具体类型。
Option<T> 和 null 的一个很重要的区别在于,Option<T> 是在编译时进行类型检查的。这意味着如果你尝试将一个 Option<T> 当做 T 来使用,你会在编译时得到一个错误。这是因为 Option<T> 和 T 是不同的类型,所以编译器会提醒你处理这种情况。但是在其他语言中,null 可以随意转换成其他类型,如果你尝试将 null 当做某个类型使用,那么你可能会在运行时得到一个错误,这就是 Tony Hoare 所说的“十亿美元的错误”。Option<T> 可以在编译时帮助你避免这种错误。
Rust 中无法将 i8 和 Option<i8> 相加,因为它们是不同的类型。当我们在 Rust 中有一个 i8 类型的值时,编译器会确保我们始终拥有一个有效值。我们可以放心地使用该值,而无需在使用之前检查 null。只有当我们有一个 Option<i8>(或我们正在处理的任何类型的值)时,我们才需要担心可能没有值,编译器会确保我们在使用该值之前处理该情况
执行 T 操作之前,你必须将 Option<T> 转换为 T。通常,这可以帮助捕获 null 最常见的问题之一:假设某物不是 null,但实际上是 null。
消除假设非 null 值的风险有助于让你对代码更有信心。为了有一个可能为 null 的值,你必须通过将该值的类型设置为 Option<T> 显式选择。然后,当你使用该值时,你必须显式处理值为 null 的情况。所有类型不是 Option<T> 的值都可以安全地假设该值不为 null。这是 Rust 为了限制 null 的普遍性并提高 Rust 代码安全性而做出的一个有意的设计决策
-
可以使用 match 表达式来处理 Option<T> 的每个变体。
-
unwrap来尝试获取 Option<T> 的内部值,如果是 None 变体,unwrap 会 panic
-
expect 与
unwrap
类似,但允许你指定当 Option<T> 为 None 变体时 panic 时显示的错误信息。
match,它允许你将值与一系列模式进行比较,然后根据匹配的模式执行代码。模式可以由文字值、变量名、通配符和许多其他内容组成。
对于 if,表达式需要返回一个布尔值,但是在这里,它可以返回任何类型。
一个臂有两部分:一个模式和一些代码。这里的第一个臂有一个模式,即值 Coin::Penny,然后是 => 运算符,它将模式和要运行的代码分隔开来。在这种情况下,代码只是值 1。每个臂与下一个臂用逗号隔开。
如果匹配臂的代码很短,我们通常不使用花括号。如果要在匹配臂中运行多行代码,则必须使用花括号,并且臂之后的逗号就可选了
绑定到值的模式
匹配臂的另一个有用特性是它们可以绑定到与模式匹配的值的部分。
Matching with Option<T>
对枚举进行匹配,将变量绑定到内部数据,然后根据它执行代码。
Matches Are Exhaustive
使用 match 语句时,必须考虑到所有可能的情况(包括None),否则编译器会报错。这与其他语言中的 switch 语句不同,在其他语言中,switch 语句通常可以有一个 default 分支,如果没有匹配到任何一个条件,则执行 default 分支。
错误写法如下:
Catch-all Patterns and the _ Placeholder
在前两个臂中,模式是 3 和 7 的字面值。对于覆盖所有其他可能值的最后一个臂,模式是我们选择的命名为 other 的变量。其他臂运行的代码使用该变量,通过将其传递给 move_player 函数。
即使我们没有列出 u8 可以具有的所有可能值,该代码也可以编译,因为最后一个模式将匹配未列出的所有值。这个通用模式满足 match 必须是完全的要求。请注意,我们必须将通用臂放在最后,因为模式按顺序计算。如果我们将通用臂放在前面,其他臂将永远不会运行,所以如果在通用臂之后添加臂,Rust 会给出警告!
Rust 还有一个模式,我们可以在想要通用臂但不想在通用臂模式中使用值时使用:_ 是一个特殊模式,它匹配任何值,并不绑定到该值。这告诉 Rust 我们不会使用该值,因此 Rust 不会给出未使用变量的警告。
通用臂之后添加臂:
可以在想要通用臂但不想在通用臂模式中使用值时使用_: _是一个特殊模式,它匹配任何值,并不绑定到该值。这告诉 Rust 我们不会使用该值,因此 Rust 不会给出未使用变量的警告。
除了3或7以外的任何东西,不会发生任何其他事情
if-let
if let 语法允许你将 if 和 let 组合成一种简洁的方式,用于处理与一个模式匹配的值,同时忽略其余的值。
if let pattern = value {
// code block
} else {
// code block
}
使用match
使用if-let
if let 语法使用模式和表达式,用等号隔开。它的工作方式与 match 相同,其中表达式给定给 match,模式是它的第一个臂。在这种情况下,模式是 Some(max),max 绑定到 Some 内部的值。然后我们可以在 if let 块的主体中像在相应的 match 臂中一样使用 max。如果值不匹配模式,则不会运行 if let 块中的代码。
使用 if let 意味着打的字比较少,缩进次数比较少,所需的代码也比较少。但是,你会失去 match 强制执行的详尽检查。选择 match 和 if let 取决于你在特定情况下正在进行的操作,以及是否放弃详尽检查以获得简洁性是否合适。
换句话说,你可以把 if let 看作是 match 的语法糖,当值与一个模式匹配时运行代码忽略所有其他值。if let 语法也可以有一个 else 分句。
if let - else
match 写法
if let -else写法
通用臂
-
match 语句中,_ 总可以替代 None
-
使用变量名作为通用臂的好处是,你可以在代码块中使用这个变量,比使用 _ 更加灵活。但是,使用变量名作为通用臂时,要注意,通用臂必须放在所有其他臂的最后,因为它是最后一个匹配的,如果放在前面,则永远不会执行其他臂。
if let … else if let … else
if let pattern_1 = value_1 {
// code block 1
} else if let pattern_2 = value_2 {
// code block 2
} else {
// code block 3
}
如果 pattern_1 与 value_1 匹配成功,则执行代码块 1;如果 pattern_1 匹配失败,但 pattern_2 与 value_2 匹配成功,则执行代码块 2;如果所有条件都不成立,则执行代码块 3。
match 语法和 if let 语法都是用来匹配模式的。但是,它们有一些重要的区别:
-
语法不同:match 语法的语法结构比 if let 语法的语法结构复杂。 match 语法的语法结构如下:
match EXPRESSION {
PATTERN => CODE,
PATTERN => CODE,
...
}
而 if let 语法的语法结构如下:
if let PATTERN = EXPRESSION {
CODE
}
-
匹配的模式不同:match 语法可以匹配任何模式,而 if let 语法只能匹配单个模式。例如,可以使用 match 语法来匹配一个带有枚举值的元组,但是不能使用 if let 语法来匹配。
-
可读性不同:由于 if let 语法的语法结构更简单,因此它可能更易于阅读和理解。但是,由于 match 语法在模式匹配时有更多的功能,因此在进行复杂的模式匹配时,match 语法可能更易于阅读和理解。
-
可移植性不同:match 语法是 Rust 中的语言特性,而 if let 语法是 Rust 的语法糖(syntax sugar)。这意味着,if let 语法的实现是基于 match 语法的。因此,如果要在其他语言中使用相似的功能,可能需要自己实现 if let 语法。
-
其他功能不同:match 语法具有更多的功能,例如可以使用守卫(guards)和多个分支(arms)来实现更复杂的逻辑。而 if let 语法仅能进行简单的单分支匹配。
文章评论