枚举与模式匹配
在本章中,我们将介绍 枚举(enumerations),也称为 enums。枚举允许你通过列举其可能的变体来定义一个类型。首先,我们将定义和使用一个枚举,展示枚举如何将含义与数据一起编码。接下来,我们将探索一个特别有用的枚举 —— Option,它表示一个值可以是某个值,也可以什么都不是。然后,我们将了解如何使用 is 关键字进行模式匹配,从而轻松地为枚举的不同值运行不同的代码。最后,我们将介绍 Auto 的 ?? 运算符如何提供一种简洁的方式来处理可选值。
定义枚举
结构体提供了一种将相关字段和数据组织在一起的方式(比如带有 width 和 height 的 Rectangle),而枚举则提供了一种表示某个值是一组可能值之一的方式。例如,我们可能想说 Rectangle 是一组可能形状中的一种,这组形状还包括 Circle 和 Triangle。为此,Auto 允许我们将这些可能性编码为枚举。
让我们看一个需要在代码中表达的场景,看看为什么枚举比结构体更合适。假设我们需要处理方向。我们可以 枚举 所有可能的变体 —— 这就是"枚举"名称的由来。
Direction 现在是一个自定义数据类型,可以在代码的其他地方使用。注意与 Rust 的关键区别:
| 特性 | Auto | Rust |
|---|---|---|
| 访问变体 | Direction.North | Direction::North |
| 显示输出 | ${dir}(自动 Display) | 需要 #[derive(Display)] |
Auto 使用点号(.)访问枚举变体,而 Rust 使用双冒号(::)。Auto 还会自动为枚举生成 Display 实现,允许你直接在字符串插值中使用它们。
枚举值
我们可以创建每个变体的实例,并将它们传递给函数:
带数据的枚举
枚举相对于结构体的一个优势是,每个变体可以持有不同类型和数量的数据。我们不需要在结构体内部嵌套枚举,而是可以直接将数据放入每个枚举变体中:
每个变体可以有不同类型和数量的关联数据。以下是空变体和带数据变体混合的例子:
关键区别:
| 特性 | Auto | Rust |
|---|---|---|
| 空变体 | Quit(无括号) | Quit |
| 带数据变体 | Write String | Write(String) |
| 构造带数据变体 | Message.Write("hello") | Message::Write(String::from("hello")) |
枚举方法
与结构体一样,我们可以使用 ext 关键字为枚举定义方法:
ext 关键字对枚举的工作方式与结构体相同 —— 它添加方法(生成 Rust 的 impl 块),使用隐式 self 和 .field 简写。
Option 枚举
Auto 的 Option 类型使用 ?T 语法表示,代表一个值可能是某个值,也可能什么都不是。这是 Auto 中等价于 Rust Option<T> 的写法:
// ?T 是 May<T>(Auto 的 Option 类型)的语法糖
// 它可以是 Some(value) 或 None// Option<T> 可以是 Some(value) 或 None?T 语法消除了 Tony Hoare 著名的"十亿美元错误"——空引用问题。Auto 不使用 null,而是让你显式处理值可能缺失的情况。
Option 的关键区别:
| 特性 | Auto | Rust |
|---|---|---|
| Option 类型 | ?T | Option<T> |
| 有值 | Some(value) | Some(value) |
| 无值 | None | None |
| 函数返回 | fn foo() ?int | fn foo() -> Option<i32> |
因为 ?T 和 T 是不同的类型,编译器不会让你把可选值当作确定存在的值来使用。你必须显式处理 None 的情况。
使用 is 进行模式匹配
Auto 使用 is 关键字进行模式匹配,等价于 Rust 的 match 表达式。is 表达式将一个值与一系列模式进行比较,并根据匹配到的模式执行相应的代码。
基本模式匹配
关键区别:
| 特性 | Auto | Rust |
|---|---|---|
| 匹配关键字 | is value { } | match value { } |
| 通配符分支 | else => | _ => |
| 分支分隔符 | 换行(无逗号) | 分支之间用逗号 |
| 模式语法 | Variant.Name(binding) | Variant::Name(binding) |
绑定值的模式
is 最强大的特性之一是模式可以绑定到枚举变体内部的值:
当 atom 是 Atom.Int(11) 时,模式 Atom.Int(i) 匹配,i 绑定到值 11。绑定的变量随后可以在分支代码中使用。
使用 is 匹配 Option
我们可以像处理其他枚举一样,使用 is 来处理 ?T(Option)值:
匹配是穷尽的
与 Rust 的 match 一样,Auto 的 is 是穷尽的 —— 编译器会确保你处理了每一种可能的情况。如果你遗漏了某个情况,你将得到编译时错误。
使用 else 作为通配符
else 分支是通配符模式,等价于 Rust 的 _:
else 分支匹配所有未在其他分支中列出的值。注意 else 必须放在最后,因为分支是按顺序求值的。
使用 ?? 简洁处理可选值
??(空值合并)运算符提供了一种简洁的方式来为可选值提供默认值:
?? 运算符是 Auto 中等价于 Rust .unwrap_or() 方法的写法。如果左侧有值,就返回该值;否则返回右侧的默认值。
当你想提供一个回退值而不需要使用完整的 is 匹配时,这非常有用:
// 繁琐:完整的 is 匹配
let result = is optional_value {
Some(v) => v
None => default_value
}
// 简洁:?? 运算符
let result = optional_value ?? default_value总结
Auto 的枚举和模式匹配提供了与 Rust 相同的强大功能,但语法更加简洁:
| 概念 | Auto | Rust |
|---|---|---|
| 定义枚举 | enum Name { ... } | enum Name { ... } |
| 访问变体 | Name.Variant | Name::Variant |
| 数据变体 | Variant Type | Variant(Type) |
| 模式匹配 | is value { } | match value { } |
| 通配符分支 | else => | _ => |
| Option 类型 | ?T | Option<T> |
| 有值 | Some(value) | Some(value) |
| 无值 | None | None |
| 空值合并 | x ?? default | x.unwrap_or(default) |
| 枚举方法 | ext Name { } | impl Name { } |
枚举是 Auto 工具箱中一个强大的工具。结合第 5 章的结构体,你可以创建自定义类型来表达程序领域中的概念。is 模式匹配和 ?? 运算符使得处理枚举和可选值既安全又简洁。
接下来让我们进入第 7 章,了解 Auto 如何使用包和模块来组织代码。