参考:https://kaisery.github.io/trpl-zh-cn/ch18-00-patterns.html
模式是 Rust 中特殊的语法,它用来匹配类型中的结构,无论类型是简单还是复杂。结合使用模式和 match 表达式以及其他结构可以提供更多对程序控制流的支配权。模式由如下一些内容组合而成:

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

这些部分描述了我们要处理的数据的形状,接着可以用其匹配值来决定程序是否拥有正确的数据来运行特定部分的代码。如果数据符合这个形状,就可以使用这些命名的片段。如果不符合,与该模式相关的代码则不会运行。
本章是所有模式相关内容的参考。我们将涉及到使用模式的有效位置,refutableirrefutable 模式的区别,和你可能会见到的不同类型的模式语法。在最后,你将会看到如何使用模式创建强大而简洁的代码。

模式的位置

match 分支

  1. match value {
  2. pattern => expr,
  3. pattern => expr,
  4. pattern => expr,
  5. }
  1. 必须是 穷尽exhaustive )的,编译器会进行检查,因此所有可能的值都必须被考虑到。
  2. 模式 _ 可以匹配所有情况,不过它从不绑定任何变量。这在例如希望忽略任何未指定值的情况很有用。

    if let 条件表达式

    1. if let pattern = expr {
    2. // code
    3. } else {
    4. // code
    5. }
  3. if let ... 等同于 只关心一个匹配情况的 match 语句简写

  4. if let ... else ... 等同于 一个匹配情况+ if let 中的模式不匹配的情况
  5. if letelse ifelse if let 组合:支持复杂的匹配需求,且它们的分支并不要求其条件相互关联。
    缺点在于其穷尽性没有为编译器所检查,如果去掉最后的 else 块而遗漏处理一些情况,编译器也不会警告这类可能的逻辑错误。

    1. fn main() {
    2. let favorite_color: Option<&str> = None;
    3. let is_tuesday = false;
    4. let age: Result<u8, _> = "34".parse();
    5. if let Some(color) = favorite_color {
    6. println!("Using your favorite color, {}, as the background", color);
    7. } else if is_tuesday {
    8. println!("Tuesday is green day!");
    9. } else if let Ok(age) = age {
    10. if age > 30 {
    11. println!("Using purple as the background color");
    12. } else {
    13. println!("Using orange as the background color");
    14. }
    15. } else {
    16. println!("Using blue as the background color");
    17. }
    18. }

    if let 系列的另一个优点在于:允许匹配枚举非参数化的变量。
    比如枚举未注明 #[derive(PartialEq)],我们也没有为其实现 PartialEq。在这种情况下,通常 if Foo::Bar==a 会出错,因为此类枚举的实例不具有可比性。但是,if let 是可行的,即 if let 可以对枚举体判断实例是否属于(匹配)类型。

    1. // 该枚举故意未注明 `#[derive(PartialEq)]`,
    2. // 并且也没为其实现 `PartialEq`。这就是为什么下面比较 `Foo::Bar==a` 会失败的原因。
    3. enum Foo {Bar}
    4. fn main() {
    5. let a = Foo::Bar;
    6. // 变量匹配 Foo::Bar
    7. if Foo::Bar == a {
    8. // ^-- 这就是编译时发现的错误。使用 `if let` 来替换它。
    9. println!("a is foobar");
    10. }
    11. }

    while let 条件循环

    1. while let pattern = expr {}
  6. 只要模式匹配就一直进行 while 循环;如果模式不匹配,循环终止。

  7. 通常用来弹出栈中的每一个元素。

    1. let mut stack = Vec::new();
    2. stack.push(1);
    3. stack.push(2);
    4. stack.push(3);
    5. while let Some(top) = stack.pop() {
    6. println!("{}", top);
    7. }

    这个例子会打印出 3、2 接着是 1。pop 方法取出 vector 的最后一个元素并返回 Some(value)。如果 vector 是空的,它返回 Nonewhile 循环只要 pop 返回 Some 就会一直运行其块中的代码。一旦其返回 Nonewhile 循环停止。

  8. 如果遇到包含 loop 和判断的代码,可以考虑使用 while let 来简化

    1. let mut optional = Some(0);
    2. loop {
    3. match optional {
    4. // 如果 `optional` 解构成功,就执行下面语句块。
    5. Some(i) => {
    6. if i > 9 {
    7. println!("Greater than 9, quit!");
    8. optional = None;
    9. } else {
    10. println!("`i` is `{:?}`. Try again.", i);
    11. optional = Some(i + 1);
    12. }
    13. // ^ 需要三层缩进!
    14. },
    15. // 当解构失败时退出循环:
    16. _ => { break; }
    17. // ^ 为什么必须写这样的语句呢?肯定有更优雅的处理方式!
    18. }
    19. }
    1. let mut optional = Some(0);
    2. while let Some(i) = optional { // i: i32
    3. if i > 9 {
    4. println!("Greater than 9, quit!");
    5. optional = None;
    6. } else {
    7. println!("`i` is `{:?}`. Try again.", i);
    8. optional = Some(i + 1);
    9. }
    10. }

    一个综合的例子见 “此处” 。

    for 解构元组

    1. for pattern in expr {}
    1. let v = vec!['a', 'b', 'c'];
    2. for (index, value) in v.iter().enumerate() {
    3. println!("{} is at index {}", value, index);
    4. }

    let 语句

    1. let pattern = expr;
  9. 赋值操作:例如 let x = 5;x 是一个模式代表 “将匹配到的值绑定到变量 x”——变量名不过是形式特别朴素的模式。同时因为名称 x 是整个模式,这个模式实际上等于 “将任何值绑定到变量 x,不管值是什么”。

  10. 解构元组:let (x, y, z) = (1, 2, 3); Rust 会比较值 (1, 2, 3) 与模式 (x, y, z) 并发现此值匹配这个模式。在这个例子中,将会把 1 绑定到 x2 绑定到 y 并将 3 绑定到 z。你可以将这个元组模式看作是将三个独立的变量模式结合在一起。
  11. 当已经被绑定的变量再绑定给新的变量时,会根据数据类型来自动进行 Copy 还是 move 语义
    1. fn main() {
    2. let a = 1; // a: i32
    3. // copy 语义
    4. let b = a; // b: i32
    5. println!("{}", b);
    6. let c = vec!["a"]; // c: Vec<&str>
    7. // move 语义
    8. let d = c; // d: Vec<&str>
    9. println!("{:?}", c); // error:c 已经无法被使用了
    10. }

    函数参数

    1. fn func(pattern: expr) {}
    1. fn foo(x: i32) {
    2. // 代码
    3. }
    4. // 在参数中解构元组
    5. // 闭包类似于函数,也可以在闭包参数列表中使用模式
    6. fn print_coordinates(&(x, y): &(i32, i32)) {
    7. println!("Current location: ({}, {})", x, y);
    8. }
    9. fn main() {
    10. let point = (3, 5);
    11. print_coordinates(&point);
    12. }

    模式的形式

    模式在每个使用它的地方并不以相同的方式工作;在一些地方,模式必须是 irrefutable 的,意味着他们必须匹配所提供的任何值。在另一些情况,他们则可以是 refutable 的。

    refutable

    可反驳的refutable ):对某些可能的值进行匹配会失败的模式 。can fail to match for some possible value
    比如 if let Some(x) = a_value 表达式中的 Some(x);如果变量 a_value 中的值是 None 而不是 Some,那么 Some(x) 模式不能匹配。
    if letwhile let 表达式被限制为只能接受可反驳的模式,因为根据定义他们意在处理可能的失败:条件表达式的功能就是根据成功或失败执行不同的操作。
    match 匹配分支必须使用可反驳模式,除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式。

