Rust 所有的模式語法

2023-03-22 15:15 更新
ch18-03-pattern-syntax.md >
commit e72de80f114dc68f69f3920768314f7c005d0dd5

通過本書我們已領(lǐng)略過許多不同類型模式的例子。在本節(jié)中,我們收集了模式中所有有效的語法,并討論為什么以及何時你可能要使用這些語法。

匹配字面值

如第六章所示,可以直接匹配字面值模式。如下代碼給出了一些例子:

    let x = 1;

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

這段代碼會打印 one 因為 x 的值是 1。如果希望代碼獲得特定的具體值,則該語法很有用。

匹配命名變量

命名變量是匹配任何值的不可反駁模式,這在之前已經(jīng)使用過數(shù)次。然而當其用于 match 表達式時情況會有些復雜。因為 match 會開始一個新作用域,match 表達式中作為模式的一部分聲明的變量會覆蓋 match 結(jié)構(gòu)之外的同名變量,與所有變量一樣。在示例 18-11 中,聲明了一個值為 Some(5) 的變量 x 和一個值為 10 的變量 y。接著在值 x 上創(chuàng)建了一個 match 表達式。觀察匹配分支中的模式和結(jié)尾的 println!,并在運行此代碼或進一步閱讀之前推斷這段代碼會打印什么。

文件名: src/main.rs

    let x = Some(5);
    let y = 10;

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

    println!("at the end: x = {:?}, y = {:?}", x, y);

示例 18-11: 一個 match 語句其中一個分支引入了覆蓋變量 y

讓我們看看當 match 語句運行的時候發(fā)生了什么。第一個匹配分支的模式并不匹配 x 中定義的值,所以代碼繼續(xù)執(zhí)行。

第二個匹配分支中的模式引入了一個新變量 y,它會匹配任何 Some 中的值。因為我們在 match 表達式的新作用域中,這是一個新變量,而不是開頭聲明為值 10 的那個 y。這個新的 y 綁定會匹配任何 Some 中的值,在這里是 x 中的值。因此這個 y 綁定了 x 中 Some 內(nèi)部的值。這個值是 5,所以這個分支的表達式將會執(zhí)行并打印出 Matched, y = 5

如果 x 的值是 None 而不是 Some(5),頭兩個分支的模式不會匹配,所以會匹配下劃線。這個分支的模式中沒有引入變量 x,所以此時表達式中的 x 會是外部沒有被覆蓋的 x。在這個假想的例子中,match 將會打印 Default case, x = None。

一旦 match 表達式執(zhí)行完畢,其作用域也就結(jié)束了,同理內(nèi)部 y 的作用域也結(jié)束了。最后的 println! 會打印 at the end: x = Some(5), y = 10

為了創(chuàng)建能夠比較外部 x 和 y 的值,而不引入覆蓋變量的 match 表達式,我們需要相應(yīng)地使用帶有條件的匹配守衛(wèi)(match guard)。我們稍后將在 “匹配守衛(wèi)提供的額外條件” 這一小節(jié)討論匹配守衛(wèi)。

多個模式

在 match 表達式中,可以使用 | 語法匹配多個模式,它代表 or)的意思。例如,如下代碼將 x 的值與匹配分支相比較,第一個分支有  選項,意味著如果 x 的值匹配此分支的任一個值,它就會運行:

    let x = 1;

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

上面的代碼會打印 one or two

通過 ..= 匹配值的范圍

..= 語法允許你匹配一個閉區(qū)間范圍內(nèi)的值。在如下代碼中,當模式匹配任何在此范圍內(nèi)的值時,該分支會執(zhí)行:

    let x = 5;

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

如果 x 是 1、2、3、4 或 5,第一個分支就會匹配。這相比使用 | 運算符表達相同的意思更為方便;相比 1..=5,使用 | 則不得不指定 1 | 2 | 3 | 4 | 5。相反指定范圍就簡短的多,特別是在希望匹配比如從 1 到 1000 的數(shù)字的時候!

