模式适用场景
模式
模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,它往往和 match
表达式联用,以实现强大的模式匹配能力。模式一般由以下内容组合而成:
- 字面值
- 解构的数组、枚举、结构体或者元组
- 变量
- 通配符
- 占位符
所有可能用到模式的地方
match 分支
#![allow(unused)] fn main() { match VALUE { PATTERN => EXPRESSION, PATTERN => EXPRESSION, PATTERN => EXPRESSION, } }
如上所示,match
的每个分支就是一个模式,因为 match
匹配是穷尽式的,因此我们往往需要一个特殊的模式 _
,来匹配剩余的所有情况:
#![allow(unused)] fn main() { match VALUE { PATTERN => EXPRESSION, PATTERN => EXPRESSION, _ => EXPRESSION, } }
if let 分支
if let
往往用于匹配一个模式,而忽略剩下的所有模式的场景:
#![allow(unused)] fn main() { if let PATTERN = SOME_VALUE { } }
while let 条件循环
一个与 if let
类似的结构是 while let
条件循环,它允许只要模式匹配就一直进行 while
循环。下面展示了一个使用 while let
的例子:
#![allow(unused)] 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); } }
这个例子会打印出 3
、2
接着是 1
。pop
方法取出动态数组的最后一个元素并返回 Some(value)
,如果动态数组是空的,将返回 None
,对于 while
来说,只要 pop
返回 Some
就会一直不停的循环。一旦其返回 None
,while
循环停止。我们可以使用 while let
来弹出栈中的每一个元素。
你也可以用 loop
+ if let
或者 match
来实现这个功能,但是会更加啰嗦。
for 循环
#![allow(unused)] 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 语句
#![allow(unused)] fn main() { let PATTERN = EXPRESSION; }
是的, 该语句我们已经用了无数次了,它也是一种模式匹配:
#![allow(unused)] fn main() { let x = 5; }
这其中,x
也是一种模式绑定,代表将匹配的值绑定到变量 x 上。因此,在 Rust 中,变量名也是一种模式,只不过它比较朴素很不起眼罢了。
#![allow(unused)] fn main() { let (x, y, z) = (1, 2, 3); }
上面将一个元组与模式进行匹配(模式和值的类型必需相同!),然后把 1, 2, 3
分别绑定到 x, y, z
上。
模式匹配要求两边的类型必须相同,否则就会导致下面的报错:
#![allow(unused)] fn main() { let (x, y) = (1, 2, 3); }
#![allow(unused)] fn main() { error[E0308]: mismatched types --> src/main.rs:4:5 | 4 | let (x, y) = (1, 2, 3); | ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})` | | | expected a tuple with 3 elements, found one with 2 elements | = note: expected tuple `({integer}, {integer}, {integer})` found tuple `(_, _)` For more information about this error, try `rustc --explain E0308`. error: could not compile `playground` due to previous error }
对于元组来说,元素个数也是类型的一部分!
函数参数
函数参数也是模式:
#![allow(unused)] fn main() { fn foo(x: i32) { // 代码 } }
其中 x
就是一个模式,你还可以在参数中匹配元组:
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 和 if let
对于以下代码,编译器会报错:
#![allow(unused)] fn main() { let Some(x) = some_option_value; }
因为右边的值可能不为 Some
,而是 None
,这种时候就不能进行匹配,也就是上面的代码遗漏了 None
的匹配。
类似 let
, for
和match
都必须要求完全覆盖匹配,才能通过编译( 不可驳模式匹配 )。
但是对于 if let
,就可以这样使用:
#![allow(unused)] fn main() { if let Some(x) = some_option_value { println!("{}", x); } }
因为 if let
允许匹配一种模式,而忽略其余的模式( 可驳模式匹配 )。
let-else(Rust 1.65 新增)
使用 let-else
匹配,即可使 let
变为可驳模式。它可以使用 else
分支来处理模式不匹配的情况,但是 else
分支中必须用发散的代码块处理(例如:break
、return
、panic
)。请看下面的代码:
use std::str::FromStr; fn get_count_item(s: &str) -> (u64, &str) { let mut it = s.split(' '); let (Some(count_str), Some(item)) = (it.next(), it.next()) else { panic!("Can't segment count item pair: '{s}'"); }; let Ok(count) = u64::from_str(count_str) else { panic!("Can't parse integer: '{count_str}'"); }; // error: `else` clause of `let...else` does not diverge // let Ok(count) = u64::from_str(count_str) else { 0 }; (count, item) } fn main() { assert_eq!(get_count_item("3 chairs"), (3, "chairs")); }
与 match
和 if let
相比,let-else
的一个显著特点在于其解包成功时所创建的变量具有更广的作用域。在 let-else
语句中,成功匹配后的变量不再仅限于特定分支内使用:
#![allow(unused)] fn main() { // if let if let Some(x) = some_option_value { println!("{}", x); } // let-else let Some(x) = some_option_value else { return; } println!("{}", x); }
在上面的例子中,if let
写法里的 x
只能在 if
分支内使用,而 let-else
写法里的 x
则可以在 let
之外使用。