在可反驳模式的地方使用不可反驳模式的例子:

  1. if let x = 5 {
  2. println!("{}", x);
  3. };
  4. // 编译器警告:将不可反驳模式用于 `if let` 是没有意义的
  5. warning: irrefutable if-let pattern
  6. --> <anon>:2:5
  7. |
  8. 2 | / if let x = 5 {
  9. 3 | | println!("{}", x);
  10. 4 | | };
  11. | |_^
  12. |
  13. = note: #[warn(irrefutable_let_patterns)] on by default

irrefutable

不可反驳的irrefutable ):能匹配任何传递的可能值的模式。match for any possible value passed
比如 let x = 5; 语句中的 x,因为 x 可以匹配任何值所以不可能会失败。
函数参数、 let 语句和 for 循环 只能接受不可反驳的模式,因为通过不匹配的值程序无法进行有意义的工作。


在不可反驳模式的地方使用可反驳模式的例子:

  1. let Some(x) = some_option_value;
  2. // 编译器报错:
  3. error[E0005]: refutable pattern in local binding: `None` not covered
  4. -->
  5. |
  6. 3 | let Some(x) = some_option_value;
  7. | ^^^^^^^ pattern `None` not covered

如果 some_option_value 的值是 None,其不会成功匹配模式 Some(x),表明这个模式是可反驳的。
然而 let 语句只能接受不可反驳模式因为代码不能通过 None 值进行有效的操作。
因为我们没有覆盖(也不可能覆盖)到模式 Some(x) 的每一个可能的值, 所以 Rust 会合理地抗议。
修复:

  1. if let Some(x) = some_option_value {
  2. println!("{}", x);
  3. }

