match expression {}
是一个表达式,可以用于变量赋值(各分支返回的值类型必须相同):
- 子句格式:
pattern => { statements; }
, 如果是单条语句则可以省略大括号,如pattern => expression,
- match block 中各子句用逗号分割;(注:函数和闭包的返回值用 -> 分割;)
- expression 可以返回复杂类型,从而实现复杂类型的 pattern 匹配;
enum Direction {
East,
West,
North,
South,
}
fn main() {
let dire = Direction::South;
let result = match dire {
// println!() 返回 ()
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
进行匹配,可用于表达式或条件判断:
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];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}",)
}
}
}
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 */ }
};
pattern ..
:
- 匹配 array/tuple/slice 值时,可以使用 .. 来省略任意数量的元素;
- 对于 struct,使用 .. 来省略未列出的 field 且只能位于最后;
- 只能使用一次 ..;
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),
}
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),
}
let num = Some(4);
let split = 5;
match num {
Some(x) if num < split => assert!(x < split),
Some(x) => assert!(x >= split),
None => (),
}
使用 pattern 解构的场景:
- let 赋值:tuple/slice/struct/enum 等复杂数据类型值的
赋值解构
场景: - 函数或方法或闭包的参数赋值,即传参时也是赋值解构;
- 表达式:if-let,while-let,for,match;
赋值析构的变量 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]);
// 解构嵌套 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:?}");
#[derive(PartialEq, PartialOrd)]
struct Centimeters(f64);
#[derive(Debug)]
struct Inches(i32);
impl Inches {
fn to_centimeters(&self) -> Centimeters {
// let 析构是不可失败模式,& 匹配引用类型,inches 是 i32 类型值。
// 对元组应用的匹配需要在外层进行,而不能是 Inches(&inches)
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 或 move, 通过指定 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);
},
}
// 也可以在声明变量时指定它为 ref 类型
let ref _is_a_reference = 3;
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: _} => (),
}
match a {
None => (),
Some(value) => (), // a 的值被 Copy 或 Moved 到 value 中
}
match a {
None => (),
Some(ref value) => (), // value 是 a 值的引用类型
}
&val 匹配借用,然后将值赋值给 val:
let (a, b) = &v;
这时 a 和 b 都是引用类型, 正确!let &(a, b) = &v;
这时 a 和 b 都是 move 语义.Rust 不允许从引用类型 (不管是共享还是可变)值 move 其中的内容
, 所以如果对应值没有实现 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 类型是在枚举 variant 值外部而非内部来匹配 & 或 &mut;
- 对于 tuple 类型, 也是在 tuple 外部匹配 & 或 &mut;
- &/&mut 匹配共享引用和可变引用, && 或 &&mut 来匹配间接引用: Reference patterns dereference the pointers that are being matched and, thus, borrow them.
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 {
}
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) => (),
_ => (),
}
对于实现了 Deref<Target=U>
的类型 T 值, &*T 返回 &U
类型:
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 可能会产生 partial move:
- struct/enum/tuple 的 field 可以被 partial move,但是 Vec/Array 不能;
- struct/enum/tuple 被 partial move 的字段后续不能再访问,同时 struct 整体也不能被访问, 但是未被 partial move 的字段还可以访问;
// 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 等容器不支持 partial move(如果元素实现了 Copy,则不是 move),解决办法:
- 元素 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:?}");
}