枚举的定义:
enum Ordering {
Less,
Equal,
Greater
}
枚举的值被称为变体,或者构造式(constructor):
Ordering::Less
;Ordering::Equal
;Ordering::Greater
。这个枚举是标准库中定义的,可以在代码中直接导入它本身:【推荐】
use std::cmp::Ordering;
或者,导入它的所有构造式:除非可以让代码更好理解,否则不要直接导入构造式。
use std::cmp::Ordering::*;
如果要导入当前模块中声明的枚举的构造式,要使用 self
参数:
enum Pet {
Orca,
Giraffe
}
use self::Pet::*;
Rust 允许把枚举值转换为整数,但不能直接把整数转换为枚举值。
Rust 要保证枚举值一定是在 enum
声明中定义的。
枚举和结构体一样,支持使用#[derive]
属性,来标记共有特型。
枚举和结构体一样,也可以通过 impl
块定义方法。
枚举包含什么数据,就称为什么变体:
#[derive(Copy, Clone, Debug, PartialEq)]
enum RoughTime {
InThePast(TimeUnit, u32),
JustNow,
InTheFuture(TimeUnit, u32)
}
InThePast
和 InTheFuture
称为元组变体(tuple variant)RoughTme
的每个构造式占用 8 个字节。Rust 有三种枚举变体:
类基元变体:没有数据的变体。
enum EnumName;
元组变体:
enum EnumName (...);
结构体变体:
enum EnumName {
...
}
一个枚举中可以同时包含这三种变体数据:
enum RelationshipStatus {
Single,
InARelationship,
ItsComplicated(Option<String>),
ItsExtremelyComplicated {
car: DifferentialEquation,
cdr: EarlyModernistPoem
}
}
公有枚举的所有构造式和字段自动都是公有的。
枚举可以用来快速实现类似树的数据结构。
Rust 实现 Json
枚举:
enum Json {
Null,
Boolean(bool),
Number(f64),
String(String),
Array(Vec<Json>),
Object(Box<HashMap<String, Json>>)
}
Box
包装 HashMap
来表示 Object
,可以让所有 JSON 值更简洁。String
和 Vec
值占 3 个机器字Box
占 1 个机器字常见的泛型枚举:
enum Option<T> {
None,
Some(T)
}
enum Result<T, E> {
Ok(T),
Err(E)
}
基于泛型的数据结构举例:实现一个二叉树
// 创建T类型值的有序集合
enum BinaryTree<T> { // BinaryTree的值只占1个机器字
Empty, // 不包含任何数据
NonEmpty(Box<TreeNode<T>>) // 包含一个Box,它是指向位于堆内存的TreeNode的指针
}
// BinaryTree的节点
struct TreeNode<T> {
element: T, // 实际的元素
left: BinaryTree<T>, // 左子树
right: BinaryTree<T> // 右子树
}
创建这个树的任何特定节点:
use self::BinaryTree::*
let jupiter_tree = NonEmpty(Box::new(TreeNode {
element: "Jupiter",
left: Empty,
right: Empty
}));
大一点的树可以基于小一点的树创建
// 将jupiter_node和mercury_node的所有权,通过赋值转移给新的父节点mars_tree
let mars_tree = NonEmpty(Box::new(TreeNode {
element: "Mars",
left: jupiter_tree,
right: mercury_tree
}));
根节点也使用相同的方式创建:
let tree = NonEmpty(Box::new(TreeNode {
element: "Saturn",
left: mars_tree,
right: uranus_tree
}));
假如这个树有一个 add
方法,那么可以通过这样调用这个树:
let mut tree = BinaryTree::Empty;
for planet in planets {
tree.add(planet);
}
访问枚举数据的唯一安全方式:模式匹配。
定义一个 RoughTme
枚举类型:
enum RoughTime {
InThePast(TimeUnit, u32),
JustNow,
InTheFuture(TimeUnit, u32)
}
使用 match
表达式访问枚举的数据:
fn rough_time_to_english(rt: RoughTime) -> String {
match rt {
RoughTime::InThePast(units, count) =>
format!("{} {} ago", count, units.plural()),
RoughTime::JustNow =>
format!("just now"),
RoughTime::InTheFuture(units, count) =>
format!("{} {} from now", count, units.plural())
}
}
枚举、结构体或元组在匹配模式时,会从左到右堆模式的每个组件,依次检查当前值是否与之匹配。如果不匹配,就会进入到下一个模式。
模式的特型
Rust 模式本身就是一个迷你语言:
模式类型 | 示例 | 说明 |
---|---|---|
字面量 | 100 “name” |
匹配确切的值;const 声明的名字也可以 |
范围 | 0 … 100 ‘a’ … ‘k’ |
匹配范围中的任意值,包括最终值 |
通配符 | _ | 匹配任意值并忽略该值 |
变量 | name mut count |
类似_ ,但会把匹配的值转移或复制到新的局部变量 |
ref 变量 |
ref field ref mut field |
不转移或复制匹配的值,而是借用匹配值的引用 |
子模式绑定 | val @ 0 … 99 ref circle @ Shape::Circle { .. } |
匹配 @右侧的模式,使用左侧的变量名 |
枚举模式 | Some(value) None Pet::Orca |
|
元组模式 | (Key, value) |
|
结构体模式 | Color(r, g, b) Point { x, y } Card { suit: Clubs, rank: n } Account { id, name, … } |
|
引用 | &value &(k, v) |
只匹配引用值 |
多个模式 | 'a' |
'A' |
护具表达式 | x if x * x <= r2 |
仅限 match(不能在 let 等中使用) |
0、1 等整数值可以作为模式使用:
match meadow.count_rabbits() {
0 => {},
1 => println!("A rabbit is nosing around in the clover."),
n => println!("There are {} rabbits hopping about in the meadow", n)
}
其他类型的字面量也可以用作模式,包括布尔值、字符,甚至字符串:
let calendar =
match settings.get_string("calendar") {
"gregorian" => Calendar::Gregorian,
"chinese" => Calendar::Chinese,
"ethiopian" => Calendar::Ethiopian,
other => return parse_error("calendar", other)
};
可以使用通配符_
作为模式,以匹配任意值,但不保存匹配的值:
let caption =
match photo.tagged_pet() {
Pet::Tyrannosaur => "RRRAAAAAHHHHHH",
Pet::Samoyed => "*dog thoughts*",
_ => "I'm cute, love me" // 通用标题,任何宠物都适用
};
每个 match
表达式最后都会有一个通配符,即使非常确定其他情况不会发生,也必须至少加上一个后备的诧异分支:
// 有很多形状(shape),但只支持“选择”某些文本,
// 或者一个矩形区域中的所有内容,不能选择椭圆或梯形。
match document.selection() {
Shape::TextSpan(start, end) => paint_text_selection(start, end),
Shape::Rectangle(rect) => paint_rect_selection(rect),
_ => panic!("unexpected selection type")
}
为了避免最后一个分支无法运行到,可以结合 if
表达式实现模式:
fn check_move(current_hex: Hex, click: Point) -> game::Result<Hex> {
match point_to_hex(click) {
None => Err("That's not a game space"),
Some(hex) =>
if hex == current_hex {
Err("You are already there! You must click somewhere else")
} else {
Ok(hex)
}
}
}
元组模式匹配元组,适合在一个 match
表达式中同时匹配多个数据:
fn describe_point(x: i32, y: i32) -> &'static str {
use std::cmp::Ordering::*;
match (x.cmp(&0), y.cmp(&0)) {
(Equal, Equal) => "at the orgin",
(_, Equal) => "on the x axis",
(Equal, _) => "on the y axis",
(Greater, Greater) => "in the first quadrant",
(Less, Greater) => "in the second quadrant",
_ => "somewhere else"
}
}
结构体模式使用花括号,类似结构体表达式,其中的每个字段都是一个子模式:
match balloon.location {
Point {
x: 0,
y: height
} => println!("straight up {} meters", height),
Point {
x: x,
y: y
} => println!("at ({}m, {}m)", x, y)
}
对于复杂的结构体,为了使代码简洁,可以使用..
表示不关心其他字段:
match get_account(id) {
...
Some(Account {
name,
language,
..
}) => language.show_custom_greeting(name)
}
对于引用,Rust 支持两种模式:
ref
模式:借用匹配值的元素&
模式:匹配引用一般情况下,匹配不可复制的值会转移值:
match account {
Account {
name, language, ..
} => {
ui.greet(&name, &language);
ui.show_settings(&account); // 错误:使用了转移的值account
}
}
使用 ref
模式,可以借用匹配的值,而不转移它:
match account {
Account {
ref name,
ref language,
..
} => {
ui.greet(name, language);
ui.show_settings(&account);
}
}
还可以用 ref mut
借用 mut
引用:
match line_result {
Err(ref err) => log_error(err), // err是&Error(共享的ref)
// 模式Ok(ref mut line)可以匹配任何成功的结果,并借用该结果中存储的值的mut引用
Ok(ref mut line) => { // line是&mut String(可修改的ref)
trim_comments(line); // 就地修改字符串
handle(line);
}
}
与 ref
模式对应的是 &
模式。以 &
开头的模式匹配引用:
match sphere.center() {
&Point3d {x, y, z} => ...
}
匹配引用遵循引用的规则:
mut
操作;mut
引用)中转移出值。竖线 |
可用于在一个 match
分支中组合多个模式:
let at_end =
match chars.peek() {
Some(&'\r') | Some(&'\n') | None => true,
_ => false
};
使用...
可以匹配某个范围中的值。范围模式包含起点值和终点值,即'0' ... '9'
匹配所有 ASCII 数字:
match next_char {
'0' ... '9' => self.read_number(),
'a' ... 'z' | 'A' ... 'Z' => self.read_word(),
' ' | '\t' | '\n' => self.skip_whitespace(),
_ => self.handle_punctuation()
}
全纳(inclusive)范围...
:对模式匹配比较适用。
互斥范围..
:对循环和片段比较适用。
使用 if
关键字可以为 match
分支添加护具。只有在护具求值为 true
时匹配才成功,如果护具求值为 false
,那么 Rust 会继续匹配下一个模式。
match robot.last_known_location() {
Some(point) if self.distance_to(point) < 10 =>
short_distance_strategy(point),
Some(point) =>
long_distance_strategy(point),
None =>
searching_strategy()
}
@
模式x @ pattern
可以匹配给定的 pattern
,可以把匹配值整个转移或复制到一个变量 x
中:
创建如下的模式:
match self.get_selection() {
Shape::Rect(top_left, bottom_right) =>
optimized_paint(&Shape::Rect(top_left, bottom_right)),
other_shape =>
paint_outline(other_shape.get_outline()),
}
Shape::Rect
分支拆解出值后,可以再重新创建一个相同的值,所以可以用 @
模式重写:
rect @ Shape::Rect(..) => Optimized_paint(&rect),
@
模式支持匹配范围值:
match chars.next() {
Some(digit @ '0' ... '9') => read_number(digit, chars),
...
}
通过模式匹配可以实现拆解值,而不是仅仅把值保存在一个变量中:
用在 match
表达式中
用来代替标识符
用于将结构体拆解为 3 个新的局部变量:
let Track { album, track_number, title, .. } = song;
用于拆解作为函数参数的元组:
fn distance_to((x, y): (f64, f64)) -> f64 { ... }
用于迭代 HashMap
的键和值:
for (id, document) in &cache_map {
println!("Document #{}: {}", id, document.title);
}
用于自动对传给闭包的参数解引用:
let sum = numbers.fold(0, |a, &num| a + num);
上述例子,在 JavaScript 中叫解构(restructuring),Python 中叫解包(unpacking)。
不可驳模式(irrefutable pattern):始终都可以匹配的模式。可用于:
let
后面。for
后面。可驳模式(refutable pattern):可能不会匹配的模式。可用于:
match
表达式。
if let
表达式。
while let
表达式。
只处理一种特定的枚举变体:
if let RoughTime::InTheFuture(_, _) = user.date_of_birth() {
user.set_time_traveler(true);
}
只在查表成功时运行某些代码:
if let Some(document) = cache_map.get(&id) {
return send_cached_response(document);
}
不成功则重复做一些事:
while let Err(err) = present_cheesy_anti_robot_task() {
log_robot_attempt(err);
}
手工便利一个迭代器:
while let Some(_) = lines.peek() {
read_paragraph(&mut lines);
}
实现 BinaryTree::add()
方法,用于向 BinaryTree
中添加相同类型的子节点:
enum BinaryTree<T> {
Empty,
NonEmpty(Box<TreeNode<T>>)
}
struct TreeNode<T> {
element: T,
left: BinaryTree<T>,
right: BinaryTree<T>
}
impl<T: Ord> BinaryTree<T> {
fn add(&mut self, value: T) {
match *self {
BinaryTree::Empty =>
*self = BinaryTree::NonEmpty(Box::new(TreeNode {
element: value,
left: BinaryTree::Empty,
right: BinaryTree::Empty
})),
BinaryTree::NonEmpty(ref mut node) =>
if value <= node.element {
node.left.add(value);
} else {
node.right.add(value);
}
}
}
}
当 *self
为空,那么运行 BinaryTree::Empty
模式,把 Empty
树改成 NonEmpty
树。
当 *self
不为空,那么运行 BinaryTree::NonEmpty(ref mut node)
模式,可以访问并修改该树节点中的数据。
调用这个 add
方法:
let mut tree = BinaryTree::Empty;
tree.add("Mercury");
tree.add("Venus");
...
union
联合体同时支持变体、指针(引用)和可变性,但它不是内存安全的。match
表达式,因为需要给他们都添加一个新分支以处理新变体。详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第十章
原文地址