Rust 基础入门 - 第五章
match 匹配
通用形式如下:
fn main() {
match target {
模式 1 => 表达式 1,
模式 2 | 模式 3 => {
语句 1;
语句 2;
表达式 2
},
_ => 表达式 3
}
}
其中,target
是要进行匹配的值,模式
是要匹配的模式,表达式
是要执行的代码。
例子:
fn main() {
enum Coin { // 定义枚举类型
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!"); // 语句
1 // 最后一行要是表达式
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
}
该函数 value_in_cents
接受一个 Coin
类型的参数,然后根据参数的值返回不同的美分数值。
match 表达式赋值
match
本身也是表达式,可以用于赋值:
enum IpAddr {
Ipv4,
Ipv6
}
fn main() {
let ip1 = IpAddr::Ipv6;
let ip_str = match ip1 {
IpAddr::Ipv4 => "127.0.0.1",
_ => "::1",
};
println!("{}", ip_str);
}
模式绑定
模式匹配中可以从模式中取出绑定的值,例子如下:
#![allow(unused)]
fn main() {
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --skip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState), // 25 美分硬币中包含州名
}
}
在枚举类型 Coin
中,Quarter
类型的值中包含一个 UsState
类型的值,我们可以从 Quarter
中取出 UsState
的值:
#![allow(unused)]
fn main() {
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => { // 从 Quarter 中取出 state
println!("State quarter from {:?}!", state);
25
},
}
}
}
如上代码中,在第八行,当匹配到 Coin::Quarter(state)
时,会将 state
绑定到 Coin::Quarter
中的 UsState
类型的值。
穷尽匹配
match
的匹配必须要穷尽所有情况,不然不能通过编译,如下代码:
enum Direction {
East,
West,
North,
South,
}
fn main() {
let dire = Direction::South;
match dire {
Direction::East => println!("East"),
Direction::North | Direction::South => {
println!("South or North");
},
};
}
这样严格的匹配,可以避免 null
陷阱。
_ 通配符
当不想列出所有的匹配情况时,可以使用 _
通配符,如下代码:
#![allow(unused)]
fn main() {
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
}
将 _
放置于所有匹配模式的最后,可以匹配遗漏的模式。在这个例子中,()
返回单元类型,即不做任何事。
if let 匹配
对于只有一种模式需要处理的场景,用 match
会显得有些冗余,这时可以使用 if let
来简化代码。
match
的通用形式如下:
#![allow(unused)]
fn main() {
let v = Some(3u8);
match v {
Some(3) => println!("three"),
_ => (),
}
}
这里最后的 _
匹配多多少少有些冗余,可以使用 if let
来简化:
#![allow(unused)]
fn main() {
let v = Some(3u8);
if let Some(3) = v {
println!("three");
}
}
matches! 宏
在 Rust 标准库中,有一个很实用的宏 matches!
,可以将一个表达式跟模式进行匹配,然后返回匹配的结果 true
or false
。
比如我们想要判断一个字符是否是英文字母,可以用 match
这样写:
fn main() {
let foo = 'f';
println!("{}", get_foo(foo));
}
fn get_foo(c: char) -> bool {
match c {
'A'..='Z' => true,
'a'..='z' => true,
_ => false,
}
}
但是这种返回布尔值的情况,可以用 matches!
宏来简化:
fn main() {
let foo = 'f';
println!("{}", get_foo(foo));
}
fn get_foo(c: char) -> bool {
matches!(c, 'a'..='z' | 'A'..='Z')
}
Option 用法
Option
是一个枚举类型,它的定义如下:
enum Option<T> {
Some(T),
None,
}
简单来说,一个变量要么是 Some
类型,要么是 None
类型,Some
类型中包含一个值,None
类型中不包含任何值。
那么可以用 match
来判断 Option
类型的值:
#![allow(unused)]
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
在这个模式匹配中,None
类型的值不会被处理,而 Some(5)
和 Some(i)
属于相同的成员,并绑定值后加一。
因为 Option,Some,None 都包含在 prelude 中,因此你可以直接通过名称来使用它们,而无需以 Option::Some 这种形式去使用。
模式
说了这么多模式匹配,何为模式?
模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,它往往和 match 表达式联用,以实现强大的模式匹配能力。模式一般由以下内容组合而成:
- 字面值
- 解构的数组、枚举、结构体或者元组
- 变量
- 通配符
- 占位符
有如下用到模式的地方:
match 分支
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
_ => EXPRESSION,
}
if let 分支
if let PATTERN = SOME_VALUE {
/// do something
}
while let 循环
fn main() {
// Vec 是动态数组
let mut stack = Vec::new();
// 向数组尾部插入元素
stack.push(1);
stack.push(2);
stack.push(3);
// stack.pop 从数组尾部弹出元素
while let Some(top) = stack.pop() {
println!("{}", top);
}
}
pop
方法取出动态数组的最后一个元素并返回 Some(value)
,如果动态数组是空的,将返回 None
,对 while
来说,只要 pop
返回 Some
就会一直不停的循环。一旦其返回 None
,while
循环停止。我们可以使用 while let
来弹出栈中的每一个元素
for 循环
fn main() {
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
}
这里使用 enumerate
方法产生一个迭代器,该迭代器每次迭代会返回一个 (索引,值) 形式的元组,然后用 (index,value) 来匹配
let 语句
let (x, y, z) = (1, 2, 3);
其实这里也是一种模式匹配,将一个元组解构成三个变量。
函数参数
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
&(3, 5)
会匹配模式 &(x, y)
,因此 x
得到了 3
,y
得到了 5
。
全模式列表
如下罗列所有模式语法:
匹配字面值
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
这种最常用。
匹配命名变量
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {:?}", y),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);
}
这个例子中,变量 x
不符合第一个分支,跳过,在第二个分支中,引入了一个新的变量 y
,但是它在 match
的作用域中,和上面的那个 y
不是同一个变量,因此这个新绑定的 y
的值是 5
,而不是 10
。
单分支多模式
可以用 |
来匹配多个模式,代表或的关系:
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
序列匹配范围
..=
表示闭区间,..
表示开区间,可以用来匹配序列中的值,当模式匹配任何在此序列内的值时,该分支会执行:
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
这里的序列只能用于数字或者字符类型,因为这两种类型都是可以连续的。同时编译器在编译期可以检查该序列是否为空。
解构分解值
也用模式来解构结构体、元组、枚举、数组和引用:
解构结构体
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point {x: 0, y: 7};
let Point {x: a, y: b} = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
这里创建了一个结构体 Point
,然后创建了一个 p
的实例,然后使用模式匹配来解构 p
,将 x
和 y
的值分别赋值给 a
和 b
。
也可以使用字面值作为结构体模式的一部分进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point {x: 0, y: 7};
match p {
Point {x, y: 0} => println!("On the x axis at {}", x),
Point {x: 0, y} => println!("On the y axis at {}", y),
Point {x, y} => println!("On neither axis: ({}, {})", x, y),
}
}
这个例子中,值 p
因为其 x
包含 0 而匹配第二个分支。
解构枚举
enum Message {
Quit,
Move {x: i32, y: i32},
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.")
}
Message::Move {x, y} => {
println!(
"Move in the x direction {} and in the y direction {}",
x,
y
);
}
Message::Write(text) => println!("Text message: {}", text),
Message::ChangeColor(r, g, b) => {
println!(
"Change the color to red {}, green {}, and blue {}",
r,
g,
b
)
}
}
}
模式匹配要类型相同。
解构嵌套的结构体和枚举
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move {x: i32, y: i32},
Write(String),
ChangeColor(Color),
}
fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
match msg {
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!(
"Change the color to red {}, green {}, and blue {}",
r,
g,
b
)
}
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!(
"Change the color to hue {}, saturation {}, and value {}",
h,
s,
v
)
}
_ => ()
}
}
match
第二个分支的模式匹配一个 Message::ChangeColor
枚举成员,该枚举成员又包含了一个 Color::Hsv
的枚举成员,最终绑定了 3 个内部的 i32 值。
解构数组
和元组类似:
固定长度数组
let arr: [u16; 2] = [114, 514];
let [x, y] = arr;
assert_eq!(x, 114);
assert_eq!(y, 514);
可变长度数组
let arr: &[u16] = &[114, 514];
if let [x, ..] = arr {
assert_eq!(x, &114);
}
if let &[.., y] = arr {
assert_eq!(y, 514);
}
let arr: &[u16] = &[];
assert!(matches!(arr, [..]));
assert!(!matches!(arr, [x, ..]));
这个还不懂。可恶
忽略模式中的值
使用下划线
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {}", y);
}
fn main() {
foo(3, 4);
}
使用嵌套的下划线忽略部分值
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {:?}", setting_value);
这个例子中,第一个匹配分支,只关心 setting_value
和 new_setting_value
是否都是 Some
值,所以使用了下划线忽略了它们的值。
使用下划线忽略未使用的变量
这个之前提到过,就是为了消除编译器的警告。
但是,只使用 和使用以下划线开头的名称有些微妙的不同:比如 _x
仍会 将值绑定到变量 ,而 `` 则 完全不会绑定 。比如下面两个例子:
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s { // 这里的 _s 仍然会绑定到 s 的值,s 的值会被移动到 _s 中
println!("found a string");
}
println!("{:?}", s); // s 的值已经被移动到 _s 中,所以这里报错
}
在上面代码中,我们会得到一个错误,因为 s
的值会被转移给 _s
。但是,如果我们使用 _
而不是 _s
,则不会发生这种情况:
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_) = s {
println!("found a string");
}
println!("{:?}", s);
}
```
使用.. 忽略剩余的值
对于有多个部分的值,可以使用 ..
语法来只使用部分值而忽略其它值,这样也不用再为每一个被忽略的值都单独列出下划线。
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {}, {}", first, last);
},
}
}
主打一个优雅。但是同时要主要的是,..
不能存在歧义,比如下面这个例子:
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {}", second) // only God knows which number this is
},
}
}
匹配守卫
匹配守卫,(match guards) 允许我们在匹配模式的同时,加入额外的条件判断。如果这个条件判断为 true
,则执行匹配分支,否则继续尝试下一个匹配分支。
let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
在这个例子中,当 num
的值为 Some(4)
时,匹配守卫 x < 5
为 true
,所以匹配分支会被执行。
当一个匹配分支中,指定了多个模式,用 |
分隔,这时,匹配守卫的优先级是独立的,比如下面这个例子:
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
优先级是这样的:
(4 | 5 | 6) if y => ...
@ 绑定
@
绑定,(at binding) 允许我们将匹配的值绑定到一个变量,这个变量可以在匹配分支中使用。
fn main() {
enum Message {
Hello {id: i32},
}
let msg = Message::Hello {id: 5};
match msg {
Message::Hello {id: id_variable @ 3..=7} => {
println!("Found an id in range: {}", id_variable)
},
Message::Hello {id: 10..=12} => {
println!("Found an id in another range")
},
Message::Hello {id} => {
println!("Found some other id: {}", id)
},
}
}
在这个例子中,第一个分支中,测试 id
的值是否在 3..=7
范围内,如果是,则将 id
的值绑定到 id_variable
中,然后在匹配分支中打印出 id_variable
。
当你既想要限定分支范围,又想要使用分支的变量时,就可以用 @
来绑定到一个新的变量上,实现想要的功能。