模式匹配 match expression {}
是一个表达式,可用于变量赋值:
- expression 结果可以是各种复杂类型, 如 struct、enum 等;
- 各 branch 是逗号分割的
pattern => { statements; }
。如果是单条语句,则可以省略大括号,如pattern => expression,
; - 各 branch 返回值类型必须相同;
enum Direction {
East,
West,
North,
South,
}
fn main() {
let dire = Direction::South;
let result = match dire {
// println!() 返回 () 单条语句,故不需要大括号。
// 各 match branch 以逗号结尾。
Direction::East => println!("East"),
_ => {
// 也返回 ()
Ok(1);
}
}; // let 赋值语句结尾的分号不能省!
println!("{result}")
}
// pattern 引入了新变量,可能 shadow 以前同名的变量。
fn main() {
let age = Some(30);
if let Some(age) = age {
// age shadow 前面的同名变量
assert_eq!(age, 30);
} // 新 age 变量被 drop
match age {
// match 的各子句也会创建新变量,可能会 shadow 以前的同名变量
Some(age) => println!("age is a new variable, it's value is {}",age),
_ => ()
}
}
let mut setting_value = Some(5);
let new_setting_value = Some(10);
// tuple 类型的 expression
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
// _ 可以匹配任何类型值
_ => {
setting_value = new_setting_value;
}
}
matches!(express, pattern) 宏
(match 是关键字)将 express value 和 pattern 进行匹配,返回 bool 值:
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));
match branch 表达式提前返回时,返回值类型是 !
, 而 !可以自动转换为任意类型 ,所以满足各 match branch 返回相同类型值的要求:
use std::num::ParseIntError;
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
let first_number = match first_number_str.parse::<i32>() {
Ok(first_number) => first_number,
Err(e) => return Err(e), // branch 提前返回,对应的返回值和类型均是 !
};
let second_number = match second_number_str.parse::<i32>() {
Ok(second_number) => second_number,
Err(e) => return Err(e),
};
Ok(first_number * second_number)
}
fn print(result: Result<i32, ParseIntError>) {
match result {
Ok(n) => println!("n is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
print(multiply("10", "2"));
print(multiply("t", "2"));
}
match pattern 语法:
字面量
:100,字符串,bool,char;range
:0..=100, ‘a’..=‘z’, b’a’..=b’c’;(Rust 1.80 开始支持 exclusive range)|
:分割多个 pattern;_
:匹配任何值;@
:匹配并定义一个变量,如[email protected], y@(1|2|3), y@..
,匹配后 y 是一个包含匹配值的变量;枚举
:Some(value), None, Ok(value), Err(err)
;变量
:name, mut name, ref name, ref mut name
, 其中 ref/mut 用来指定变量 name 是引用类型(不会有所有权转移);tuple
:(key, value), (r, g, b), (r, g, 12)
;array/slice
:[a, b, c], [a, b, 1],[a, .., b], [a, _, b]
;
- 对于 Vec,需要先转换为 slice 再匹配(可以直接匹配 [T], 而不需要 &[T])。
struct
:必须列出每一个 field,可以使用 .. 来忽略部分 field;匹配引用
:&value, &(k, v)
,& 用于匹配表达式结果,value/k/v 是解引用后的值;guard expression
:4|5|6 if x < 2
,表达式是针对整个 pattern,等效于(4|5|6) if x < 2
;
let x = 9;
let message = match x {
0 | 1 => "not many",
2 ..= 9 => "a few",
_ => "lots"
};
struct S(i32, i32);
match S(1, 2) {
// _ 在 slice/array/tuple 中用于忽略指定位置的元素
S(z @ 1, _) | S(_, z @ 2) => assert_eq!(z, 1),
_ => panic!(),
}
let p = Point { x: 0, y: 7 };
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})");}
}
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.");
}
// 必须列出并匹配 enum variant 的各个字段值
Message::Move { x, y } => {
println!("Move in the x direction {x} and in the y direction {y}");
}
Message::Write(text) => {
println!("Text message: {text}");
}
Message::ChangeColor(r, g, b) => {
println!("Change the color to red {r}, green {g}, and blue {b}",)
}
}
}
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 3, y: 10};
match p {
// y 值用来做匹配判断
Point { x, y: 0 } => println!("On the x axis at {}", x),
// y: yy@(xx) 是将 y 与 xx 匹配, 如果满足,匹配的值被设置给变量 yy
Point { x: 0..=5, y: yy@ (10 | 20 | 30) } => println!("On the y axis at {}", yy),
Point { x, y } => println!("On neither axis: ({}, {})", x, y),
}
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 color to red {r}, green {g}, and blue {b}"); }
Message::ChangeColor(Color::Hsv(h, s, v)) => { println!("Change color to hue {h}, saturation {s}, value {v}") }
_ => (),
}
}
// (xx) 匹配 tuple
let numbers = (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048);
match numbers {
// ERROR: pattern 中最多只能包含一个 ..
// (first, .., 16, .., 1024, last) => {
// assert_eq!(first, 2);
// assert_eq!(last, 2048);
// }
// OK
(first, .., 1024, last) => {
assert_eq!(first, 2);
assert_eq!(last, 2048);
}
// OK
(first, .., last) => {
assert_eq!(first, 2);
assert_eq!(last, 2048);
}
}
// [xx] 匹配 Array/Slice
let array = [1, -2, 6];
match array {
[0, second, third] => println!("array[0] = 0, array[1] = {}, array[2] = {}", second, third),
[1, _, third] => println!( "array[0] = 1, array[2] = {} and array[1] was ignored", third ),
[-1, second, ..] => println!("array[0] = -1, array[1] = {} and all the other ones were ignored",second ),
// The code below would not compile
// [-1, second] => ...
// 匹配后,tail 是一个包含匹配值的变量。
[3, second, tail @ ..] => println!( "array[0] = 3, array[1] = {} and the other elements were {:?}", second, tail ),
[first, middle@ .., last] => println!("array[0] = {}, middle = {:?}, array[2] = {}", first, middle, last),
}
Vec 需要先转换为 Slice 再进行匹配:
let v = vec![1, 2, 3];
match v[..] {
[a, b] => { /* this arm will not apply because the length doesn't match */ }
[a, b, c] => { /* this arm will apply */ }
_ => { /* this wildcard is required, since the length is not known statically */ }
};
..
:
- 匹配 array/tuple/slice 值时,使用
..
来省略任意数量的元素; - 对于 struct,使用 .. 来省略未列出的 fields,且只能位于最后;
- 只能使用一次 ..;
struct Foo {
x: (u32, u32),
y: u32,
}
let foo = Foo { x: (1, 2), y: 3 };
match foo {
Foo { x: (1, b), y } => println!("First of x is 1, b = {}, y = {} ", b, y),
// field 顺序无关
Foo { y: 2, x: i } => println!("y is 2, i = {:?}", i),
// 忽略未列出的其它 field,必须位于最后
Foo { y, .. } => println!("y = {}, we don't care about x", y),
// 错误:未列出 field `x`
//Foo { y } => println!("y = {}", y),
}
使用 pattern 解构的场景:
- let 赋值:tuple/slice/struct/enum 等复杂数据类型值的赋值解构;
- 函数或方法或闭包的传参赋值;
- 表达式:if-let,while-let,for,matches!();
赋值析构的变量 scope 是所在 block,新的变量 by-ref/by-mov/by-copy 对应的值:
- if-let/while-let/match/matches! 的析构匹配时可失败模式;
- 变量赋值解构(let)、函数传参、for 循环解构是不可失败模式。
- 但 let 赋值支持析构失败时的 else 语句;
// 变量赋值和函数传参,使用 match pattern 进行解构,是不能失败的匹配。
let faa = Foo { x: (1, 2), y: 3 };
let Foo { x : x0, y: y0 } = faa; // x0 或 y0 是创建的新变量。
let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
let Some(caps) = re.captures(hay) else { return };
assert_eq!("J", &caps[1]);
// 解构时使用 _ 来忽略特定字段
let (_, right) = slice.split_at(middle);
fn print_number(n: Number) {
if let Number { odd: true, value } = n {
println!("Odd number: {}", value);
} else if let Number { odd: false, value } = n {
println!("Even number: {}", value);
}
}
fn print_number(n: Number) {
match n {
Number { odd: true, value } => println!("Odd number: {}", value),
Number { odd: false, value } => println!("Even number: {}", value),
}
}
fn print_number(n: Number) {
match n {
Number { value: 1, .. } => println!("One"),
Number { value: 2, .. } => println!("Two"),
Number { value, .. } => println!("{}", value),
// if that last arm didn't exist, we would get a compile-time error
}
}
// 解构嵌套 struct
struct Bar { foo: Foo,}
let bar = Bar { foo: faa };
let Bar { foo: Foo { x: nested_x, y: nested_y } } = bar;
println!("Nested: nested_x = {nested_x:?}, nested_y = {nested_y:?}");
let v = Vec2 { x: 3.0, y: 6.0 };
let Vec2 { x, y } = v;
// `x` is now 3.0, `y` is now `6.0`
let Vec2 { x, .. } = v;
// this throws away `v.y`
#[derive(PartialEq, PartialOrd)]
struct Centimeters(f64);
#[derive(Debug)]
struct Inches(i32);
impl Inches {
fn to_centimeters(&self) -> Centimeters {
// let 析构是不可失败模式,& 匹配引用类型,inches 是 i32 类型值。
// 对元组借用的匹配需要在外层进行,而不能是 Inches(&inches)(enum 也是类似的在外层匹配借用)
let &Inches(inches) = self;
Centimeters(inches as f64 * 2.54)
}
}
// 函数参数使用 pattern 进行赋值解构, 元组引用的匹配需要在外层进行。
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
// 参数名称可以是 _
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {}", y);
}
ref/ref mut
:析构时是先匹配再定义变量, 值如果没有实现 Copy 则默认是转移到变量, 通过指定 ref 或 ref mut,表示变量是引用或可变引用类型:
let value = 5;
let mut mut_value = 6;
match value {
// 匹配后,r 是 value 的引用类型
ref r => println!("Got a reference to a value: {:?}", r),
}
match mut_value {
// 匹配后,m 是 &mut 类型变量
ref mut m => {
*m += 10;
println!("We added 10. `mut_value`: {:?}", m);
},
}
match a {
None => (),
Some(value) => (), // a 的值被 Copy 或 Moved 到 value 中
}
match a {
None => (),
Some(ref value) => (), // value 是 a 值的引用类型
}
// 对 struct 部分成员使用 ref/ref mut,否则在成员类型没有实现 Copy 时,默认是转移值到变量
match struct_value {
Struct{a: 10, b: 'X', c: false} => (),
Struct{a: 10, b: 'X', ref c} => (),
Struct{a: 10, b: 'X', ref mut c} => (),
Struct{a: 10, b: 'X', c: _} => (),
Struct{a: _, b: _, c: _} => (),
}
// 也可以在声明变量时指定它为 ref 类型
let ref _is_a_reference = 3;
&val
: 匹配借用,然后将值赋值给 val:
let (a, b) = &v;
: a 和 b 都是引用类型, 正确!let &(a, b) = &v;
: a 和 b 都是 move 语义。- Rust 不允许从借用(不管是 &T 还是 &mut T)转移内部对象, 所以如果对应值没有实现 Copy 则出错;
let reference = &4;
match reference {
&val => println!("Got a value via destructuring: {:?}", val),
}
// pattern 中 & 不能用于 field value:
if let Person { name: &person_name, age: 18..=150 } = value { } // 错误
if let Person {name: ref person_name, age: 18..=150 } = value { } // 正确
注意:
- 对于 enum 和 tuple 类型是在枚举 variant 值或 tuple 值外部,而非内部,来匹配 & 或 &mut;
- &/&mut 匹配共享引用和可变引用, && 或 &&mut 来匹配间接引用:
let x: &Option<i32> = &Some(3);
// OK: 等效为 Some(ref y), y 的类型是 &i32
if let Some(y) = x {
}
// OK: 在 variant 外指定 &, y 的类型是 i32, y 类型必须实现 Copy
if let &Some(y) = x {
}
// ERROR: 不能在 variant 内指定 &:expected `i32`, found `&_`
if let Some(&y) = x {
}
fn printf_hellov2(say_hello: Option<&Box<dyn MyTrait + Send + 'static>>) {
// 错误:
// 1. 不能通过借用(共享或可变)来转移对象,my_trait 是 Box<dyn MyTrait + Send + 'static> 类型,转移了 Box 对象所有权
// 2. 对于 enum ,需要在外部而非内部排匹配 & 或 &mut
// error[E0507]: cannot move out of `*say_hello` as enum variant `Some` which is behind a shared reference
// if let Some(&my_trait) = say_hello {
if let Some(my_trait) = say_hello {
my_trait.say_hello();
}
}
enum MyEnum {
A { name: String, x: u8 },
B { name: String },
}
fn a_to_b(e: &mut MyEnum) {
// &mut String 类型
if let MyEnum::A { name, x: 0, } = e {
// name 是 &mut String 类型。
// take() 参数类型是 &mut T, 将 name 设置为 T 缺省值,返回 T 值
*e = MyEnum::B { name: std::mem::take(name), }
}
// OK: name 是 String 类型
if let &mut MyEnum::A { name, x: 0, } = e {
// ...
}
}
let (a, b ) = &(1, 2); // a 和 b 都是 &i32 类型
let &(c, d ) = &(1, 2); // c 和 d 都是 i32 类型, c/d 都必须实现 Copy
let (&c, d ) = &(1, 2); // Error
let (ref c, d ) = &(1, 2); // OK
// Rust 字面量也支持引用
let int_reference = &3;
let a = match *int_reference { 0 => "zero", _ => "some" };
let b = match int_reference { &0 => "zero", _ => "some" };
assert_eq!(a, b);
let int_reference = &3;
match int_reference {
&(0..=5) => (),
_ => (),
}
如果 T 类型实现实现了 Deref<Target=U>
,则 &*T
返回 &U
类型,常用于从智能指针类型值,如 Box、Rc、Arc 等返回内部的对象类型:
- 对于泛型函数参数, Rust 传参时不会进行 type coercion 或自动 Deref 来满足 trait bound 的要求,这时需要使用
&*T
;
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
// Mutex 和 Condvar 都具有内部可变性,所以可以通过 pair 和 pair2 的共享引用来进行修改。
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = Arc::clone(&pair);
thread::spawn(move|| {
// 这里 &*pair2 返回 &(), 赋值解构后, lock 是 &Mutex, cvar 是 &Convar。
//
// 不能使用 let &(lock, cvar) = &*pair2; 这会导致 pair2 中的值发生移动(lock 是 Mutex, cvar 是 Convar),
// 由于 pair2 和 pair 是共享底层的对象, 所以移动时出错。
let (lock, cvar) = &*pair2;
let mut started = lock.lock().unwrap();
*started = true;
cvar.notify_one();
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
pattern match 可能会产生部分转移:
- struct/enum/tuple 的 field 可以被部分转移,但是 Vec/Array/Slice 不能;
- struct/enum/tuple 被部分转移的字段后续不能再访问,同时 struct 整体也不能被访问, 但是未被部分转移的字段还可以访问;
// https://practice.course.rs/ownership/ownership.html
#[derive(Debug)]
struct Person {
name: String,
age: Box<u8>,
}
let person = Person {
name: String::from("Alice"),
age: Box::new(20),
};
// name 从 person 中 move,age 引用 person 中 age 值,所以 person 被 partial move。
let Person { name, ref age } = person;
// Error:person 被 partial move 后,不能再整体访问 person
//println!("The person struct is {:?}", person);
// OK:可以访问 person 未被 move 的字段
println!("The person's age from person struct is {}", person.age);
Vec/Slice/Array 等容器不支持部分转移(如果元素类型实现了 Copy,则不是转移),解决办法:
- 元素 clone();
- 如果能获得元素的 &mut 引用,可以使用
std::mem:take()/std:mem::replace()
来返回对应元素;
let mut data = vec!["abc".to_string()];
// Error: move occurs because value has type `String`, which does not implement the `Copy` trait
// let e = data[0];
// Vec 不能被解构,需要转换为 &[T] 后才能解构。
// Error: pattern cannot match with input type `Vec<String>`
//let [a] = data;
// Error: move occurs because `a` has type `String`, which does not implement the `Copy` trait
// if let [a] = data[..] {
// println!("Results: {a:?}");
// }
// Error: move occurs because `a` has type `String`, which does not implement the `Copy` trait
// if let &[a] = &data[..] {
// println!("Results: {a:?}");
// }
// OK: a 是 &String 类型, 引用的是 data 中的元素。
if let [a] = &data[..] {
println!("Results: {a:?}");
}