范圍只允許用于數(shù)字或 char 值,因為編譯器會在編譯時檢查范圍不為空。char 和 數(shù)字值是 Rust 僅有的可以判斷范圍是否為空的類型。

如下是一個使用 char 類型值范圍的例子:

    let x = 'c';

    match x {
        'a'..='j' => println!("early ASCII letter"),
        'k'..='z' => println!("late ASCII letter"),
        _ => println!("something else"),
    }

Rust 知道 c 位于第一個模式的范圍內(nèi),并會打印出 early ASCII letter

解構(gòu)并分解值

也可以使用模式來解構(gòu)結(jié)構(gòu)體、枚舉和元組,以便使用這些值的不同部分。讓我們來分別看一看。

解構(gòu)結(jié)構(gòu)體

示例 18-12 展示帶有兩個字段 x 和 y 的結(jié)構(gòu)體 Point,可以通過帶有模式的 let 語句將其分解:

文件名: src/main.rs

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

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

    let Point { x: a, y: b } = p;
    assert_eq!(0, a);
    assert_eq!(7, b);
}

示例 18-12: 解構(gòu)一個結(jié)構(gòu)體的字段為單獨的變量

這段代碼創(chuàng)建了變量 a 和 b 來匹配結(jié)構(gòu)體 p 中的 x 和 y 字段。這個例子展示了模式中的變量名不必與結(jié)構(gòu)體中的字段名一致。不過通常希望變量名與字段名一致以便于理解變量來自于哪些字段。

因為變量名匹配字段名是常見的,同時因為 let Point { x: x, y: y } = p; 包含了很多重復,所以對于匹配結(jié)構(gòu)體字段的模式存在簡寫:只需列出結(jié)構(gòu)體字段的名稱,則模式創(chuàng)建的變量會有相同的名稱。示例 18-13 展示了與示例 18-12 有著相同行為的代碼,不過 let 模式創(chuàng)建的變量為 x 和 y 而不是 a 和 b

文件名: src/main.rs

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

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

    let Point { x, y } = p;
    assert_eq!(0, x);
    assert_eq!(7, y);
}

示例 18-13: 使用結(jié)構(gòu)體字段簡寫來解構(gòu)結(jié)構(gòu)體字段

這段代碼創(chuàng)建了變量 x 和 y,與變量 p 中的 x 和 y 相匹配。其結(jié)果是變量 x 和 y 包含結(jié)構(gòu)體 p 中的值。

也可以使用字面值作為結(jié)構(gòu)體模式的一部分進行解構(gòu),而不是為所有的字段創(chuàng)建變量。這允許我們測試一些字段為特定值的同時創(chuàng)建其他字段的變量。

示例 18-14 展示了一個 match 語句將 Point 值分成了三種情況:直接位于 x 軸上(此時 y = 0 為真)、位于 y 軸上(x = 0)或不在任何軸上的點。

文件名: src/main.rs

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

    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),
    }
}

示例 18-14: 解構(gòu)和匹配模式中的字面值

第一個分支通過指定字段 y 匹配字面值 0 來匹配任何位于 x 軸上的點。此模式仍然創(chuàng)建了變量 x 以便在分支的代碼中使用。

類似的,第二個分支通過指定字段 x 匹配字面值 0 來匹配任何位于 y 軸上的點,并為字段 y 創(chuàng)建了變量 y。第三個分支沒有指定任何字面值,所以其會匹配任何其他的 Point 并為 x 和 y 兩個字段創(chuàng)建變量。

在這個例子中,值 p 因為其 x 包含 0 而匹配第二個分支,因此會打印出 On the y axis at 7。

解構(gòu)枚舉

本書之前的部分曾經(jīng)解構(gòu)過枚舉,比如第六章中示例 6-5 中解構(gòu)了一個 Option<i32>。一個當時沒有明確提到的細節(jié)是解構(gòu)枚舉的模式需要對應(yīng)枚舉所定義的儲存數(shù)據(jù)的方式。讓我們以示例 6-2 中的 Message 枚舉為例,編寫一個 match 使用模式解構(gòu)每一個內(nèi)部值,如示例 18-15 所示:

