模式语法
模式匹配在很多地方都有用到,比如let
声明变量,函数参数,match
语句,if let
等等,前面章节只是简单讲了一些基本使用方法,这一节我们来看一下所有的模式匹配方式和模式匹配的强大能力
匹配字面量
最简单的匹配能力,匹配字面量类型,例如下面i32
类型:
let x = 1;
match x {
1 => println!("1"),
2 => println!("2"),
3 => println!("3"),
_ => println!("任何值"), // match需要穷举i32类型,_用来匹配所有类型
}
// 1
复制代码
匹配命名变量
这种匹配方式一般用于解构,另外match
会为匹配的结果创建独立的作用域:
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("x = 50"),
// 这里的y在独立的作用域,是绑定了Some中的值,而不是外边的y
Some(y) => println!("y = {:?}", y),
_ => println!("默认 x = {:?}", x),
}
// 这里的y仍然是10
println!("匹配结束后: x = {:?}, y = {:?}", x, y);
// y = 5
// 最终: x = Some(5), y = 10
复制代码
多个模式
还支持使用|
符号,表示或者的关系:
let x = 1;
match x {
1 | 2 => println!("1 或者 2"),
3 => println!("3"),
_ => println!("任何值"),
}
// 1 或者 2
复制代码
匹配范围
类似区间,还可以使用..=
来表示匹配一个区间:
let x = 2;
match x {
1..=5 => println!("x >= 1 并且 x <= 5"),
_ => println!("任何值"),
}
// x >= 1 并且 x <= 5
复制代码
不仅是数字,可以用char
类型来表示一个字符的区间:
let y = 'c';
match y {
'a'..='d' => println!("a-d之间"),
'e'..='h' => println!("e-h之间"),
_ => println!("任何值"),
}
// a-d之间
复制代码
解构结构体
结构体也可以在match
的匹配模式中,前面我们可能没讲到解构结构体,这里先看一下正常情况下解构结构体的方法:
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 10, y: 15 };
// 等号左边是模式,右边是结构体
let Point { x, y } = p;
println!("x = {} y = {}", x, y);
// x = 10 y = 15
复制代码
解构的时候可以重命名结构体中字段:
let Point { x: a, y: b } = p;
println!("a = {} b = {}", a, b);
// a = 10 b = 15
复制代码
最后我们在match
条件中解构,模式与上面一样:
let p = Point { x: 0, y: 15 };
match p {
Point { x: 0, y } => println!("在y轴上, y = {}", y),
Point { x, y: 0 } => println!("在x轴上, x = {}", x),
Point { x, y } => println!("既不在x轴也不在y轴, x = {} y = {}", x, y),
}
// 在y轴上, y = 15
复制代码
解构枚举
结构体可以解构,当然枚举也不例外了:
enum Message {
ChangeColor(i32, i32, i32),
}
let msg = Message::ChangeColor(0, 160, 255);
// 同样等号左边是模式,右边是枚举值
let Message::ChangeColor(r, g, b) = msg;
println!("r: {}, g: {}, b: {}", r,g,b);
// r: 0, g: 160, b: 255
复制代码
在match
语句中匹配枚举值的多种变体:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move x = {} y = {}", x, y),
Message::Write(str) => println!("Write{}", str),
Message::ChangeColor(r, g, b) => println!("ChangeColor r = {} g = {} b = {}", r, g, b)
}
// ChangeColor r = 0 g = 160 b = 255
复制代码
解构嵌套的结构体和枚举
解构还支持多层嵌套的解构,下面在Message
枚举中嵌套Color
枚举,然后在match
语句中解构:
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}
// Message嵌套Color
let color = Color::Rgb(0, 160, 255);
let msg = Message::ChangeColor(color);
match msg {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move x = {} y = {}", x, y),
Message::Write(str) => println!("Write{}", str),
// 解构嵌套Color
Message::ChangeColor(Color::Rgb(r, g, b)) => println!("Color r = {} g = {} b = {}", r, g, b),
Message::ChangeColor(Color::Hsv(h, s, v)) => println!("Color h = {} s = {} v = {}", h, s, v)
}
// Color r = 0 g = 160 b = 255
复制代码
解构嵌套的结构体和元组
下面使用一个元组将Point
结构体嵌套,然后使用let
将其解构:
let tuple = ((3, 10), Point { x: 3, y: -10 });
let ((a, b), Point { x, y}) = tuple;
println!("a = {} b = {} x = {} y = {}", a, b, x, y);
// a = 3 b = 10 x = 3 y = -10
复制代码
使用 _ 忽略整个值
有时候有些参数或者变量我们并不关心,这时可以使用_
来进行忽略,防止编译器报提醒,其实前面在match
语句中已经使用过了:
fn foo(_: i32, b: i32) {
println!("b = {}", b);
}
foo(10, 20);
复制代码
_
是不能直接使用的:
fn foo(_: i32, b: i32) {
println!("_ = {} b = {}", _, b); // 报错,不允许使用 _
}
foo(10, 20);
复制代码
使用嵌套的_
忽略部分值:
let mut value = Some(5);
let new_value = Some(10);
match (value, new_value) {
// 匹配两个值都是Some的情况,但不关心具体是什么值
(Some(_), Some(_)) => {
println!("value已经存在,不能覆盖");
},
// 如果两者中有一个None,那么就更新value
_ => {
value = new_value;
}
}
println!("被更新之后的值 {:?}", value);
// 被更新之后的值 Some(5)
复制代码
一个模式中的多处使用_
来忽略特定值:
let arr = [1, 2, 3, 4, 5];
match arr {
[a, _, b, _, c] => println!("a = {} b = {} c = {}", a, b, c)
}
// a = 1 b = 3 c = 5
复制代码
变量名称使用_
开头来忽略未使用的警告:
let _x = 1;
let y = 2; // warning: 未使用的变量y
// 上边的y被警告,而_x没有
复制代码
_
和_
开头变量的区别,_
变量会绑定变量:
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("匹配了字符串");
}
println!("{:?}", s); // error,s的所有权已经被转移到if let的条件内
复制代码
_
并不会绑定变量的值:
let s = Some(String::from("Hello!"));
if let Some(_) = s {
println!("匹配了字符串");
}
println!("{:?}", s);
// Some("Hello!")
复制代码
使用 .. 忽略剩余的值
_
可以忽略单个值,但是如果在结构体时,我们如果只关心其中某一个属性,此时其他的属性不可能都用_
来忽略,因为太多了,此时我们可以使用..
来忽略结构体中的多个值:
struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
// 忽略除x外的所有值
Point { x, .. } => println!("x = {}", x),
}
// x = 0
复制代码
忽略元组中的值:
let nums = (1, 2, 3, 4, 5);
match nums {
// 取第一个和最后一个值,忽略中间的所有值
(a, .., b) => println!("a = {} b = {}", a, b)
}
// a = 1 b = 5
复制代码
..
不能有歧义:
let nums = (1, 2, 3, 4, 5);
match nums {
// 报错,因为a有歧义,可能是一组数中的任意位置
(.., a, ..) => println!("a = {}", a)
}
复制代码
匹配守卫提供的额外条件
匹配守卫(match guard)是一个指定于match
分支模式之后的额外if
条件,它也必须被满足才能选择此分支。匹配守卫用于表达比单独的模式所能允许的更为复杂的情况:
let num = Some(4);
match num {
// 要求num是Some类型,并且x小于5
Some(x) if x < 5 => println!("小于5: {}", x),
Some(x) => println!("{}", x),
None => (),
}
// 小于5: 4
复制代码
允许获取外部变量:
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("x = 50"),
// 可以获取外部的y进行比较
Some(n) if n == y => println!("与y相等, n = {}", n),
_ => println!("默认 x = {:?}", x),
}
println!("最终: x = {:?}, y = {}", x, y);
// 最终: x = Some(5), y = 10
复制代码
与多个模式结合使用:
let x = 4;
let y = false;
match x {
// 首先与 2 | 3 | 4 进行匹配,然后在判断条件 if y
2 | 3 | 4 if y => println!("yes"),
_ => println!("no"),
}
// no
复制代码
@绑定
at 运算符@
允许我们在结构体、枚举中使用有条件的匹配,下面在枚举中使用@
:
enum Message {
Hello { id: i32 }
}
let msg = Message::Hello { id: 10 };
match msg {
// 对id创建别名并添加限制在1-10之间
Message::Hello { id: value@ 1..=10 } => println!("id 在1-10之间 {}", value),
Message::Hello { id: value@ 11..=20 } => println!("id 在11-20之间 {}", value),
Message::Hello { id } => println!("id不在1-20之间 {}", id)
}
复制代码
在结构体中使用@
:
struct User {
name: String,
age: u8
}
let user = User { name: "xiaoming".to_string(), age: 10 };
match user {
User { name, age: value @ 1..=15 } => println!("{} 年龄在1-15之间 {}", name, value),
User { name, age } => println!("{} 年龄不在1-15之间 {}", name, age)
}
// xiaoming 年龄在1-15之间 10
复制代码
看了这么多例子是不是体会到了rust的模式匹配的强大,尤其是在match
语句中,可以帮我们少写很多条件判断,建议大家把上面的例子都试着写一下,加深一下印象,才能运用的熟练。
封面图:跟着Tina画美国
关注「码生笔谈」公众号,阅读更多最新章节