👈 15 面向对象

所有可能会用到模式的位置

match

匹配一个 Option<i32>,如果为 Some(n),返回 Some(n + 1),否则返回 None

let x = Some(2);
match x {
    None => None,
    Some(n) => Some(n + 1),
}

编译器要求 match 进行穷尽匹配,具名变量可以匹配任何模式,匿名变量则丢弃任何模式。

if let

一个复杂的 if let 语句,包含很多可选子句:

fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();
    
    if let Some(color) = favorite_color {
        println!("{color}");
    } else if is_tuesday { // 可以有 else if
        println!("green");
    } else if let Ok(age) = age { // 还可以有 else if let
        if age > 30 { // 不能归入 else if let, 因为 age 只在 {} 内有效
            println!("purple");
        } else {
            println!("orange");
        }
    } else { // 最后, 可以有 else
        println!("blue");
    }
}

编译器不会检查 if let 的穷尽性,适用于不要求穷尽的情况。

while let

类似于 if let,但只要模式匹配就一直执行 while

在栈非空时持续弹出并打印元素:

let mut stk = Vec::new();
stk.push(1);
stk.push(2);
stk.push(3);
while let Some(top) = stk.pop() {
	println!("top={top}");
}

for

解构元组并用于 for

let arr = vec!['a', 'b', 'c'];
for (i, x) in arr.iter().enumerate() {
	println!("arr[{}]={}", i, x);
}

let

这也是模式匹配:

let x = 0;

解构元组:

let (x, y) = (3, 4);

解构元组,用匿名变量 _.. 忽略部分元素:

let (a, b, _) = (6, 2, 0);

函数参数

在函数参数中解构元组:

fn main() {
    let v = (3.0, 4.0);
    assert!((len(&v) - 5.0).abs() <= 0.00001);
}
 
fn len(&(x, y): &(f64, f64)) -> f64 {
    (x * x + y * y).sqrt()
}

可反驳性:模式是否会匹配失效

模式有两种形式:可反驳的(refutable) 和 不可反驳的(irrefutable),称能匹配任何传递的可能值的模式为不可反驳的。

for / let / 函数参数只能接受不可反驳的模式(否则,如果模式不匹配,程序将无法执行),而 if let / while let 对两种形式都能接受,但是通常要接受可反驳的模式才有意义(如果不可反驳,if 就不会失败,while 也不会终止,编译器将发出警告)。

尝试在 let 中使用可反驳的模式:

let Some(x) = Some(1);
println!("x={x}");

视觉上,似乎应该打印 x = 1,但是无法过编:

error[E0005]: refutable pattern in local binding
 --> src\main.rs:2:9
  |
2 |     let Some(x) = Some(1);
  |         ^^^^^^^ pattern `None` not covered
  |
  = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
  = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
  = note: the matched value is of type `Option<i32>`
help: you might want to use `let else` to handle the variant that isn't matched    
  |
2 |     let Some(x) = Some(1) else { todo!() };
  |                           ++++++++++++++++

应该使用 if let

if let Some(x) = Some(1) {
	println!("x={x}");
}

所有的模式语法

match

match x {
	1 => {}                 // 字面值
	2 | 3 => {}             // 多个模式
	1..=10 => {}            // 范围值
	Some(n) => {}           // 命名变量(枚举)
	Point { x: 0, y } => {} // 命名变量(结构体) // 匹配 y 轴上的点
	_ => {}                 // 匿名变量
}

解构

解构结构体:

struct Point {
    x: i32,
    y: i32,
}
 
fn main() {
    let p = Point { x: 0, y: 7 };
    let Point { x, y } = p; // 等效于元组解构 let (x, y) = (0, 7)
    assert_eq!(0, x);
    assert_eq!(7, y);
}

解构枚举:

enum Message {
    Quit,
    Write(String),
    Move { x: i32, y: i32 },
}
 
fn main() {
    let msg = Message::Move { x: 1, y: 2 };
    match msg {
        Message::Quit => {}
        Message::Write(text) => {}
        Message::Move { x, y } => {}
    }
}

忽略模式中的值

_ 忽略全部值

在函数签名中用 _ 忽略某个参数:

fn main() {
    foo(3, 4);
}
 
fn foo(_: i32, y: i32) {
    println!("Only y matters: y={y}");
}

在变量名前用 _ 忽略未使用变量:

fn main() {
	let _x = 5; // no warning
	let y = 10; // warning
}

.. 忽略剩余值

如果有较多值需要用 _ 忽略,可以改用 ..

struct Point {
    x: i32,
    y: i32,
    z: i32,
}
 
fn main() {
    let p = Point { x: 0, y: 0, z: 0 };
    match p {
        Point { x, .. } => println!("x={x}"),
    }
}

在不产生歧义的前提下,.. 将自动扩展为所需要的值的数量:

fn main() {
    let arr = (2, 4, 8, 16, 32);
    match arr {
        (first, .., last) => {
            println!("first={first}, last={last}");
        }
    }
}

匹配守卫提供的额外条件

匹配守卫(match guard) 是 match 的模式分支内额外的 if,进门(匹配 match 分支)后,还要通过守卫的检查(满足 if),才能执行分支:

let num = Some(4);
match num {
	Some(x) if x % 2 == 0 => println!("{x} is even"),
	Some(x) => println!("{x} is odd"),
	None => (),
}

也可以同时使用 | 指定多个模式:

let x = 4;
let y = false;
match x {
	4 | 5 | 6 if y => println!("yes"),
	_ => println!("no"),
}

@ 绑定

@:创建一个存放值的变量的同时测试其值是否匹配模式。

enum Message {
    Hello { id: i32 },
}
 
fn main() {
    let msg = Message::Hello { id: 5 };
    match msg {
        Message::Hello {
            id: id_variable @ 3..=7,
        } => println!("Id in [3,7]: {id_variable}"),
        Message::Hello { id: 10..=12 } => {
            println!("Id is in [10,12]")
        }
        Message::Hello { id } => println!("Invalid Id: {id}"),
    }
}

id_variable 的命名是为了演示用,其实它可以与字段 id 同名。

👉 17 高级特征