模式匹配的语法

匹配字面值

如果希望代码获得特定的具体值,则该语法很有用。

  1. let x = 1;
  2. match x {
  3. 1 => println!("one"),
  4. 2 => println!("two"),
  5. 3 => println!("three"),
  6. _ => println!("anything"),
  7. }

匹配命名变量

命名变量是匹配任何值的不可反驳模式,然而当其用于 match 表达式时情况会有些复杂。

  1. fn main() {
  2. let x = Some(5);
  3. let y = 10;
  4. match x {
  5. Some(50) => println!("Got 50"),
  6. // Some(y) 就是命名变量,y 是在 match 作用域中的新变量
  7. // y 会匹配任何 Some 中的值,x会进入第二个分支:y 会绑定 x 中 Some 内部的值
  8. Some(y) => println!("Matched, y = {:?}", y),
  9. // 如果 x 的值是 None ,头两个分支的模式不会匹配,所以会匹配下划线
  10. // 这个分支的模式中没有引入变量 x,所以此时表达式中的 x 会是外部没有被覆盖的 x
  11. _ => println!("Default case, x = {:?}", x),
  12. }
  13. println!("at the end: x = {:?}, y = {:?}", x, y);
  14. }

如果希望比较外部 xy 的值,而不引入覆盖变量的 match 表达式,我们需要相应地使用带有条件的匹配守卫(match guard),”正如这样”。

| 匹配多个模式

使用 | 语法匹配多个模式,它代表 or )的意思。

  1. let x = 1;
  2. match x {
  3. // x 的值匹配此分支的任一个值
  4. 1 | 2 => println!("one or two"),
  5. 3 => println!("three"),
  6. _ => println!("anything"),
  7. }

..= 匹配数字或 char 范围

..= 语法允许你匹配一个闭区间范围内的值。
范围只允许用于数字或 char 值,因为编译器会在编译时检查范围不为空。char 和 数字值是 Rust 仅有的可以判断范围是否为空的类型。

  1. let x = 5;
  2. match x {
  3. // 如果 x 是 1、2、3、4 或 5,第一个分支就会匹配
  4. // 1..=5 等价于 1 | 2 | 3 | 4 | 5
  5. 1..=5 => println!("one through five"),
  6. _ => println!("something else"),
  7. }
  8. let x = 'c';
  9. match x {
  10. 'a'..='j' => println!("early ASCII letter"),
  11. 'k'..='z' => println!("late ASCII letter"),
  12. _ => println!("something else"),
  13. }

这里的 range 语法必须使用 ..= 形式,即包含左右端点;如果需要使用 .. 形式,需要添加 #![feature(exclusive_range_pattern)] 属性。
由于浮点数的特殊性,在 match 中匹配浮点数或者浮点数的范围会出现提示:

  1. floating-point types cannot be used in patterns
  2. ...
  3. this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!