文件名: src/main.rs

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
        ),
    }
}

示例 18-15: 解構(gòu)包含不同類型值成員的枚舉

這段代碼會打印出 Change the color to red 0, green 160, and blue 255。嘗試改變 msg 的值來觀察其他分支代碼的運行。

對于像 Message::Quit 這樣沒有任何數(shù)據(jù)的枚舉成員,不能進一步解構(gòu)其值。只能匹配其字面值 Message::Quit,因此模式中沒有任何變量。

對于像 Message::Move 這樣的類結(jié)構(gòu)體枚舉成員,可以采用類似于匹配結(jié)構(gòu)體的模式。在成員名稱后,使用大括號并列出字段變量以便將其分解以供此分支的代碼使用。這里使用了示例 18-13 所展示的簡寫。

對于像 Message::Write 這樣的包含一個元素,以及像 Message::ChangeColor 這樣包含三個元素的類元組枚舉成員,其模式則類似于用于解構(gòu)元組的模式。模式中變量的數(shù)量必須與成員中元素的數(shù)量一致。

解構(gòu)嵌套的結(jié)構(gòu)體和枚舉

目前為止,所有的例子都只匹配了深度為一級的結(jié)構(gòu)體或枚舉。當然也可以匹配嵌套的項!

例如,我們可以重構(gòu)列表 18-15 的代碼來同時支持 RGB 和 HSV 色彩模式:

enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(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
        ),
        _ => (),
    }
}

示例 18-16: 匹配嵌套的枚舉

match 表達式第一個分支的模式匹配一個包含 Color::Rgb 枚舉成員的 Message::ChangeColor 枚舉成員,然后模式綁定了 3 個內(nèi)部的 i32 值。第二個分支的模式也匹配一個 Message::ChangeColor 枚舉成員, 但是其內(nèi)部的枚舉會匹配 Color::Hsv 枚舉成員。我們可以在一個 match 表達式中指定這些復雜條件,即使會涉及到兩個枚舉。

解構(gòu)結(jié)構(gòu)體和元組

甚至可以用復雜的方式來混合、匹配和嵌套解構(gòu)模式。如下是一個復雜結(jié)構(gòu)體的例子,其中結(jié)構(gòu)體和元組嵌套在元組中,并將所有的原始類型解構(gòu)出來:

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

這將復雜的類型分解成部分組件以便可以單獨使用我們感興趣的值。

通過模式解構(gòu)是一個方便利用部分值片段的手段,比如結(jié)構(gòu)體中每個單獨字段的值。

忽略模式中的值

有時忽略模式中的一些值是有用的,比如 match 中最后捕獲全部情況的分支實際上沒有做任何事,但是它確實對所有剩余情況負責。有一些簡單的方法可以忽略模式中全部或部分值:使用 _ 模式(我們已經(jīng)見過了),在另一個模式中使用 _ 模式,使用一個以下劃線開始的名稱,或者使用 .. 忽略所剩部分的值。讓我們來分別探索如何以及為什么要這么做。

使用 _ 忽略整個值

我們已經(jīng)使用過下劃線(_)作為匹配但不綁定任何值的通配符模式了。雖然 _ 模式作為 match 表達式最后的分支特別有用,也可以將其用于任意模式,包括函數(shù)參數(shù)中,如示例 18-17 所示:

文件名: src/main.rs

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

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

示例 18-17: 在函數(shù)簽名中使用 _

這段代碼會完全忽略作為第一個參數(shù)傳遞的值 3,并會打印出 This code only uses the y parameter: 4。

大部分情況當你不再需要特定函數(shù)參數(shù)時,最好修改簽名不再包含無用的參數(shù)。在一些情況下忽略函數(shù)參數(shù)會變得特別有用,比如實現(xiàn) trait 時,當你需要特定類型簽名但是函數(shù)實現(xiàn)并不需要某個參數(shù)時。此時編譯器就不會警告說存在未使用的函數(shù)參數(shù),就跟使用命名參數(shù)一樣。

