Rust 学习笔记(38)-模式匹配

参考章节《Rust程序设计语言》第18.3章 模式语法

模式是 Rust 中特殊的语法,它用来匹配类型中的结构,无论类型是简单还是复杂。

我们先来看看模式匹配的语法

  1. 匹配字面值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let x = 1;

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

这段代码会打印 one 因为 x 的值是 1

  1. 匹配命名变量
1
2
3
4
5
6
7
8
9
fn main() {
    let x = Some(5);

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

第一个匹配分支的模式并不匹配 x 中定义的值,所以代码继续执行。
第二个匹配分支中的模式引入了一个新变量 y,它会匹配任何 Some 中的值。
因此这个 y 绑定了 xSome 内部的值。这个值是 5,所以这个分支的表达式将会执行并打印出 Matched, y = 5

  1. 匹配多个模式
1
2
3
4
5
6
7
8
9
fn main() {
    let x = 1;

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

match 表达式中,可以使用 | 语法匹配多个模式,它代表 或(or)的意思。上面的代码会打印 one or two

  1. 范围匹配
1
2
3
4
5
6
7
8
fn main() {
    let x = 5;

    match x {
        1..=5 => println!("one through five"), // 表示匹配 1 到 5 的值
        _ => println!("something else"),
    }
}

..= 语法允许你匹配一个闭区间范围内的值。 如果 x1、2、3、45,第一个分支就会匹配。这相比使用 | 运算符表达相同的意思更为方便

范围只允许用于数字char 值,因为编译器会在编译时检查范围不为空。
char数字值是 Rust 仅有的可以判断范围是否为空的类型。

  1. 解构结构体
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x: a, y: b } = p; // 将结构体中 x 和 y 的值,解构到变量 a 和 b 中
    assert_eq!(0, a);
    assert_eq!(7, b);
}

这段代码创建了变量 ab 来匹配结构体 p 中的 xy 字段。模式中的变量名不必与结构体中的字段名一致。

  • 一种简写形式
1
2
3
4
5
6
7
8
9
// 省略结构体定义

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x, y } = p; // 将结构体中 x 和 y 的值,解构到新变量 x 和 y 中
    assert_eq!(0, x);
    assert_eq!(7, y);
}

只需列出结构体字段的名称,则模式创建的变量会有相同的名称。

  • 还可以利用 match 匹配不同情况
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 省略结构体定义

fn main() {
    let p = Point { x: 0, y: 7 };

    match p {
        Point { x, y: 0 } => println!("On the x axis at {}", x), // y === 0 的情况
        Point { x: 0, y } => println!("On the y axis at {}", y), // x === 0 的情况
        Point { x, y } => println!("On neither axis: ({}, {})", x, y), // 其他
    }
}
  1. 解构枚举
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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.")
        }
        Message::Move { x, y } => {
            println!("Move in the x direction {} and in the y direction {}", x, y);
        }
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => {
            println!("Change the color to red {}, green {}, and blue {}", r, g, b)
        }
    }
}

一个细节是解构枚举的模式需要对应枚举所定义的储存数据的方式

  • 解构嵌套的枚举
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color), // 嵌套一个 Color 枚举
}

fn main() {
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!("Change the color to red {}, green {}, and blue {}", r, g, b)
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => println!(
            "Change the color to hue {}, saturation {}, and value {}",
            h, s, v
        ),
        _ => (),
    }
}
  1. 忽略模式中的值
  • 在函数签名中使用 _
1
2
3
4
5
6
7
fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {}", y);
}

fn main() {
    foo(3, 4);
}

这段代码会完全忽略作为第一个参数传递的值 3,并会打印出 This code only uses the y parameter: 4

  • 也可以在一个模式内部使用 _ 忽略部分值
1
2
3
4
5
6
7
8
9
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, _, third, _, fifth) => {
            println!("Some numbers: {}, {}, {}", first, third, fifth)
        }
    }
}

这会打印出 Some numbers: 2, 8, 32, 值 416 会被忽略。

  • 通过在名字前以一个下划线开头来忽略未使用的变量
1
2
3
4
fn main() {
    let _x = 5;
    let y = 10;
}

这会得到警告说未使用变量 y,不过没有警告说未使用下划线开头的变量。

  1. .. 忽略剩余值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct Point {
    x: i32,
    y: i32,
    z: i32,
}

fn main() {
    let origin = Point { x: 0, y: 0, z: 0 };

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

对于有多个部分的值,可以使用 .. 语法来只使用部分并忽略其它值,避免不得不每一个忽略值列出下划线。

  • .. 会扩展为所需要的值的数量。
1
2
3
4
5
6
7
8
9
fn main() {
    let numbers = (2, 4, 8, 16, 32);

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

这里用 firstlast 来匹配第一个和最后一个值。而 .. 将匹配并忽略中间的所有值。

  • .. 匹配必须是 无歧义的
1
2
3
4
5
6
7
8
9
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {}", second)
        },
    }
}

上面的例子会报错,因为它是 有歧义的,Rust 不可能决定在元组中匹配 second 值之前应该忽略多少个值,以及在之后忽略多少个值。

  1. 利用匹配守卫提供的额外条件

匹配守卫(match guard)是一个指定于 match 分支模式之后的额外 if 条件,它也必须被满足才能选择此分支。

1
2
3
4
5
6
7
8
9
fn main() {
    let num = Some(4);

    match num {
        Some(x) if x < 5 => println!("less than five: {}", x),
        Some(x) => println!("{}", x),
        None => (),
    }
}

上面的例子会打印出 less than five: 4。当 num 与模式中第一个分支比较时,因为 Some(4) 匹配 Some(x) 所以可以匹配。
接着匹配守卫检查 x 值是否小于 5,因为 4 小于 5,所以第一个分支被选择。

  1. @ 绑定

at 运算符(@)允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
enum Message {
    Hello { id: i32 },
}

fn main() {
    let msg = Message::Hello { id: 5 };

    match msg {
        Message::Hello {
            id: id_variable @ 3..=7, // 测试 Message::Hello 的 id 字段是否位于 3..=7 范围内,同时也希望能将其值绑定到 id_variable 变量中,以便此分支相关联的代码可以使用它。
        } => println!("Found an id in range: {}", id_variable),
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        }
        Message::Hello { id } => println!("Found some other id: {}", id),
    }
}

上面的例子会打印出 Found an id in range: 5。通过在 3..=7 之前指定 id_variable @,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。

使用 @ 可以在一个模式中同时测试和保存变量值。

总结一下

模式匹配非常多,我的建议是,先看2 - 3遍,混个脸熟,使用时当作工具书来查,用的多了自然就记住了。

updatedupdated2025-03-012025-03-01