解构结构体

我们知道:实例化结构体时,如果变量和字段同名,我们可以简写:

  1. #[derive(Debug)]
  2. struct Point {
  3. x: i32,
  4. y: i32,
  5. }
  6. fn main() {
  7. let (x, y) = (0, 1); // 解构元组
  8. let p = Point { x, y }; // 这里不是解构
  9. println!("p = {:?}", p);
  10. }

解构结构体就可以把 let p = Point { x, y }; 模式与表达式反过来:

  1. struct Point {
  2. x: i32,
  3. y: i32,
  4. }
  5. fn main() {
  6. let p = Point { x: 0, y: 7 };
  7. // 模式中的变量名不必与结构体中的字段名一致
  8. let Point { x: a, y: b } = p; // 解构结构体语句,等价于 let a = p.x; let b = p.y
  9. assert_eq!(0, a);
  10. assert_eq!(7, b);
  11. // 变量名匹配字段名是常见的,通常希望变量名与字段名一致以便于理解变量来自于哪些字段
  12. // 匹配结构体字段的模式进行简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称
  13. let Point { x, y } = p; // 解构结构体语句,等价于 let x = p.x; let y = p.y
  14. assert_eq!(0, x);
  15. assert_eq!(7, y);
  16. }

也可以使用字面值作为结构体模式的一部分进行进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。

  1. struct Point {
  2. x: i32,
  3. y: i32,
  4. }
  5. fn main() {
  6. let p = Point { x: 0, y: 7 };
  7. match p {
  8. Point { x, y: 0 } => println!("On the x axis at {}", x), // 指定字段 y 匹配字面值 0 来匹配任何位于 x 轴上的点;创建了变量 x 以便在分支的代码中使用
  9. Point { x: 0, y } => println!("On the y axis at {}", y), // p 会匹配到第二个分支,情况与第一个分支类似
  10. Point { x, y } => println!("On neither axis: ({}, {})", x, y), // 没有指定任何字面值,所以其会匹配任何其他的 Point 并为 x 和 y 两个字段创建变量
  11. }
  12. }

解构枚举体

  1. enum Message {
  2. Quit,
  3. Move { x: i32, y: i32 },
  4. Write(String),
  5. ChangeColor(i32, i32, i32),
  6. }
  7. fn main() {
  8. let msg = Message::ChangeColor(0, 160, 255);
  9. match msg {
  10. // Message::Quit 没有任何数据,不能进一步解构其值。只能匹配其字面值 Message::Quit,因此模式中没有任何变量
  11. Message::Quit => {
  12. println!("The Quit variant has no data to destructure.")
  13. }
  14. // 结构体枚举成员,可以采用类似于匹配结构体的模式。在成员名称后,使用大括号并列出字段变量以便将其分解以供此分支的代码使用。
  15. Message::Move { x, y } => {
  16. println!(
  17. "Move in the x direction {} and in the y direction {}",
  18. x,
  19. y
  20. );
  21. }
  22. // Message::Write 以及 Message::ChangeColor 这样包含多个元素的类元组枚举成员,其模式则类似于用于解构元组的模式
  23. // 模式中变量的数量必须与成员中元素的数量一致
  24. Message::Write(text) => println!("Text message: {}", text),
  25. Message::ChangeColor(r, g, b) => {
  26. println!(
  27. "Change the color to red {}, green {}, and blue {}",
  28. r,
  29. g,
  30. b
  31. )
  32. }
  33. }
  34. }

解构嵌套的代码

