match控制流构造

Rust 有一个非常强大的控制流结构,称为match那 允许您将值与一系列模式进行比较,然后执行 基于哪个模式匹配的代码。模式可以由文字值、 变量名称、通配符和许多其他内容;章 18 涵盖了所有不同种类的图案 以及他们做什么。的力量match来自 模式以及编译器确认所有可能的情况都是 处理。

想想match像硬币分拣机一样的表情:硬币滑动 沿着一条带有大小不一的孔的轨道向下,每枚硬币都会落下 它遇到的第一个适合的洞。同样,值会变为 通过match,在第一个模式中,值为“fits”, 该值属于要在执行期间使用的关联代码块。

说到硬币,让我们以它们为例,使用match!我们可以编写一个 函数,它采用未知的美国硬币,并且以类似于计数的方式 machine 确定它是哪种硬币并返回以美分为单位的值,如图所示 在示例 6-3 中。

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

fn main() {}

示例 6-3:一个枚举和一个match表达式,该表达式具有 枚举的变体作为其模式

让我们分解一下matchvalue_in_cents功能。首先我们列出 这matchkeyword 后跟一个表达式,在本例中为值coin.这似乎与if但 有一个很大的区别:使用if,则条件需要计算为 Boolean 值,但在这里它可以是任何类型。的类型coin在此示例中 是Coinenum 中。

接下来是match武器。arm 有两个部分:一个模式和一些代码。这 这里的第一个分支有一个模式,该模式是Coin::Penny然后是分隔模式和要运行的代码的运算符。本例中的代码 只是价值=>1.每个分支都用逗号分隔。

matchexpression 执行时,它会将结果值与 每个臂的模式,按顺序排列。如果模式与该值匹配,则代码 将执行与该模式关联的模式。如果该模式与 value 时,执行会继续到下一个分支,就像在硬币分拣机中一样。 我们需要多少个 arm:在示例 6-3 中,我们的match有四只手臂。

与每个分支关联的代码是一个表达式,结果值 匹配 Arm 中的 expression 是为 整个match表达。

如果 match 臂代码很短,我们通常不会使用大括号,因为它确实如此 在示例 6-3 中,每个 arm 只返回一个值。如果要运行多个 代码行,则必须使用大括号和逗号 然后,跟随 ARM 是可选的。例如,以下代码打印 “Lucky penny!” 每次使用Coin::Penny,但仍然 返回块的最后一个值,1:

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

fn main() {}

绑定到值的模式

match arms 的另一个有用功能是它们可以绑定到 与模式匹配的值。这就是我们从 enum 中提取值的方法 变种。

例如,让我们更改其中一个枚举变体以在其中保存数据。 从 1999 年到 2008 年,美国铸造了具有不同 一侧为 50 个州中的每一个州设计。没有其他硬币进入状态 设计,因此只有 Quarter 具有此额外值。我们可以将此信息添加到 我们enum通过将Quartervariant 中,以包含UsState价值 存储在其中,我们在示例 6-4 中已经完成了。

#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn main() {}

示例 6-4:一个Coin枚举,其中Quarter变体 还持有UsState价值

假设一个朋友正在尝试收集所有 50 个州的 25 美分硬币。而 我们按硬币类型对零钱进行排序,我们还将叫出 state 与每个季度相关联,因此如果它是我们的朋友没有的,则 他们可以将其添加到他们的收藏中。

在此代码的 match 表达式中,我们添加了一个名为state到 匹配变体值的模式Coin::Quarter.当Coin::Quartermatches、state变量将绑定到该 季度的状态。然后我们可以使用state在该 Arm 的代码中,如下所示:

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {state:?}!");
            25
        }
    }
}

fn main() {
    value_in_cents(Coin::Quarter(UsState::Alaska));
}

如果我们调用value_in_cents(Coin::Quarter(UsState::Alaska)),coinCoin::Quarter(UsState::Alaska).当我们将该值与每个 的火柴臂中,在我们到达之前,它们都没有匹配Coin::Quarter(state).在 该点,则state将是值UsState::Alaska.我们可以 然后在println!表达式,从而得到内部的 state 值从Coinenum 变体Quarter.

匹配Option<T>

在上一节中,我们想获取内部的T值从Somecase when usingOption<T>;我们也可以处理Option<T>match如 我们用Coinenum!我们不是比较硬币,而是比较 的变体Option<T>,但是match表达式有效 相同。

假设我们想编写一个函数,该函数采用Option<i32>并且,如果 里面有一个值,将该值加 1。如果里面没有值, 该函数应返回None值,并且不会尝试执行任何 操作。

这个函数很容易编写,多亏了match,如下所示 示例 6-5.

fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

示例 6-5:使用match表达式开启 一Option<i32>

让我们看看plus_one更详细地。当我们调用plus_one(five)、变量xplus_one将具有 价值Some(5).然后,我们将其与每个匹配组进行比较:

fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

Some(5)值与模式不匹配None,因此我们继续执行 下一个 Arm:

fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

DoesSome(5)火柴Some(i)?确实如此!我们有相同的变体。这i绑定到 中包含的值Some所以i取值5.中的代码 然后执行 match arm,因此我们将 1 添加到i并创建一个 新增功能Some值替换为我们的总计6里面。

现在让我们考虑plus_one在示例 6-5 中,其中xNone.我们输入match并与第一只手臂进行比较:

fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

它匹配!没有可添加的值,因此程序停止并返回None值位于 . 的右侧。因为第一个手臂匹配,所以没有其他 手臂进行比较。=>

结合match枚举在许多情况下都很有用。您将看到以下内容 pattern 中:match针对枚举,将变量绑定到 data 中,然后基于它执行代码。一开始有点棘手,但是 一旦你习惯了它,你会希望你拥有所有语言的版本。它 一直是用户的最爱。

匹配项详尽无遗

还有另一个方面match我们需要讨论:手臂的图案必须 涵盖所有可能性。考虑一下我们的plus_one功能 它有一个 bug 并且不会编译:

fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

我们没有处理Nonecase,所以这段代码会导致一个 bug。幸运的是,它是 Rust 知道如何捕捉的 bug。如果我们尝试编译这段代码,我们会得到这个 错误:

$ cargo run
   Compiling enums v0.1.0 (file:///projects/enums)
error[E0004]: non-exhaustive patterns: `None` not covered
 --> src/main.rs:3:15
  |
3 |         match x {
  |               ^ pattern `None` not covered
  |
note: `Option<i32>` defined here
 --> /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/option.rs:574:1
 ::: /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/option.rs:578:5
  |
  = note: not covered
  = note: the matched value is of type `Option<i32>`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
  |
4 ~             Some(i) => Some(i + 1),
5 ~             None => todo!(),
  |

For more information about this error, try `rustc --explain E0004`.
error: could not compile `enums` (bin "enums") due to 1 previous error

Rust 知道我们没有涵盖所有可能的情况,甚至知道哪种情况 我们忘了的模式!Rust 中的匹配项是详尽的:我们必须穷尽每一个 的可能性,才能使代码有效。尤其是在Option<T>,当 Rust 防止我们忘记显式处理Nonecase 中,它可以保护我们避免在可能的时候假设我们有价值 为 null,因此之前讨论的 10 亿美元错误变得不可能。

Catch-all 模式和占位符_

使用枚举,我们也可以对一些特定值采取特殊作,但是 对于所有其他值,请执行一个 default作。想象一下我们正在实现一个游戏 其中,如果您在掷骰子时掷出 3,您的玩家不会移动,而是 得到一顶新的花哨帽子。如果您掷出 7,您的玩家将失去一顶花哨的帽子。对于所有人 other 值时,您的玩家会在游戏板上移动该数量的空格。这是 一个match实现该逻辑,以及掷骰子的结果 hardcoded 而不是 random 值,所有其他 logic 由 函数,因为实际实现它们超出了 此示例:

fn main() {
    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other),
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn move_player(num_spaces: u8) {}
}

对于前两个分支,模式是文本值37.为 覆盖所有其他可能值的最后一个分支,模式是 变量other.为other手臂 通过将其传递给move_player功能。

这段代码可以编译,即使我们没有列出所有可能的值u8可以有,因为最后一个模式将匹配所有值,而不是专门匹配 上市。此 catch-all 模式满足以下要求match必须是 详尽。请注意,我们必须将 catch-all 臂放在最后,因为 按顺序评估模式。如果我们把 catch-all 臂放在前面,另一个 arms 永远不会运行,所以如果我们在 catch-all 之后添加 arms,Rust 会警告我们!

Rust 还有一个模式,当我们想要一个 catch-all 但不想使用 catch-all 模式中的值时,我们可以使用:是一个特殊的模式,它匹配 any 值,并且不绑定到该值。这告诉 Rust 我们不会 使用该值,这样 Rust 就不会警告我们未使用的变量。_

让我们改变游戏规则:现在,如果您掷出 3 或 A 7,您必须再次掷骰子。我们不再需要使用 catch-all 值,因此我们 可以更改我们的代码以代替名为_other:

fn main() {
    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => reroll(),
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn reroll() {}
}

此示例还满足穷举性要求,因为我们显式地 忽略最后一个分支中的所有其他值;我们没有忘记任何东西。

最后,我们将再次更改游戏规则,以便没有其他内容 如果你掷出 3 或 7 以外的任何东西,就会在你的回合发生。我们可以表达 通过使用 unit 值(我们在“元组 Type“ 部分)作为 arm 附带的代码:_

fn main() {
    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => (),
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
}

在这里,我们明确告诉 Rust 我们不会使用任何其他值 这与早期 ARM 中的模式不匹配,我们不想运行任何 code 中。

我们将在 章节 中介绍更多关于 pattern 和 matching 的信息 18. 现在,我们将继续讨论if let语法,这在match表达 有点啰嗦。

本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准