使用嵌套的 _ 忽略部分值

也可以在一個模式內(nèi)部使用_ 忽略部分值,例如,當只需要測試部分值但在期望運行的代碼中沒有用到其他部分時。示例 18-18 展示了負責管理設(shè)置值的代碼。業(yè)務(wù)需求是用戶不允許覆蓋現(xiàn)有的自定義設(shè)置,但是可以取消設(shè)置,也可以在當前未設(shè)置時為其提供設(shè)置。

    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;
        }
    }

    println!("setting is {:?}", setting_value);

示例 18-18: 當不需要 Some 中的值時在模式內(nèi)使用下劃線來匹配 Some 成員

這段代碼會打印出 Can't overwrite an existing customized value 接著是 setting is Some(5)。在第一個匹配分支,我們不需要匹配或使用任一個 Some 成員中的值;重要的部分是需要測試 setting_value 和 new_setting_value 都為 Some 成員的情況。在這種情況,我們打印出為何不改變 setting_value,并且不會改變它。

對于所有其他情況(setting_value 或 new_setting_value 任一為 None),這由第二個分支的 _ 模式體現(xiàn),這時確實希望允許 new_setting_value 變?yōu)?nbsp;setting_value

也可以在一個模式中的多處使用下劃線來忽略特定值,如示例 18-19 所示,這里忽略了一個五元元組中的第二和第四個值:

    let numbers = (2, 4, 8, 16, 32);

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

示例 18-19: 忽略元組的多個部分

這會打印出 Some numbers: 2, 8, 32,值 4 和 16 會被忽略。

通過在名字前以一個下劃線開頭來忽略未使用的變量

如果你創(chuàng)建了一個變量卻不在任何地方使用它,Rust 通常會給你一個警告,因為這可能會是個 bug。但是有時創(chuàng)建一個還未使用的變量是有用的,比如你正在設(shè)計原型或剛剛開始一個項目。這時你希望告訴 Rust 不要警告未使用的變量,為此可以用下劃線作為變量名的開頭。示例 18-20 中創(chuàng)建了兩個未使用變量,不過當編譯代碼時只會得到其中一個的警告:

文件名: src/main.rs

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

示例 18-20: 以下劃線開始變量名以便去掉未使用變量警告

這里得到了警告說未使用變量 y,不過沒有警告說未使用下劃線開頭的變量。

注意,只使用 _ 和使用以下劃線開頭的名稱有些微妙的不同:比如 _x 仍會將值綁定到變量,而 _ 則完全不會綁定。為了展示這個區(qū)別的意義,示例 18-21 會產(chǎn)生一個錯誤。

    let s = Some(String::from("Hello!"));

    if let Some(_s) = s {
        println!("found a string");
    }

    println!("{:?}", s);

示例 18-21: 以下劃線開頭的未使用變量仍然會綁定值,它可能會獲取值的所有權(quán)

我們會得到一個錯誤,因為 s 的值仍然會移動進 _s,并阻止我們再次使用 s。然而只使用下劃線本身,并不會綁定值。示例 18-22 能夠無錯編譯,因為 s 沒有被移動進 _

    let s = Some(String::from("Hello!"));

    if let Some(_) = s {
        println!("found a string");
    }

    println!("{:?}", s);

示例 18-22: 單獨使用下劃線不會綁定值

上面的代碼能很好的運行;因為沒有把 s 綁定到任何變量;它沒有被移動。

用 .. 忽略剩余值

對于有多個部分的值,可以使用 .. 語法來只使用部分并忽略其它值,同時避免不得不每一個忽略值列出下劃線。.. 模式會忽略模式中剩余的任何沒有顯式匹配的值部分。在示例 18-23 中,有一個 Point 結(jié)構(gòu)體存放了三維空間中的坐標。在 match 表達式中,我們希望只操作 x 坐標并忽略 y 和 z 字段的值:

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

    let origin = Point { x: 0, y: 0, z: 0 };

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

示例 18-23: 通過使用 .. 來忽略 Point 中除 x 以外的字段