目前为止,所有的例子都只匹配了深度为一级的结构体或枚举。当然也可以匹配嵌套的 item!甚至可以用复杂的方式来混合、匹配和嵌套解构模式。
解构嵌套的结构体和枚举:

  1. enum Color {
  2. Rgb(i32, i32, i32),
  3. Hsv(i32, i32, i32),
  4. }
  5. enum Message {
  6. Quit,
  7. Move { x: i32, y: i32 },
  8. Write(String),
  9. ChangeColor(Color),
  10. }
  11. fn main() {
  12. let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
  13. match msg {
  14. Message::ChangeColor(Color::Rgb(r, g, b)) => { // 两层嵌套解构
  15. println!(
  16. "Change the color to red {}, green {}, and blue {}",
  17. r,
  18. g,
  19. b
  20. )
  21. }
  22. Message::ChangeColor(Color::Hsv(h, s, v)) => { // 两层嵌套解构
  23. println!(
  24. "Change the color to hue {}, saturation {}, and value {}",
  25. h,
  26. s,
  27. v
  28. )
  29. }
  30. _ => ()
  31. }
  32. }

解构结构体和元组:

  1. struct Point {
  2. x: i32,
  3. y: i32,
  4. }
  5. // 结构体和元组嵌套在元组
  6. let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });

总之,模式解构是一个方便利用部分值片段的手段,比如结构体中每个单独字段的值。

