详解Rust的模式

tech2023-11-10  89

文章目录

详解Rust的模式什么是模式哪里能用到模式letif letwhile letfor函数参数match 解构进阶解构结构体解构枚举 匹配进阶匹配多个模式匹配值的范围匹配结构体

详解Rust的模式

模式是学习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的本质却与其他语言截然不同。它正式的用法是:

let PATTERN = EXPRESSION;

这其实就是在使用模式匹配来对表达式进行解构:

let (x, y) = (1, 2);

这个例子大家都知道是在对元组进行解构,那么,如果要解构的表达式只有一个字面值呢?

let a = 2;

返璞归真,来看Rust的设计理念,这同样是在解构。

if let

在if语句中,可以将let作为判断条件,如:

fn main() { let a = Some(5); if let Some(b) = a { println!("b = {}", b); } }

这句话不仅仅是在if里对b作赋值这么简单。它意味着,如果解构成功,则做下面的事情。

while let

和if let的作用差不多,当解构成功时进入循环体:

fn main() { let mut a = vec![1,2,3,4,5]; while let Some(i) = a.pop() { println!("i = {}", i); } }

for

在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

通过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也需要使用引用。

最新回复(0)