這里列出了 x 值,接著僅僅包含了 .. 模式。這比不得不列出 y: _ 和 z: _ 要來得簡單,特別是在處理有很多字段的結(jié)構(gòu)體,但只涉及一到兩個字段時的情形。

.. 會擴展為所需要的值的數(shù)量。示例 18-24 展示了元組中 .. 的應(yīng)用:

文件名: src/main.rs

fn main() {
    let numbers = (2, 4, 8, 16, 32);

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

示例 18-24: 只匹配元組中的第一個和最后一個值并忽略掉所有其它值

這里用 first 和 last 來匹配第一個和最后一個值。.. 將匹配并忽略中間的所有值。

然而使用 .. 必須是無歧義的。如果期望匹配和忽略的值是不明確的,Rust 會報錯。示例 18-25 展示了一個帶有歧義的 .. 例子,因此其不能編譯:

文件名: src/main.rs

fn main() {
    let numbers = (2, 4, 8, 16, 32);

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

示例 18-25: 嘗試以有歧義的方式運用 ..

如果編譯上面的例子,會得到下面的錯誤:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error: `..` can only be used once per tuple pattern
 --> src/main.rs:5:22
  |
5 |         (.., second, ..) => {
  |          --          ^^ can only be used once per tuple pattern
  |          |
  |          previously used here

error: could not compile `patterns` due to previous error

Rust 不可能決定在元組中匹配 second 值之前應(yīng)該忽略多少個值,以及在之后忽略多少個值。這段代碼可能表明我們意在忽略 2,綁定 second 為 4,接著忽略 816 和 32;抑或是意在忽略 2 和 4,綁定 second 為 8,接著忽略 16 和 32,以此類推。變量名 second 對于 Rust 來說并沒有任何特殊意義,所以會得到編譯錯誤,因為在這兩個地方使用 .. 是有歧義的。

匹配守衛(wèi)提供的額外條件

匹配守衛(wèi)match guard)是一個指定于 match 分支模式之后的額外 if 條件,它也必須被滿足才能選擇此分支。匹配守衛(wèi)用于表達比單獨的模式所能允許的更為復雜的情況。

這個條件可以使用模式中創(chuàng)建的變量。示例 18-26 展示了一個 match,其中第一個分支有模式 Some(x) 還有匹配守衛(wèi) if x < 5

    let num = Some(4);

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

示例 18-26: 在模式中加入匹配守衛(wèi)

上例會打印出 less than five: 4。當 num 與模式中第一個分支比較時,因為 Some(4) 匹配 Some(x) 所以可以匹配。接著匹配守衛(wèi)檢查 x 值是否小于 5,因為 4 小于 5,所以第一個分支被選擇。

相反如果 num 為 Some(10),因為 10 不小于 5 所以第一個分支的匹配守衛(wèi)為假。接著 Rust 會前往第二個分支,這會匹配因為它沒有匹配守衛(wèi)所以會匹配任何 Some 成員。

無法在模式中表達類似 if x % 2 == 0 的條件,所以通過匹配守衛(wèi)提供了表達類似邏輯的能力。這種替代表達方式的缺點是,編譯器不會嘗試為包含匹配守衛(wèi)的模式檢查窮盡性。

在示例 18-11 中,我們提到可以使用匹配守衛(wèi)來解決模式中變量覆蓋的問題,那里 match 表達式的模式中新建了一個變量而不是使用 match 之外的同名變量。新變量意味著不能夠測試外部變量的值。示例 18-27 展示了如何使用匹配守衛(wèi)修復這個問題。

文件名: src/main.rs

fn main() {
    let x = Some(5);
    let y = 10;

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

    println!("at the end: x = {:?}, y = {}", x, y);
}

示例 18-27: 使用匹配守衛(wèi)來測試與外部變量的相等性

現(xiàn)在這會打印出 Default case, x = Some(5)?,F(xiàn)在第二個匹配分支中的模式不會引入一個覆蓋外部 y 的新變量 y,這意味著可以在匹配守衛(wèi)中使用外部的 y。相比指定會覆蓋外部 y 的模式 Some(y),這里指定為 Some(n)。此新建的變量 n 并沒有覆蓋任何值,因為 match 外部沒有變量 n

匹配守衛(wèi) if n == y 并不是一個模式所以沒有引入新變量。這個 y 正是 外部的 y 而不是新的覆蓋變量 y,這樣就可以通過比較 n 和 y 來表達尋找一個與外部 y 相同的值的概念了。

也可以在匹配守衛(wèi)中使用  運算符 | 來指定多個模式,同時匹配守衛(wèi)的條件會作用于所有的模式。示例 18-28 展示了結(jié)合匹配守衛(wèi)與使用了 | 的模式的優(yōu)先級。這個例子中重要的部分是匹配守衛(wèi) if y 作用于 4、5  6,即使這看起來好像 if y 只作用于 6

    let x = 4;
    let y = false;

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

示例 18-28: 結(jié)合多個模式與匹配守衛(wèi)

這個匹配條件表明此分支值匹配 x 值為 4、5 或 6 同時 y 為 true 的情況。運行這段代碼時會發(fā)生的是第一個分支的模式因 x 為 4 而匹配,不過匹配守衛(wèi) if y 為假,所以第一個分支不會被選擇。代碼移動到第二個分支,這會匹配,此程序會打印出 no。這是因為 if 條件作用于整個 4 | 5 | 6 模式,而不僅是最后的值 6。換句話說,匹配守衛(wèi)與模式的優(yōu)先級關(guān)系看起來像這樣:

(4 | 5 | 6) if y => ...

而不是:

4 | 5 | (6 if y) => ...

可以通過運行代碼時的情況看出這一點:如果匹配守衛(wèi)只作用于由 | 運算符指定的值列表的最后一個值,這個分支就會匹配且程序會打印出 yes

@ 綁定

at 運算符(@)允許我們在創(chuàng)建一個存放值的變量的同時測試其值是否匹配模式。示例 18-29 展示了一個例子,這里我們希望測試 Message::Hello 的 id 字段是否位于 3..=7 范圍內(nèi),同時也希望能將其值綁定到 id_variable 變量中以便此分支相關(guān)聯(lián)的代碼可以使用它??梢詫?nbsp;id_variable 命名為 id,與字段同名,不過出于示例的目的這里選擇了不同的名稱。

    enum Message {
        Hello { id: i32 },
    }

    let msg = Message::Hello { id: 5 };

    match msg {
        Message::Hello {
            id: id_variable @ 3..=7,
        } => 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),
    }

示例 18-29: 使用 @ 在模式中綁定值的同時測試它

上例會打印出 Found an id in range: 5。通過在 3..=7 之前指定 id_variable @,我們捕獲了任何匹配此范圍的值并同時測試其值匹配這個范圍模式。

第二個分支只在模式中指定了一個范圍,分支相關(guān)代碼沒有一個包含 id 字段實際值的變量。id 字段的值可以是 10、11 或 12,不過這個模式的代碼并不知情也不能使用 id 字段中的值,因為沒有將 id 值保存進一個變量。

最后一個分支指定了一個沒有范圍的變量,此時確實擁有可以用于分支代碼的變量 id,因為這里使用了結(jié)構(gòu)體字段簡寫語法。不過此分支中沒有像頭兩個分支那樣對 id 字段的值進行測試:任何值都會匹配此分支。

使用 @ 可以在一個模式中同時測試和保存變量值。

總結(jié)

模式是 Rust 中一個很有用的功能,它幫助我們區(qū)分不同類型的數(shù)據(jù)。當用于 match 語句時,Rust 確保模式會包含每一個可能的值,否則程序?qū)⒉荒芫幾g。let 語句和函數(shù)參數(shù)的模式使得這些結(jié)構(gòu)更強大,可以在將值解構(gòu)為更小部分的同時為變量賦值??梢詣?chuàng)建簡單或復雜的模式來滿足我們的要求。

接下來,在本書倒數(shù)第二章中,我們將介紹一些 Rust 眾多功能中較為高級的部分。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號