_ 忽略模式中的值

  1. 除了在 match 分支中使用 _ 来忽略剩余的所有情况,_ 可以用在所有的模式场景中。

    1. fn foo(_: i32, y: i32) { // 函数参数中忽略第一个位置上值
    2. println!("This code only uses the y parameter: {}", y);
    3. }
    4. fn main() {
    5. foo(3, 4);
    6. }

    大部分情况当你不再需要特定函数参数时,最好修改签名不再包含无用的参数。
    在一些情况下忽略函数参数会变得特别有用,比如实现 trait 时,当你需要特定类型签名但是函数实现并不需要某个参数时。此时编译器就不会警告说存在未使用的函数参数,就跟使用命名参数一样。

  2. 也可以在嵌套的代码中忽略值:

    1. let mut setting_value = Some(5);
    2. let new_setting_value = Some(10);
    3. match (setting_value, new_setting_value) {
    4. (Some(_), Some(_)) => { // setting_value 和 new_setting_value 都为 Some 的情况
    5. println!("Can't overwrite an existing customized value");
    6. }
    7. _ => { // setting_value 或 new_setting_value 任一为 None
    8. setting_value = new_setting_value;
    9. }
    10. }
    11. println!("setting is {:?}", setting_value);
  3. 也可以在一个模式中的多处使用下划线来忽略特定值:

    1. let numbers = (2, 4, 8, 16, 32);
    2. match numbers {
    3. (first, _, third, _, fifth) => { // 忽略了一个五元元组中的第二和第四个值
    4. println!("Some numbers: {}, {}, {}", first, third, fifth)
    5. },
    6. }
  4. 在名字前以一个下划线 _ 开头来忽略未使用的变量:

    1. fn main() {
    2. let _x = 5;
    3. let _y = 10;
    4. }

    有时创建一个还未使用的变量是有用的,比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头。
    只使用 _ 和使用以下划线开头的名称有些微妙的不同:比如 _x 仍会将值绑定到变量,而 _ 则完全不会绑定。

    1. let s = Some(String::from("Hello!"));
    2. if let Some(_s) = s { // s 的值会移动进 _s,所有权被转交出去
    3. println!("found a string");
    4. }
    5. println!("{:?}", s); // s 无法使用
    1. let s = Some(String::from("Hello!"));
    2. if let Some(_) = s { // s 没有被移动进 _
    3. println!("found a string");
    4. }
    5. println!("{:?}", s); // s 可以使用

    .. 忽略剩余值

    我们知道,在索引的时候可以用 ..,来表示一段连续的索引。
    而在模式中,.. 会忽略模式中剩余的任何没有显式匹配的值部分。

    1. struct Point {
    2. x: i32,
    3. y: i32,
    4. z: i32,
    5. }
    6. let origin = Point { x: 0, y: 0, z: 0 };
    7. match origin {
    8. // .. 来忽略 Point 中除 x 以外的字段,相当于
    9. // Point { x, y: _, z: _ } => println!("x is {}", x),
    10. Point { x, .. } => println!("x is {}", x),
    11. }

    .. 会扩展为所需要的值的数量:

    1. fn main() {
    2. let numbers = (2, 4, 8, 16, 32);
    3. match numbers {
    4. // 只匹配元组中的第一个和最后一个值并忽略掉中间所有其它值
    5. (first, .., last) => {
    6. println!("Some numbers: {}, {}", first, last);
    7. },
    8. }
    9. }

    然而使用 .. 必须是无歧义的。如果期望匹配和忽略的值是不明确的,Rust 会报错:

    1. fn main() {
    2. let numbers = (2, 4, 8, 16, 32);
    3. match numbers {
    4. // Rust 不可能决定在元组中匹配 second 值之前应该忽略多少个值,以及在之后忽略多少个值
    5. (.., second, ..) => {
    6. println!("Some numbers: {}", second)
    7. },
    8. }
    9. }

    match 分支的 if:匹配守卫

    匹配守卫match guard ):指定于 match 分支模式之后的额外 if 条件,它也必须被满足才能选择此分支。也就是附加在模式后面的需要满足 if 的条件。
    匹配守卫用于表达比单独的模式所能允许的更为复杂的情况。

    1. let num = Some(4);
    2. match num {
    3. Some(x) if x < 5 => println!("less than five: {}", x), // 最终进入第一个分支
    4. Some(x) => println!("{}", x),
    5. None => (),
    6. }

    这里的 Some(x) 被称为“模式”,if x < 5 被称为 “匹配守卫”。无法在模式中表达 if x < 5 的条件,所以匹配守卫提供了表现此逻辑的能力。
    如果 numSome(10),因为 10 不小于 5 所以第一个分支的匹配守卫为假。接着 Rust 会前往第二个分支,这会匹配,因为它没有匹配守卫所以会匹配任何 Some 成员。
    使用 匹配守卫 可以在 match 分支里面引入外部变量:”完善这个例子”

    1. fn main() {
    2. let x = Some(5);
    3. let y = 5;
    4. match x {
    5. Some(50) => println!("Got 50"),
    6. // 相比指定会覆盖外部 y 的模式 Some(y),这里指定为 Some(n)。此新建的变量 n 并没有覆盖任何值,因为 match 外部没有变量 n。
    7. // 匹配守卫 if n == y 并不是一个模式所以没有引入新变量。这个 y 正是 外部的 y 而不是新的覆盖变量 y。
    8. // 这样就可以通过比较 n 和 y 来表达寻找一个与外部 y 相同的值的概念了
    9. Some(n) if n == y => println!("Matched, n = {}", n),
    10. _ => println!("Default case, x = {:?}", x),
    11. }
    12. println!("at the end: x = {:?}, y = {}", x, y);
    13. }

    匹配守卫与使用了 | 的模式的优先级:或逻辑优先,且匹配守卫作用于或逻辑中的所有情况。

    1. let x = 4;
    2. let y = false;
    3. match x {
    4. // 可以理解成 (4 | 5 | 6) if y => ...
    5. // 或者理解成 (4 if y) | (5 if y) | (6 if y) => ...
    6. // 而不是 4 | 5 | (6 if y) => ...
    7. 4 | 5 | 6 if y => println!("yes"),
    8. _ => println!("no"),
    9. }

    @ 绑定

    使用 @ 可以在一个模式中同时进行 测试值是否匹配模式 和 保存变量值 两个操作。

    1. fn main() {
    2. enum Message {
    3. Hello { id: i32 },
    4. }
    5. let msg = Message::Hello { id: 5 };
    6. match msg {
    7. // id_variable 也可以写成 id,这里是为了区分 字段名和变量名
    8. // 此例也展示了在结构体中使用 `..=` 语法、`@ ..=` 同时存在时的写法
    9. // 表示 如果进入 Message::Hello 分支,且 id 是 [3,7] 中的一个整数,则把匹配成功的 id 的值赋给 名称为 id_variable 的变量
    10. Message::Hello { id: id_variable @ 3..=7 } => {
    11. println!("Found an id in range: {}", id_variable)
    12. }
    13. // 第二分支里没有一个包含 id 字段实际值的变量
    14. // id 字段的值可以是 10、11 或 12,不过这个模式的代码并不知情也不能使用 id 字段中的值,因为没有将 id 值保存进一个变量
    15. Message::Hello { id: 10..=12 } => {
    16. println!("Found an id in another range")
    17. }
    18. // 这里使用了结构体字段简写语法,确实拥有可以用于分支代码的变量 id
    19. // 不过此分支中没有像头两个分支那样对 id 字段的值进行测试:任何值都会匹配此分支,变量 id 是一个没有范围的变量
    20. // 此时 id 的值是 除了 [3, 7] 和 [10, 12] 之外的一个整数
    21. Message::Hello { id } => {
    22. println!("Found some other id: {}", id)
    23. }
    24. }
    25. }

    ref 和匹配中的解引用

    match 会消耗掉 被匹配的对象,因此无法在 match 之后的代码使用这个被匹配的对象。比如下面的例子是无法通过编译的:

    1. let maybe_name = Some(String::from("Alice"));
    2. // The variable 'maybe_name' is consumed here ...
    3. match maybe_name {
    4. Some(n) => println!("Hello, {}", n),
    5. _ => println!("Hello, world"),
    6. }
    7. // ... and is now unavailable.
    8. println!("Hello again, {}", maybe_name.unwrap_or("world".into()));

    如果我们想继续使用被匹配的对象,那么就使用 ref 关键字来表明匹配绑定的是引用,而不是移动或者复制。

    1. let maybe_name = Some(String::from("Alice"));
    2. // Using `ref`, the value is borrowed, not moved ...
    3. match maybe_name {
    4. Some(ref n) => println!("Hello, {}", n),
    5. _ => println!("Hello, world"),
    6. }
    7. // ... so it's available here!
    8. println!("Hello again, {}", maybe_name.unwrap_or("world".into()));
    9. // 打印结果:
    10. // Hello, Alice
    11. // Hello again, Alice

    之所以需要ref而不是&,是因为&无法在解构子模式中作用于值的字段 (in destructuring subpatterns the & operator can’t be applied to the value’s fields)。
    解构子模式就是解构一次之后的模式。

    1. struct Person {
    2. name: String,
    3. age: u8,
    4. }
    5. let value = Person { name: String::from("John"), age: 23 };
    6. // 在模式中,无法使用 `&` 来表明这个字段是进行引用
    7. // if let Person { name: &person_name, age: 18..=150 } = value { }
    8. if let Person {name: ref person_name, age: 18..=150 } = value { }

    当然,也存在 ref mut ;此外,也不是说完全不能在模式匹配中使用 &
    对指针来说,解构(destructure)和解引用(dereference)要区分开,因为这两者的概念 是不同的,和 C 那样的语言用法不一样。

  • 解构使用 &ref、和 ref mut
    • &a:在匹配时,把一个引用的变量匹配成引用 & 和值的变量 a
    • ref a:在匹配时,声明变量 a 是一个被匹配到的引用
    • ref mut a:在匹配时,声明变量 a 是一个被匹配到的可变引用
  • 解引用使用 *:如果 a 是一个引用类型,那么 *a 获取到一层引用实际指向的值

