目录
一、概念
- 模式是 Rust 中特殊的语法,它用来匹配类型中的结构,一般由以下内容组成
- 字面量、变量、通配符和占位符
- 解构的数组、枚举、结构体或者元组
二、模式的位置
2.1 match分支
- match 表达式由match 关键字、用于匹配的值和一个或多个分支构成;
- match 表达式必须是 穷尽(exhaustive) 的;
- 通配符
_
可以匹配所有情况,它从不绑定任何变量;
2.2 if let表达式
if let
可以对应一个可选的带有代码的else在if let中的模式不匹配时运行;- 下述示例代码组合并匹配 if let、else if 和 else if let 表达式,这比单一的match匹配更加灵活;
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {}, as the background", color);
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
- 根据不同的条件设置背景色;
2.3 while let条件循环
- 只要模式匹配就一直执行
while
的内部函数体;
fn main() {
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
}
- pop 方法取出vector的最后一个元素并返回 Some(value);
- 如果 vector 是空的,它返回 None,一旦其返回 None,while 循环停止;
- while循环只要pop返回Some就会一直运行其块中的代码;
2.4 for循环
- for循环的模式是for关键字直接跟随的值,例如for x in y 中的 x;
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
enumerate
方法适配一个迭代器产生一个值和其在迭代器中的索引,位于一个元组中;
2.5 let语句
fn main() {
let x = 5;
let (x, y, z) = (1, 2, 3);
}
2.6 函数参数
- 参数部分就是模式,例如下面的x ;
- 可以在函数参数中匹配元组;
fn foo(x: i32) {
// 代码
}
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
foo(3);
}
三、模式是否会匹配失效
- 模式有两种形式:refutable(可反驳的)和 irrefutable(不可反驳的);
- 不可反驳的指能匹配任何传递的可能值的模式;
- 函数参数、 let 语句和 for 循环只能接受该模式;
- 例如
let x = 5;
;
- 可反驳的 指对某些可能的值进行匹配会失败的模式;
if let
和while let
表达式被限制为只能接受该模式;- 例如
if let Some(x) = a_value
表达式中的 Some(x);- 如果变量 a_value 中的值是None而不是Some,那么 Some(x) 模式不能匹配;
fn main() {
let Some(x) = Some(5);
}
- 编译时会报错:在要求不可反驳模式的地方使用可反驳模式;
- 使用
if let
修复上面的问题;- 如果模式不匹配,大括号中的代码将被忽略,其余代码保持有效;
fn main() {
if let Some(x) = Some(5){
println!("{}", x);
}
}
- 如果为
if let
提供了一个总是会匹配的模式,编译器会产生警告;
fn main() {
if let t = 5 {
println!("{}", t);
}
}
- 编译结果如下
四、模式语法
4.1 匹配字面量
- 可以配置整数或字符的单模式;
- 可以通过
|
匹配多个模式 - 可以通过
a..=b
匹配值为 [a,b] 的范围(范围只能用数字或char);
fn main() {
let x = 5;
match x {
1 => println!("one"),
2 | 3 => println!("two or three"),
4..=10 => println!("four --> ten"),
_ => println!("anything"),
}
let y = 'j';
match y {
'a'..='i' => println!("a ---> i"),
'j' => println!("j"),
'k'..='z' => println!("k ---> z"),
_ => println!("something else"),
}
}
4.2 匹配命名变量
- 命名变量是匹配任何值的不可反驳模式;
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);
}
- 代码声明了一个值为 Some(5) 的变量 x 和一个值为 10 的变量 y;
- 接着在值 x 上创建了一个 match 表达式;
- 第一个匹配分支的模式并不匹配 x 中定义的值;
- 第二个匹配分支中的模式引入了一个新变量 y,它会匹配任何 Some 中的值;
- y在 match 表达式的新作用域中,是一个新变量,而必非
y=10
中的y; - Some(y)中的 y 会匹配任何 Some 中的值(在这里是 x 中的值);
- 这个 y 绑定了 x 中 Some 内部的值,此分支会成功执行;
- y在 match 表达式的新作用域中,是一个新变量,而必非
- 如果 x 的值是 None则会匹配下划线;
- 此时
_
中println!
中的x还是外部的x,因此会输出Default case, x = None;
- 此时
- match执行完毕,内部变量的作用域都结束了,因此最后的 println! 会打印at the end: x = Some(5), y = 10 ;
4.3 解构并分解值
- 使用模式来解构结构体、枚举、元组和引用;
1)解构结构体
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
let Point { x, y } = p;
println!("a = {}, b = {}", a, b);
println!("x = {}, y = {}", x, y);
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),
}
}
let Point { x: a, y: b } = p
创建了变量 a 和 b 来匹配结构体 p 中的 x 和 y 字段;- 模式中的变量名不必与结构体中的字段名一致;
let Point { x, y } = p
创建了变量 x 和 y 来匹配结构体 p 中的 x 和 y 字段;- 模式中的变量名与结构体中的字段名一致 (与顺序无关),自动匹配值;
- 使用字面量作为结构体模式的一部分进行解构;
2)解构枚举
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
)
}
}
}
- Message::Quit没有任何数据的枚举成员,只能匹配字面量;
- 类结构体枚举成员Message::Move,采用类似于匹配结构体的模式;
- 包含一个或三个元素的Message::Write和Message::ChangeColor类元组枚举成员,类似于解构元组的模式;
3)解构嵌套的结构体和枚举
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
)
}
_ => ()
}
}
4)解构结构体和元组
- 用复杂的方式来混合、匹配和嵌套解构模式;
struct Point{
x: i32,
y: i32,
}
fn main() {
let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
println!("feet = {}", feet);
println!("inches = {}", inches);
println!("x = {}", x);
println!("y = {}", y);
}
运行结果
4.4 忽略模式中的值
1)使用_忽略整个值
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {}", y);
}
fn main() {
foo(3, 4);
}
2)使用嵌套的_忽略部分值
- 可以在一个模式内部使用 _ 忽略部分值;
fn main() {
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);
}
- match匹配第一个条会忽略所有的Some值,只有当其中某个为None时才会匹配到第二个分支;
也可以忽略部分特定值
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {}, {}, {}", first, third, fifth)
},
}
}
- 忽略了numbers元组中的4和16;
3)在名字前以下划线开头忽略未使用的变量
- 一般情况下创建一个变量却不在任何地方使用,编译器会有警告;
- 如果变量以下划线开头,则编译器不会产生警告;
fn main() {
let _x = 5;
let y = 10;
}
- 只有y变量收到了编译器的警告;
4)用..
忽略剩余值
- 对于有多个部分的值,可以使用
..
语法只使用部分并忽略其它值; - 同时可避免为每一个被忽略的值列出下划线;
- 被忽略的部分不能有歧义,否则会报错;
struct Point {
x: i32,
y: i32,
z: i32,
}
fn main() {
let origin = Point { x: 3, y: 4, z: 5 };
match origin {
Point { x, .. } => println!("x is {}", x),
}
match origin{
Point{ z, ..} => println!("z is {}", z ),
}
let numbers = (1, 2, 3, 4, 5, 6);
match numbers{
(first,.., last) => {
println!("Some numbers: {}, {}", first, last);
}
}
// match numbers{
// (.., second, ..) => {
// println!("Some numbers: {}", second);
// }
// }
}
- 前两个match只分别想操作x和z的值而忽略其他值;
- 第三、四个match显示了在元组中的应用:
- 第三个只匹配的第一个和最后一个值,其他的使用
..
忽略; - 第四个被注释掉了,因此
second
前后的忽略值个数有歧义,因此编译器报错(如下图)
- 第三个只匹配的第一个和最后一个值,其他的使用
4.5 匹配守卫
1)引入额外条件
- 匹配守卫(match guard) 是一个指定于match分支模式之后的额外 if 条件,它也必须被满足才能选择此分支;
- 匹配守卫用于表达比单独的模式所能允许的更为复杂的情况;
fn main() {
let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
}
- 很好理解, 第一个分支就能执行成功;
2)解决模式中变量覆盖的问题
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(n) if n == y => println!("Matched, n = {}", n),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {}", x, y);
}
- 输出结果:Default case, x = Some(5)和at the end: x = Some(5), y = 10;
- 第二个分支中的模式不会引入一个覆盖外部 y 的新变量 y,这意味着可以在匹配守卫中使用外部的 y;
3)在使用或运算符指定多个模式
fn main() {
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
}
- 匹配分支中y同时作用于4,5,6,三者是或的关系;
- 即y为true时第一个分支才会进行判断;
4 | 5 | 6 if y
应该理解为(4 | 5 | 6) if y
;- 综合解释为:当y为true时,x为4,5或6时,打印yes;
4.6 at绑定
- at运算符
(@)
允许在创建一个存放值的变量的同时测试其值是否匹配模式;
enum Message {
Hello { id: i32 },
}
fn main() {
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)
},
}
}
- 代码测试
Message::Hello
的id
字段是否位于3..=7
范围内; - 同时将其值绑定到 id_variable 变量中以便此分支相关联的代码可以使用它;
- 代码输出Found an id in range: 5;
- 第一个分支通过在
3..=7
之前指定id_variable @
,捕获了任何匹配此范围的值并同时测试范围; - 第二个分支只在模式中指定了一个范围,没有将具体的值保存进一个变量,因此不能使用;
- 第三个分支任务值都可以匹配;