模式是学习Rust以来见过最多的词汇之一。最开始,我对模式的印象就是指的是match语句。随着学习的深入,我意识到这个理解太片面了。Rust的模式主要有两个作用,一个是解构,一个是匹配。模式Rust编程的始终,可以说,有赋值的地方,就有模式。
模式是 Rust 中特殊的语法, 它用来匹配类型中的结构, 无论类型是简单还是复杂。 结合使用模式和 match 表达式以及其他结构可以提供更多对程序控制流的支配权。 模式由如下一些内容组合而成 :
字面值是诸如整数 1
、浮点数 1.2
、字符 'a'
、字符串 "abc"
、布尔值 true
等。它们可以直接作为模式,如:
fn main() {
let a = 1;
match (a) {
1 => println!("a is 1"),
2 => println!("a is 2"),
_ =>println!("a is other"),
}
}
解构也是模式匹配常用的场景,通常使用在let语句中:
fn main() {
let a = (1, 2);
let (x, y) = a;
println!("x={}, y={}", x, y);
}
没错变量也是模式,只不过这个模式非常简单,简单到只有一个变量名,它意味着不管内容是什么,通通绑定到这个变量中。如:
fn main() {
let a = (1, 2);
let b = 5;
let c = 'c';
let d = "abcd";
}
在match中,可以使用通配符来匹配数值的范围:
fn main() {
let a = 2;
match a {
1..=5 => println!("a is bigger than 1 and smaller than 5"),
_ => println!("other")
}
let b = 'd';
match b {
'a'..='z' => println!("a is bigger than a and smaller than z"),
_ => println!("other")
}
}
match最后一个_
就是占位符,表示除了上面的所有情况,类似于其它语言的default。
前面我们使用let,觉得let和其他语言定义变量并没有太大的区别,但是,let的本质却与其他语言截然不同。它正式的用法是:
let PATTERN = EXPRESSION;
这其实就是在使用模式匹配来对表达式进行解构:
let (x, y) = (1, 2);
这个例子大家都知道是在对元组进行解构,那么,如果要解构的表达式只有一个字面值呢?
let a = 2;
返璞归真,来看Rust的设计理念,这同样是在解构。
在if语句中,可以将let作为判断条件,如:
fn main() {
let a = Some(5);
if let Some(b) = a {
println!("b = {}", b);
}
}
这句话不仅仅是在if里对b作赋值这么简单。它意味着,如果解构成功,则做下面的事情。
和if let的作用差不多,当解构成功时进入循环体:
fn main() {
let mut a = vec![1,2,3,4,5];
while let Some(i) = a.pop() {
println!("i = {}", i);
}
}
在python中,我很喜欢在循环中使用enumerate来一次性得到下标和值,这在Rust中同样可以:
fn main() {
let mut a = vec![1,2,3,4,5];
for (index, value) in a.iter().enumerate() {
println!("{}: {}", index, value);
}
}
有赋值的地方就有模式,这句话同样适用来函数的参数传递:
fn main() {
let a = (1, 2);
foo(a);
}
fn foo((x, y): (i32, i32)) {
println!("x = {}, y = {}", x, y)
}
通过match能完成复杂的模式匹配。
fn main() {
let a = Some(5);
match a {
Some(10) => println!("a is ten"),
Some(b) => println!("a is {}", b),
_ => println!("a is other")
}
}
这个例子不像这前看到的match这么简单,这个例子中,第一个分支显然不能匹配,而第二个分支中,b可以匹配任何值,所以此分支被执行。那么还需要最后的_
吗?答案是必须的,因为match必须是穷尽的。去掉最后一个分支,编译器就会报错:
non-exhaustive patterns: `None` not covered
意思是match没有覆盖a是None的情况。
Some是一个内置的泛型结构体,前面经常使用它来演示对结构体的解构。咱们自己定义的结构体,也是一样的:
struct Point(f32, f32);
fn main() {
let a = Point(1.2, 3.4);
let Point(x, y) = a;
println!("x={}, y={}", x, y);
}
在某些情况下,对结构体进行解构时,只关心其中的某些字段,而不是全部,占位符_
就可以派上用场了:
struct Point(f32, f32);
fn main() {
let a = Point(1.2, 3.4);
let Point(x, _) = a;
println!("x={}", x);
}
可是如果结构体的字段有很多(10个),我只关心第一个,其它的都不关心,要写9个_
吗?写9个_
自然没有问题,不过,下面的写法会更好:
struct Point(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32);
fn main() {
let a = Point(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
let Point(x, ..) = a;
println!("x={}", x);
}
使用..
可以忽略多个字段。但是,..
在一次解构中只有使用一次,它可以放在最前面:
struct Point(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32);
fn main() {
let a = Point(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
let Point(.., x) = a;
println!("x={}", x);
}
但不能这么用:
struct Point(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32);
fn main() {
let a = Point(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
let Point(.., x, ..) = a;
println!("x={}", x);
}
这那编译器并不能知道你关心的到底是哪一个,如果只关心第二个,可以这么写:
struct Point(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32);
fn main() {
let a = Point(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
let Point(_, x, ..) = a;
println!("x={}", x);
}
枚举不能简单的被解构,需要和匹配配合才能完成:
enum Color {
RGB(u8, u8, u8),
CMYK(u8, u8, u8, u8)
}
fn main() {
let a = Color::RGB(123, 234, 0);
match a {
Color::RGB(r, g, b) => println!("r={}, g={}, b={}", r, g, b),
Color::CMYK(c, m, y, k) => println!("c={}, m={}, y={}, k={}", c, m, y, k)
}
}
在match分支中,可以使用|
来匹配多个模式:
fn main() {
let a = 2;
match a {
1 | 2 => println!("a is one or two"),
_ => println!("a is other")
}
}
前面提到过可以使用..=
来进行范围匹配:
fn main() {
let a = 2;
match a {
1..=5 => println!("a is bigger than 1 and smaller than 5"),
_ => println!("other")
}
}
即便值被保存在结构体中,这种方法依然有效:
fn main() {
let a = Some(2);
match a {
Some(1..=5) => println!("a is one or two"),
_ => println!("a is other")
}
}
可是,如果并不能明确1~5的范围,而只是小于5的范围,该如何处理呢?可以为模式附加一个条件,这被称为匹配守卫,如:
fn main() {
let a = 2;
match a {
b if b < 5 => println!("a is smaller than 5"),
_ => println!("a is other")
}
}
值被保存在结构体中,也是一样的:
fn main() {
let a = Some(2);
match a {
Some(b) if b < 5 => println!("a is smaller than 5"),
_ => println!("a is other")
}
}
这些方法可以被结合使用,构建非常复杂的匹配:
fn main() {
let a = 2;
let b = 4;
match a {
1 | 3..=5 if b < 5 => println!("true"),
_ => println!("false")
}
}
在这个例子中,如果a为1或都当b小于5时a在3~5之间,都会打印true。
当匹配结构体时,我们可以对结构体内的某些字段进行限制。如我们有一系列点,要筛选出y=0的:
struct Point{
x: i32,
y: i32
}
fn main() {
let plist = vec![
Point{x:1, y:1},
Point{x:1, y:0},
Point{x:2, y:1},
Point{x:2, y:0},
Point{x:3, y:4},
Point{x:3, y:8},
Point{x:3, y:1},
Point{x:3, y:0},
];
for p in plist.iter() {
match p {
Point{x, y:0} => println!("x={}", x),
_ => continue
}
}
}
当然也可以指定y的范围:
struct Point{
x: i32,
y: i32
}
fn main() {
let plist = vec![
Point{x:1, y:1},
Point{x:1, y:0},
Point{x:2, y:1},
Point{x:2, y:0},
Point{x:3, y:4},
Point{x:3, y:8},
Point{x:3, y:1},
Point{x:3, y:0},
];
for p in plist.iter() {
match p {
Point{x, y:0..=2} => println!("x={}", x),
_ => continue
}
}
}
匹配守卫则需要换一个写法:
struct Point{
x: i32,
y: i32
}
fn main() {
let plist = vec![
Point{x:1, y:1},
Point{x:1, y:0},
Point{x:2, y:1},
Point{x:2, y:0},
Point{x:3, y:4},
Point{x:3, y:8},
Point{x:3, y:1},
Point{x:3, y:0},
];
for p in plist.iter() {
match p {
Point{x, y} if y < &4 => println!("x={}", x),
_ => continue
}
}
}
守卫不能写成Point{x, y if y < &4} => println!("x={}", x),
因为它必须在完成匹配后,再进行二次过滤。因为plist.iter()返回的是&Point,所以4也需要使用引用。