闭包捕获参数时,需要解构由不同类型的迭代器传入的参数:

  1. fn main() {
  2. let vec1 = vec![1, 2, 3]; // vec1: Vec<i32>
  3. // `iter()` for vecs yields `&i32`. Destructure to `i32`.
  4. println!("2 in vec1: {}", vec1.iter().any(|&x| x == 2)); // x: i32
  5. // Destructure to `&&i32`, because `ref x` is `&i32`.
  6. println!("2 in vec1: {}", vec1.iter().any(|ref x| **x == 2)); // x: &&i32
  7. // `into_iter()` for vecs yields `i32`. No destructuring required.
  8. println!("2 in vec1: {}", vec1.into_iter().any(|x| x == 2)); // x: i32
  9. }

使用 match 匹配不同类型时,也需要相应地解构:

  1. fn main() {
  2. // 获得一个 `i32` 类型的引用。`&` 表示取引用。
  3. let reference = &4; // reference: &i32
  4. match reference {
  5. // 解构引用:& + 值
  6. &val => println!("Got a value via destructuring: {:?}", val), // val: i32
  7. }
  8. // 如果在匹配中不用解构 ,则需要在匹配前解引用
  9. match *reference {
  10. val => println!("Got a value via dereferencing: {:?}", val), // val: i32
  11. }
  12. // 如果一开始就不用引用,会怎样? `reference` 是一个 `&` 类型,因为赋值语句
  13. // 的右边已经是一个引用。但下面这个不是引用,因为右边不是。
  14. let _not_a_reference = 3; // _not_a_reference: i32
  15. // Rust 对这种情况提供了 `ref`。它更改了赋值行为,从而可以对具体值创建引用。
  16. // 下面这行将得到一个引用。
  17. let ref _is_a_reference = 3; // ref _is_a_reference: &i32
  18. // 相应地,定义两个非引用的变量,通过 `ref` 和 `ref mut` 仍可取得其引用。
  19. let value = 5; // value: i32
  20. let mut mut_value = 6; // mut mut_value: i32
  21. match value {
  22. r => println!("Got a a value: {}", r), // r: i32
  23. }
  24. match value {
  25. // 使用 `ref` 关键字来创建引用
  26. ref r => println!("Got a reference to a value: {:?}", r), // ref r: &i32
  27. }
  28. match mut_value {
  29. // 类似地使用 `ref mut`。
  30. ref mut m => { // ref mut m: &mut i32
  31. // 已经获得了 `mut_value` 的引用,先要解引用,才能改变它的值。
  32. *m += 10;
  33. println!("We added 10. `mut_value`: {:?}", m);
  34. }
  35. }
  36. }

