Wang's blog

十四、模式与匹配

Published on

模式是Rust的一种特殊语法,用于对类型的结构进行匹配。一个模式由若干以下项组成:

  • 字面值
  • 解构的数组、枚举、结构体或元组
  • 变量
  • 通配符
  • 占位符

在模式有效的范围内,它描述了数据的形状。程序将值与模式进行匹配,以确定它是否有正确的形状,并决定是否进一步执行一段代码。

可以使用模式的位置

match分支

在match中使用模式要求穷举所有可能性,一种保证穷举的方法是在最后一个分支处理所有剩余所有可能性。使用_可以匹配任何可能性并且不绑定值,所以一般用在最后一个分支。

match x {
    None => None,
    Some(i) => Some(i + 1),
}

if-let条件表达式

if-let/else-if/else-if-let可以混用。使用if-let的缺点是如果不在最后使用else,则不能穷举所有情况。

if let Some(color) = favorite_color {
    println!("Using your favorite color, {color}, as the background");
} else if is_tuesday {
    println!("Tuesday is green day!");
} else if let Ok(age) = age {
    if age > 30 {
        println!("Using purple as the background color");
    } else {
        println!("Using orange as the background color");
    }
} else {
    println!("Using blue as the background color");
}

while-let条件循环

while let Some(top) = stack.pop() {
    println!("{}", top);
}

for循环

for (index, value) in v.iter().enumerate() {
    println!("{} is at index {}", value, index);
}

let语句

// 模式与值匹配,进行变量绑定
let (x, y, z) = (1, 2, 3);

// 匹配失败
let (x, y) = (1, 2, 3);

函数参数

函数(或闭包)的参数实际上也是模式。

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

可否认性

模式分为可否认与不可否认两类,可匹配所有可能性的模式为不可否认模式,可能匹配失败的模式为可否认模式。

// 不可否认,x可匹配任何值
let x = 5;

// 可否认,a_value为None时失败
if let Some(x) = a_value

函数参数、let语句和for循环只接受不可否认模式,if-let和while-let表达式接受可否认与不可否认模式。

  • 在需要使用不可否认模式时,如果使用可否认模式,编译器会报错,因为未处理所有情况。如果必须使用,可改为if-let语句
  • 在需要使用可否认模式时,如果使用不可否认模式,编译器会给出警告,因为条件语句的目的就是处理可能失败的情况
  • 在match语句中,除最后一个分支外,应使用可否认模式,最后一个分支应使用不可否认模式以处理剩余所有情况
  • match语句可以只有一个使用不可否认模式的分支,但是使用let语句更加简洁

模式语法

匹配字面值

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
}

匹配命名变量

命名变量是可以匹配任何值的不可否认模式。由于match开始了一个新块,所以match语句中的变量是新的变量。

match x {
    Some(50) => println!("Got 50"),
    Some(y) => println!("Matched, y = {y}"),
    _ => println!("Default case, x = {:?}", x),
}

匹配多个模式

在match语句中,可以使用|(模式或操作符)匹配多个模式。

match x {
    1 | 2 => println!("one or two"),
    3 => println!("three"),
    _ => println!("anything"),
}

匹配范围

可以使用..=语法匹配一个范围内的值。只能用在数值类型与字符类型上。

match x {
    1..=5 => println!("one through five"),
    _ => println!("something else"),
}

解构

解构元组

let triple = (0, -2, 3);
match triple {
    // 解构第2个和第3个元素
    (0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z),
    // `..`表示忽略其它值
    (1, ..)  => println!("First is `1` and the rest doesn't matter"),
    (.., 2)  => println!("last is `2` and the rest doesn't matter"),
    (3, .., 4)  => println!("First is `3`, last is `4`, and the rest doesn't matter"),
    // `_`表示其它所有情况
    _      => println!("It doesn't matter what they are"),
}

解构数组/切片

let array = [1, -2, 6];
match array {
    // 绑定第2个和第3个元素
    [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
    ),
    // 将剩余值保存在另一个数组或切片中
    [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
    ),
}

解构指针/引用

对于指针,解构与解引是两种不同的操作:

  • 解引使用*
  • 解构使用&,ref与ref mut
// 解构引用,使用&
match reference {
    &val => println!("Got a value via destructuring: {:?}", val),
}

// 先解引再解构
match *reference {
    val => println!("Got a value via dereferencing: {:?}", val),
}

// 使用ref
match value {
    ref r => println!("Got a reference to a value: {:?}", r),
}

// 使用ref mut
match mut_value {
    ref mut m => {
        // 解引后使用
        *m += 10;
        println!("We added 10. `mut_value`: {:?}", m);
    },
}

解构结构体

struct Point {
    x: i32,
    y: i32,
}

let p = Point { x: 0, y: 7 };

// 将x,y解构至变量a,b
let Point { x: a, y: b } = p;

// 简化语法,相当于:let Point { x: x, y: y } = p;
let Point { x, y } = p;

// 使用字面值用于条件判断
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),
}

let msg = Message::ChangeColor(0, 160, 255);

match msg {
    // 无变量分支
    Message::Quit => {
        println!("The Quit variant has no data to destructure.");
    }
    // 含有类结构体数据的分支
    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),
}

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}")
    }
    _ => (),
}

解构嵌套结构体和元组

let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });

在模式中忽略值

使用_忽略一个值

fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {}", y);
}

使用嵌套_忽略部分值

let mut setting_value = Some(5);
let new_setting_value = Some(10);

match (setting_value, new_setting_value) {
    (Some(_), Some(_)) => {
        println!("Can't overwrite an existing customized value");
    }
    _ => {
        setting_value = new_setting_value;
    }
}

忽略使用_开始的变量

let _x = 5;

// 虽然_s没有使用,但是变量s仍然被移动至_s
if let Some(_s) = s {
    println!("found a string");
}

// 没有进行绑定,s没有被移动
if let Some(_) = s {
    println!("found a string");
}

使用..忽略剩余值

match origin {
    Point { x, .. } => println!("x is {}", x),
}

match numbers {
    (first, .., last) => {
        println!("Some numbers: {first}, {last}");
    }
}

使用match guards添加额外条件

match guard是在match分支后面的if条件,必须同时满足该条件才能执行该分支。需要注意编译器在检查是否覆盖所有模式时并不会考虑match guard。

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

使用@进行绑定

一些情况下分支无法直接使用变量值,需要使用@进行绑定。

match age() {
    0             => println!("I haven't celebrated my first birthday yet"),
    // 不绑定则无法获得age的值
    n @ 1  ..= 12 => println!("I'm a child of age {:?}", n),
    n @ 13 ..= 19 => println!("I'm a teen of age {:?}", n),
    n             => println!("I'm an old person of age {:?}", n),
}