一个综合的例子见 “match {identifier} 进行 move“ 。
参考:https://doc.rust-lang.org/std/keyword.ref.htmlhttps://doc.rust-lang.org/reference/patterns.html#identifier-patterns

match {identifier} 进行 move

这个例子是链表向后添加子链表,基本思路是让可变引用的指针指向最末的节点,然后添加一个链表,指向的过程就需要模式匹配。
这个例子使用到了很多不寻常的模式匹配的语法,综合性很强:

  1. use List::*;
  2. #[derive(Debug)]
  3. enum List {
  4. // Cons:元组结构体,包含链表的一个元素和一个指向下一节点的指针
  5. Cons(u32, Box<List>),
  6. // Nil:末结点,表明链表结束
  7. Nil,
  8. }
  9. impl List {
  10. // source: https://stackoverflow.com/a/43980172/15448980
  11. fn append_loop_mut(&mut self, elem: u32) {
  12. let mut current = self; // mut current: &mut List
  13. loop {
  14. // use {current} to move current into the match,
  15. // so that a mutable reference is never copied, only moved
  16. match { current } {
  17. // a mutable reference with an outstanding borrow cannot be modified
  18. &mut Cons(_, ref mut tail) => current = tail, // ref mut tail: &mut Box<List>
  19. // use `c @ &mut Nil` because we need to name the match of &mut Nil
  20. // since current has been moved
  21. c @ &mut Nil => { // c @ &mut Nil: &mut List
  22. *c = Cons(elem, Box::new(Nil));
  23. return;
  24. }
  25. }
  26. }
  27. }
  28. }
  29. fn main() {
  30. let mut list = List::new();
  31. // 向后追加元素
  32. list.append_loop_mut(1);
  33. list.append_loop_mut(2);
  34. list.append_loop_mut(3);
  35. // list: Cons(1, Cons(2, Cons(3, Nil)))
  36. }

这个例子还可以更高效,使用 while let

  1. impl List {
  2. // 来源(略作修改):
  3. // [adding-an-append-method-to-a-singly-linked-list]
  4. // (https://stackoverflow.com/a/43980606/15448980)
  5. // enhanced by using mutable reference to modify one list inside
  6. // highly efficient and graceful!
  7. fn append_mut(&mut self, elem: u32) {
  8. let mut node = self; // mut node: &mut List
  9. while let Cons(_, next) = node { // next: &mut Box<List>
  10. node = next;
  11. }
  12. *node = Cons(elem, Box::new(Nil));
  13. }
  14. }