Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Syntax Pattern

ในส่วนนี้ เรารวบรวม syntax ทั้งหมดที่ valid ใน pattern และพูดถึงว่าทำไม และเมื่อไรคุณอาจต้องการใช้แต่ละอัน

Match Literal

ดังที่คุณเห็นในบทที่ 6 คุณ match pattern กับ literal โดยตรงได้ โค้ดต่อ ไปนี้ให้ตัวอย่างบ้าง:

fn main() {
    let x = 1;

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

โค้ดนี้ print one เพราะค่าใน x คือ 1 Syntax นี้มีประโยชน์เมื่อ คุณต้องการให้โค้ดของคุณทำ action ถ้ามันได้ค่า concrete เฉพาะ

Match Named Variable

ตัวแปร named คือ pattern irrefutable ที่ match ค่าใดก็ได้ และเราใช้พวก มันหลายครั้งในหนังสือนี้ อย่างไรก็ตาม มี complication เมื่อคุณใช้ตัวแปร named ใน expression match, if let หรือ while let เพราะแต่ละชนิด ของ expression เหล่านี้เริ่ม scope ใหม่ ตัวแปรที่ประกาศเป็นส่วนของ pattern ภายใน expression เหล่านี้จะ shadow ที่มีชื่อเดียวกันนอก construct เหมือนกรณีกับตัวแปรทั้งหมด ใน Listing 19-11 เราประกาศตัวแปร ชื่อ x กับค่า Some(5) และตัวแปร y กับค่า 10 แล้วเราสร้าง expression match บนค่า x ดู pattern ใน match arm และ println! ในตอนท้าย และลองคิดออกว่าโค้ดจะ print อะไรก่อนรันโค้ดนี้หรืออ่านต่อ

Filename: src/main.rs
fn main() {
    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 = {x:?}, y = {y}");
}
Listing 19-11: expression match ที่มี arm ที่แนะนำตัวแปรใหม่ซึ่ง shadow ตัวแปร y ที่มี

มาเดินผ่านสิ่งที่เกิดขึ้นเมื่อ expression match รัน Pattern ใน match arm ตัวแรกไม่ match ค่าที่นิยามของ x ดังนั้นโค้ดต่อไป

Pattern ใน match arm ตัวที่สองแนะนำตัวแปรใหม่ชื่อ y ที่จะ match ค่าใด ภายในค่า Some เพราะเราอยู่ใน scope ใหม่ภายใน expression match นี่ คือตัวแปร y ใหม่ ไม่ใช่ y ที่เราประกาศตอนเริ่มกับค่า 10 การ binding y ใหม่นี้จะ match ค่าใดภายใน Some ซึ่งคือสิ่งที่เรามีใน x ดังนั้น y ใหม่นี้ bind กับค่าด้านในของ Some ใน x ค่านั้นคือ 5 ดังนั้น expression สำหรับ arm นั้นรันและ print Matched, y = 5

ถ้า x เป็นค่า None แทนที่จะเป็น Some(5) pattern ในสอง arm แรกจะ ไม่ match ดังนั้นค่าจะถูก match กับ underscore เราไม่ได้แนะนำตัวแปร x ใน pattern ของ arm underscore ดังนั้น x ใน expression ยังเป็น x ภายนอกที่ไม่ถูก shadow ในกรณีสมมตินี้ match จะ print Default case, x = None

เมื่อ expression match เสร็จ scope ของมันจบ และ scope ของ y ด้านในก็ จบ println! สุดท้ายผลิต at the end: x = Some(5), y = 10

เพื่อสร้าง expression match ที่เปรียบเทียบค่าของ x และ y ภายนอก แทนที่จะแนะนำตัวแปรใหม่ที่ shadow ตัวแปร y ที่มี เราจะต้องใช้ conditional match guard แทน เราจะพูดถึง match guard ภายหลังในส่วน “เพิ่ม Conditional ด้วย Match Guard”

Match หลาย Pattern

ในเครือข่าย expression match คุณ match หลาย pattern โดยใช้ syntax | ได้ ซึ่งคือ pattern operator or ตัวอย่างเช่น ในโค้ดต่อไปนี้ เรา match ค่าของ x กับ match arm ตัวแรกของซึ่งมี option or หมายความว่าถ้าค่า ของ x match ค่าใดในสองค่าใน arm นั้น โค้ดของ arm นั้นจะรัน:

fn main() {
    let x = 1;

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

โค้ดนี้ print one or two

Match Range ของค่าด้วย ..=

Syntax ..= อนุญาตให้เรา match range inclusive ของค่า ในโค้ดต่อไปนี้ เมื่อ pattern match ค่าใดภายใน range ที่ให้ arm นั้นจะรัน:

fn main() {
    let x = 5;

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

ถ้า x คือ 1, 2, 3, 4 หรือ 5 arm แรกจะ match Syntax นี้ สะดวกกว่าสำหรับหลายค่า match กว่าใช้ operator | เพื่อแสดงไอเดียเดียว กัน — ถ้าเราจะใช้ | เราจะต้องระบุ 1 | 2 | 3 | 4 | 5 ระบุ range สั้น กว่ามาก โดยเฉพาะถ้าเราต้องการ match สมมุติ ตัวเลขใดระหว่าง 1 ถึง 1,000!

Compiler check ว่า range ไม่ว่างที่ compile time และเพราะ type เดียวที่ Rust บอกได้ว่า range ว่างหรือไม่คือ char และค่า numeric, range ถูก อนุญาตเพียงกับค่า numeric หรือ char

นี่คือตัวอย่างที่ใช้ range ของค่า char:

fn main() {
    let x = 'c';

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

Rust บอกได้ว่า 'c' อยู่ภายใน range ของ pattern แรกและ print early ASCII letter

Destructure เพื่อ Break Apart ค่า

เราใช้ pattern เพื่อ destructure struct, enum และ tuple เพื่อใช้ส่วน ต่างกันของค่าเหล่านี้ได้ด้วย มาเดินผ่านค่าแต่ละ

Struct

Listing 19-12 แสดง struct Point ที่มีสอง field, x และ y ที่เรา break apart โดยใช้ pattern กับ statement let ได้

Filename: 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);
}
Listing 19-12: Destructure field ของ struct เป็นตัวแปรแยก

โค้ดนี้สร้างตัวแปร a และ b ที่ match ค่าของ field x และ y ของ struct p ตัวอย่างนี้แสดงว่าชื่อของตัวแปรใน pattern ไม่ต้อง match ชื่อ field ของ struct อย่างไรก็ตาม มันเป็นปกติที่จะ match ชื่อตัวแปรให้ชื่อ field เพื่อให้ง่ายขึ้นในการจำว่าตัวแปรใดมาจาก field ใด เพราะการใช้ปกตินี้ และเพราะการเขียน let Point { x: x, y: y } = p; บรรจุ duplication เยอะ Rust มี shorthand สำหรับ pattern ที่ match field struct — คุณเพียงต้อง list ชื่อของ field struct และตัวแปรที่สร้างจาก pattern จะมีชื่อเดียวกัน Listing 19-13 ทำตัวในวิธีเดียวกับโค้ดใน Listing 19-12 แต่ตัวแปรที่สร้าง ใน pattern let คือ x และ y แทน a และ b

Filename: 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);
}
Listing 19-13: Destructure field struct โดยใช้ shorthand field struct

โค้ดนี้สร้างตัวแปร x และ y ที่ match field x และ y ของตัวแปร p ผลคือตัวแปร x และ y บรรจุค่าจาก struct p

เรา destructure กับค่า literal เป็นส่วนของ pattern struct แทนที่จะสร้าง ตัวแปรสำหรับทุก field ได้ด้วย การทำเช่นนั้นอนุญาตให้เรา test field บาง field สำหรับค่าเฉพาะขณะที่สร้างตัวแปรเพื่อ destructure field อื่น

ใน Listing 19-14 เรามี expression match ที่แยกค่า Point เป็นสาม กรณี — point ที่อยู่บนแกน x โดยตรง (ซึ่งเป็นจริงเมื่อ y = 0), บน แกน y (x = 0) หรือไม่อยู่บนแกนใด

Filename: src/main.rs
struct Point {
    x: i32,
    y: i32,
}

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})");
        }
    }
}
Listing 19-14: Destructure และ match ค่า literal ในหนึ่ง pattern

arm แรกจะ match point ใดที่อยู่บนแกน x โดยระบุว่า field y match ถ้า ค่าของมัน match literal 0 Pattern ยังสร้างตัวแปร x ที่เราใช้ในโค้ด สำหรับ arm นี้

คล้ายกัน arm ที่สอง match point ใดบนแกน y โดยระบุว่า field x match ถ้าค่าของมันคือ 0 และสร้างตัวแปร y สำหรับค่าของ field y arm ที่ สามไม่ระบุ literal ใด ดังนั้นมัน match Point อื่นใดและสร้างตัวแปร สำหรับทั้ง field x และ y

ในตัวอย่างนี้ ค่า p match arm ที่สองโดยอาศัย x บรรจุ 0 ดังนั้นโค้ด นี้จะ print On the y axis at 7

จำว่า expression match หยุด check arm เมื่อมันพบ pattern ที่ match ตัว แรก ดังนั้นแม้ Point { x: 0, y: 0 } อยู่บนแกน x และแกน y โค้ดนี้ จะ print เพียง On the x axis at 0

Enum

เรา destructure enum ในหนังสือนี้ (ตัวอย่างเช่น Listing 6-5 ในบทที่ 6) แต่เรายังไม่ได้พูดถึงอย่างชัดเจนว่า pattern เพื่อ destructure enum ตรงกับวิธีที่ข้อมูลที่เก็บภายใน enum ถูกนิยาม เป็นตัวอย่าง ใน Listing 19-15 เราใช้ enum Message จาก Listing 6-2 และเขียน match กับ pattern ที่จะ destructure ค่าด้านในแต่ละ

Filename: 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 {x} and in the y direction {y}");
        }
        Message::Write(text) => {
            println!("Text message: {text}");
        }
        Message::ChangeColor(r, g, b) => {
            println!("Change color to red {r}, green {g}, and blue {b}");
        }
    }
}
Listing 19-15: Destructure variant enum ที่บรรจุค่าชนิดต่างกัน

โค้ดนี้จะ print Change color to red 0, green 160, and blue 255 ลอง เปลี่ยนค่าของ msg เพื่อดูโค้ดจาก arm อื่นรัน

สำหรับ variant enum ที่ไม่มีข้อมูล เช่น Message::Quit เราไม่สามารถ destructure ค่าเพิ่มได้ เรา match บนค่า literal Message::Quit ได้ เพียงและไม่มีตัวแปรใน pattern นั้น

สำหรับ variant enum แบบ struct เช่น Message::Move เราใช้ pattern ที่ คล้ายกับ pattern ที่เราระบุเพื่อ match struct ได้ หลังชื่อ variant เรา วาง curly bracket แล้ว list field กับตัวแปรเพื่อให้เรา break apart ชิ้น เพื่อใช้ในโค้ดสำหรับ arm นี้ ที่นี่เราใช้รูป shorthand เหมือนที่เราทำ ใน Listing 19-13

สำหรับ variant enum แบบ tuple เช่น Message::Write ที่บรรจุ tuple หนึ่ง element และ Message::ChangeColor ที่บรรจุ tuple สาม element pattern คล้ายกับ pattern ที่เราระบุเพื่อ match tuple จำนวนตัวแปรใน pattern ต้อง match จำนวน element ใน variant ที่เรา match

Struct และ Enum ที่ Nest

ตอนนี้ ตัวอย่างของเราทั้งหมด match struct หรือ enum หนึ่งระดับลึก แต่ matching ทำงานบน item ที่ nest ได้ด้วย! ตัวอย่างเช่น เรา refactor โค้ด ใน Listing 19-15 เพื่อสนับสนุนสี RGB และ HSV ในข้อความ ChangeColor ได้ ดังที่แสดงใน Listing 19-16

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 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}");
        }
        _ => (),
    }
}
Listing 19-16: Match บน enum ที่ nest

Pattern ของ arm แรกใน expression match match variant enum Message::ChangeColor ที่บรรจุ variant Color::Rgb; แล้ว pattern bind กับสามค่า i32 ด้านใน Pattern ของ arm ที่สองก็ match variant enum Message::ChangeColor แต่ enum ด้านใน match Color::Hsv แทน เราระบุ เงื่อนไข complex เหล่านี้ในหนึ่ง expression match ได้ แม้สอง enum เกี่ยวข้อง

Struct และ Tuple

เราผสม match และ nest pattern destructure ในวิธี complex มากกว่าได้ ตัวอย่างต่อไปนี้แสดง destructure ที่ซับซ้อนที่เรา nest struct และ tuple ภายใน tuple และ destructure ค่า primitive ทั้งหมดออก:

fn main() {
    struct Point {
        x: i32,
        y: i32,
    }

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

โค้ดนี้ให้เรา break type complex เป็นส่วน component เพื่อให้เราใช้ค่าที่ เราสนใจแยกได้

Destructure กับ pattern คือวิธีสะดวกในการใช้ชิ้นของค่า เช่นค่าจากแต่ละ field ใน struct แยกกัน

Ignore ค่าใน Pattern

คุณเห็นว่าบางครั้งมีประโยชน์ที่จะ ignore ค่าใน pattern เช่นใน arm สุดท้ายของ match เพื่อได้ catch-all ที่ไม่ทำอะไรจริง ๆ แต่คำนึงถึงค่า ที่เป็นไปได้ที่เหลือทั้งหมด มีวิธีไม่กี่อย่างในการ ignore ค่าทั้งหมดหรือ ส่วนของค่าใน pattern — ใช้ pattern _ (ซึ่งคุณเห็นแล้ว) ใช้ pattern _ ภายใน pattern อื่น ใช้ชื่อที่เริ่มด้วย underscore หรือใช้ .. เพื่อ ignore ส่วนที่เหลือของค่า มาสำรวจวิธีและทำไมจะใช้แต่ละ pattern เหล่านี้

ค่าทั้งหมดด้วย _

เราใช้ underscore เป็น pattern wildcard ที่จะ match ค่าใดแต่ไม่ bind ให้ค่า นี่มีประโยชน์เป็นพิเศษเป็น arm สุดท้ายใน expression match แต่ เราใช้มันใน pattern ใดได้ด้วย รวม parameter ฟังก์ชัน ดังที่แสดงใน Listing 19-17

Filename: src/main.rs
fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {y}");
}

fn main() {
    foo(3, 4);
}
Listing 19-17: ใช้ _ ใน signature ฟังก์ชัน

โค้ดนี้จะ ignore ค่า 3 ที่ส่งเป็น argument แรกอย่างสมบูรณ์ และจะ print This code only uses the y parameter: 4

ในกรณีส่วนใหญ่เมื่อคุณไม่ต้องการ parameter ฟังก์ชันเฉพาะอีกแล้ว คุณจะ เปลี่ยน signature เพื่อให้มันไม่รวม parameter ที่ไม่ใช้ Ignore parameter ฟังก์ชันมีประโยชน์เป็นพิเศษในกรณีเมื่อ ตัวอย่างเช่น คุณ implement trait เมื่อคุณต้องการ signature type เฉพาะแต่ body ฟังก์ชันใน implementation ของคุณไม่ต้องการหนึ่งใน parameter คุณจะหลีกเลี่ยงการได้ warning compiler เกี่ยวกับ parameter ฟังก์ชันที่ไม่ใช้ ดังที่คุณจะถ้า คุณใช้ชื่อแทน

ส่วนของค่าด้วย _ ที่ Nest

เราใช้ _ ภายใน pattern อื่นเพื่อ ignore เพียงส่วนของค่า ตัวอย่างเช่น เมื่อเราต้องการ test เพียงส่วนของค่าแต่ไม่มีการใช้ส่วนอื่นในโค้ดที่ ตรงกันที่เราต้องการรัน Listing 19-18 แสดงโค้ดที่รับผิดชอบการจัดการค่า ของ setting ข้อกำหนด business คือ user ไม่ควรถูกอนุญาตให้เขียนทับ customization ที่มีของ setting แต่ unset setting ได้และให้ค่ามันถ้ามัน ปัจจุบัน unset

fn main() {
    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:?}");
}
Listing 19-18: ใช้ underscore ภายใน pattern ที่ match variant Some เมื่อเราไม่ต้องใช้ค่าภายใน Some

โค้ดนี้จะ print Can't overwrite an existing customized value แล้ว setting is Some(5) ใน match arm แรก เราไม่ต้อง match บนหรือใช้ค่า ภายใน variant Some ใด แต่เราต้อง test สำหรับกรณีเมื่อ setting_value และ new_setting_value คือ variant Some ในกรณีนั้น เรา print เหตุผล สำหรับการไม่เปลี่ยน setting_value และมันไม่ถูกเปลี่ยน

ในกรณีอื่นทั้งหมด (ถ้าทั้ง setting_value หรือ new_setting_value คือ None) แสดงโดย pattern _ ใน arm ที่สอง เราต้องการอนุญาต new_setting_value ให้กลายเป็น setting_value

เราใช้ underscore ในหลายที่ภายในหนึ่ง pattern เพื่อ ignore ค่าเฉพาะได้ ด้วย Listing 19-19 แสดงตัวอย่างของการ ignore ค่าที่สองและสี่ใน tuple ของห้า item

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

    match numbers {
        (first, _, third, _, fifth) => {
            println!("Some numbers: {first}, {third}, {fifth}");
        }
    }
}
Listing 19-19: Ignore หลายส่วนของ tuple

โค้ดนี้จะ print Some numbers: 2, 8, 32 และค่า 4 และ 16 จะถูก ignore

ตัวแปรที่ไม่ใช้โดยเริ่มชื่อด้วย _

ถ้าคุณสร้างตัวแปรแต่ไม่ใช้มันที่ไหน Rust จะออก warning ปกติเพราะตัวแปร ที่ไม่ใช้เป็น bug ได้ อย่างไรก็ตาม บางครั้งมีประโยชน์ที่จะสามารถสร้าง ตัวแปรที่คุณจะไม่ใช้ยัง เช่นเมื่อคุณกำลัง prototype หรือเพิ่งเริ่ม project ในสถานการณ์นี้ คุณบอก Rust ไม่ให้เตือนคุณเกี่ยวกับตัวแปรที่ ไม่ใช้โดยเริ่มชื่อของตัวแปรด้วย underscore ใน Listing 19-20 เราสร้างสอง ตัวแปรที่ไม่ใช้ แต่เมื่อเรา compile โค้ดนี้ เราควรได้ warning เกี่ยวกับ หนึ่งในพวกมันเพียง

Filename: src/main.rs
fn main() {
    let _x = 5;
    let y = 10;
}
Listing 19-20: เริ่มชื่อตัวแปรด้วย underscore เพื่อหลีกเลี่ยงการได้ warning ตัวแปรที่ไม่ใช้

ที่นี่ เราได้ warning เกี่ยวกับการไม่ใช้ตัวแปร y แต่เราไม่ได้ warning เกี่ยวกับการไม่ใช้ _x

สังเกตว่ามีความแตกต่างเล็กน้อยระหว่างการใช้เพียง _ และใช้ชื่อที่เริ่ม ด้วย underscore Syntax _x ยัง bind ค่าให้ตัวแปร ขณะที่ _ ไม่ bind เลย เพื่อแสดงกรณีที่ความแตกต่างนี้สำคัญ Listing 19-21 จะให้ error เรา

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

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

    println!("{s:?}");
}
Listing 19-21: ตัวแปรที่ไม่ใช้ที่เริ่มด้วย underscore ยัง bind ค่า ซึ่งอาจรับ ownership ของค่า

เราจะรับ error เพราะค่า s จะยังถูกย้ายเข้า _s ซึ่งป้องกันเราจาก การใช้ s อีก อย่างไรก็ตาม การใช้ underscore เพียงตัวเดียวไม่เคย bind ให้ค่า Listing 19-22 จะ compile โดยไม่มี error เพราะ s ไม่ถูกย้ายเข้า _

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

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

    println!("{s:?}");
}
Listing 19-22: การใช้ underscore ไม่ bind ค่า

โค้ดนี้ทำงานได้ดีเพราะเราไม่เคย bind s กับอะไร — มันไม่ถูกย้าย

ส่วนที่เหลือของค่าด้วย ..

กับค่าที่มีหลายส่วน เราใช้ syntax .. เพื่อใช้ส่วนเฉพาะและ ignore ที่เหลือได้ หลีกเลี่ยงความต้องการ list underscore สำหรับแต่ละค่าที่ ignore Pattern .. ignore ส่วนใดของค่าที่เราไม่ได้ match อย่างชัดเจน ใน pattern ที่เหลือ ใน Listing 19-23 เรามี struct Point ที่บรรจุพิกัด ในพื้นที่สามมิติ ใน expression match เราต้องการ operate เพียงบน พิกัด x และ ignore ค่าใน field y และ z

fn main() {
    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}"),
    }
}
Listing 19-23: Ignore field ทั้งหมดของ Point ยกเว้น x โดยใช้ ..

เรา list ค่า x แล้วเพียงรวม pattern .. นี่เร็วกว่าต้อง list y: _ และ z: _ โดยเฉพาะเมื่อเราทำงานกับ struct ที่มี field เยอะในสถานการณ์ ที่เพียงหนึ่งหรือสอง field เกี่ยวข้อง

Syntax .. จะขยายเป็นค่าเยอะตามที่มันต้องการ Listing 19-24 แสดงวิธีใช้ .. กับ tuple

Filename: src/main.rs
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {first}, {last}");
        }
    }
}
Listing 19-24: Match เพียงค่าแรกและสุดท้ายใน tuple และ ignore ค่าอื่นทั้งหมด

ในโค้ดนี้ ค่าแรกและสุดท้ายถูก match กับ first และ last .. จะ match และ ignore ทุกอย่างตรงกลาง

อย่างไรก็ตาม การใช้ .. ต้องไม่กำกวม ถ้าไม่ชัดเจนว่าค่าใดตั้งใจให้ match และค่าใดควรถูก ignore Rust จะให้ error เรา Listing 19-25 แสดงตัวอย่าง ของการใช้ .. กำกวม ดังนั้นมันจะไม่ compile

Filename: src/main.rs
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {second}")
        },
    }
}
Listing 19-25: ความพยายามใช้ .. ในวิธีกำกวม

เมื่อเรา compile ตัวอย่างนี้ เราได้ error นี้:

$ 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` (bin "patterns") due to 1 previous error

มันเป็นไปไม่ได้สำหรับ Rust ที่จะตัดสินว่ามีค่าเยอะแค่ไหนใน tuple ที่จะ ignore ก่อน matching ค่ากับ second และแล้วเยอะแค่ไหนค่าถัดไปที่จะ ignore หลังจากนั้น โค้ดนี้อาจหมายถึงเราต้องการ ignore 2, bind second ให้ 4 แล้ว ignore 8, 16 และ 32; หรือเราต้องการ ignore 2 และ 4, bind second ให้ 8 แล้ว ignore 16 และ 32; และอื่นๆ ชื่อตัวแปร second ไม่หมายถึงอะไรพิเศษให้ Rust ดังนั้นเราได้ error compiler เพราะการใช้ .. ในสองที่แบบนี้กำกวม

เพิ่ม Conditional ด้วย Match Guard

match guard คือเงื่อนไข if เพิ่มเติม ระบุหลัง pattern ใน match arm ที่ต้อง match ด้วยเพื่อ arm นั้นถูกเลือก Match guard มีประโยชน์ในการ แสดงไอเดีย complex มากกว่าที่ pattern อย่างเดียวอนุญาต อย่างไรก็ตาม สังเกตว่าพวกมันใช้ได้เพียงใน expression match ไม่ใช่ expression if let หรือ while let

เงื่อนไขใช้ตัวแปรที่สร้างใน pattern ได้ Listing 19-26 แสดง match ที่ arm แรกมี pattern Some(x) และยังมี match guard if x % 2 == 0 (ซึ่ง จะเป็น true ถ้าตัวเลขเป็นเลขคู่)

fn main() {
    let num = Some(4);

    match num {
        Some(x) if x % 2 == 0 => println!("The number {x} is even"),
        Some(x) => println!("The number {x} is odd"),
        None => (),
    }
}
Listing 19-26: เพิ่ม match guard ให้ pattern

ตัวอย่างนี้จะ print The number 4 is even เมื่อ num ถูกเปรียบเทียบ กับ pattern ใน arm แรก มัน match เพราะ Some(4) match Some(x) แล้ว match guard check ว่าเศษของการหาร x ด้วย 2 เท่ากับ 0 หรือไม่ และเพราะ มันเท่า arm แรกถูกเลือก

ถ้า num เป็น Some(5) แทน match guard ใน arm แรกจะเป็น false เพราะ เศษของ 5 หารด้วย 2 คือ 1 ซึ่งไม่เท่ากับ 0 Rust จะไป arm ที่สอง ซึ่งจะ match เพราะ arm ที่สองไม่มี match guard และดังนั้น match variant Some ใด

ไม่มีวิธีแสดงเงื่อนไข if x % 2 == 0 ภายใน pattern ดังนั้น match guard ให้เราความสามารถในการแสดง logic นี้ ข้อเสียของความ expressive เพิ่มนี้ คือ compiler ไม่พยายาม check exhaustiveness เมื่อ expression match guard เกี่ยวข้อง

เมื่อพูดถึง Listing 19-11 เรากล่าวว่าเราใช้ match guard เพื่อแก้ปัญหา pattern-shadowing ของเราได้ จำว่าเราสร้างตัวแปรใหม่ภายใน pattern ใน expression match แทนที่จะใช้ตัวแปรนอก match ตัวแปรใหม่นั้นหมายความ ว่าเราไม่สามารถ test กับค่าของตัวแปรภายนอก Listing 19-27 แสดงวิธีเราใช้ match guard เพื่อ fix ปัญหานี้

Filename: 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 = {x:?}, y = {y}");
}
Listing 19-27: ใช้ match guard เพื่อ test สำหรับความเท่ากันกับตัวแปรภายนอก

โค้ดนี้ตอนนี้จะ print Default case, x = Some(5) Pattern ใน match arm ที่สองไม่แนะนำตัวแปร y ใหม่ที่จะ shadow y ภายนอก หมายความว่าเราใช้ y ภายนอกใน match guard ได้ แทนที่จะระบุ pattern เป็น Some(y) ซึ่ง จะ shadow y ภายนอก เราระบุ Some(n) นี่สร้างตัวแปร n ใหม่ที่ไม่ shadow อะไรเพราะไม่มีตัวแปร n นอก match

Match guard if n == y ไม่ใช่ pattern และดังนั้นไม่แนะนำตัวแปรใหม่ y นี่ คือ y ภายนอก แทนที่จะเป็น y ใหม่ที่ shadow มัน และเรา มองหาค่าที่มีค่าเดียวกับ y ภายนอกได้โดยเปรียบเทียบ n กับ y

คุณใช้ operator or | ใน match guard เพื่อระบุหลาย pattern ได้ด้วย — เงื่อนไข match guard จะ apply ให้ pattern ทั้งหมด Listing 19-28 แสดง precedence เมื่อรวม pattern ที่ใช้ | กับ match guard ส่วนสำคัญของ ตัวอย่างนี้คือ match guard if y apply กับ 4, 5 และ 6 แม้ มันอาจดูเหมือน if y apply เพียงกับ 6

fn main() {
    let x = 4;
    let y = false;

    match x {
        4 | 5 | 6 if y => println!("yes"),
        _ => println!("no"),
    }
}
Listing 19-28: รวมหลาย pattern กับ match guard

เงื่อนไข match บอกว่า arm match เพียงถ้าค่าของ x เท่ากับ 4, 5 หรือ 6 และ ถ้า y คือ true เมื่อโค้ดนี้รัน pattern ของ arm แรก match เพราะ x คือ 4 แต่ match guard if y คือ false ดังนั้น arm แรกไม่ถูกเลือก โค้ดเลื่อนไป arm ที่สอง ซึ่ง match และโปรแกรมนี้ print no เหตุผลคือเงื่อนไข if apply กับทั้ง pattern 4 | 5 | 6 ไม่ เพียงค่าสุดท้าย 6 ในคำพูดอื่น precedence ของ match guard ในความสัมพันธ์ กับ pattern ทำตัวแบบนี้:

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

แทนที่จะเป็นแบบนี้:

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

หลังจากรันโค้ด พฤติกรรม precedence ชัดเจน — ถ้า match guard ถูก apply เพียงกับค่าสุดท้ายใน list ของค่าที่ระบุโดยใช้ operator | arm จะ match และโปรแกรมจะ print yes

ใช้ @ Binding

Operator at @ ให้เราสร้างตัวแปรที่บรรจุค่าในเวลาเดียวกับที่เรา กำลัง test ค่านั้นสำหรับ pattern match ใน Listing 19-29 เราต้องการ test ว่า field id ของ Message::Hello อยู่ภายใน range 3..=7 เรา ต้องการ bind ค่าให้ตัวแปร id ด้วยเพื่อให้เราใช้มันในโค้ดที่ associate กับ arm

fn main() {
    enum Message {
        Hello { id: i32 },
    }

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

    match msg {
        Message::Hello { id: id @ 3..=7 } => {
            println!("Found an id in range: {id}")
        }
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        }
        Message::Hello { id } => println!("Found some other id: {id}"),
    }
}
Listing 19-29: ใช้ @ เพื่อ bind กับค่าใน pattern ขณะที่ test มันด้วย

ตัวอย่างนี้จะ print Found an id in range: 5 โดยระบุ id @ ก่อน range 3..=7 เรา capture ค่าใดที่ match range ในตัวแปรชื่อ id ขณะ test ว่า ค่า match pattern range ด้วย

ใน arm ที่สอง ที่เรามีเพียง range ระบุใน pattern โค้ดที่ associate กับ arm ไม่มีตัวแปรที่บรรจุค่าจริงของ field id ค่าของ field id อาจ เป็น 10, 11 หรือ 12 แต่โค้ดที่ไปกับ pattern นั้นไม่รู้ว่าเป็นอันไหน โค้ด pattern ไม่สามารถใช้ค่าจาก field id ได้เพราะเราไม่ save ค่า id ในตัวแปร

ใน arm สุดท้าย ที่เราระบุตัวแปรโดยไม่มี range เรามีค่าใช้ได้เพื่อใช้ ในโค้ดของ arm ในตัวแปรชื่อ id เหตุผลคือเราใช้ syntax shorthand field struct แต่เราไม่ apply test ใดกับค่าใน field id ใน arm นี้ ดังที่เรา ทำกับสอง arm แรก — ค่าใดจะ match pattern นี้

ใช้ @ ให้เรา test ค่าและ save มันในตัวแปรภายในหนึ่ง pattern

สรุป

Pattern ของ Rust มีประโยชน์มากในการแยกระหว่างชนิดต่างกันของข้อมูล เมื่อ ใช้ใน expression match Rust รับประกันว่า pattern ของคุณครอบคลุมทุก ค่าที่เป็นไปได้ หรือโปรแกรมของคุณจะไม่ compile Pattern ใน statement let และ parameter ฟังก์ชันทำ construct เหล่านั้นมีประโยชน์มากขึ้น เปิดใช้ destructure ของค่าเป็นส่วนเล็กลงและ assign ส่วนเหล่านั้นให้ ตัวแปร เราสร้าง pattern simple หรือ complex เพื่อตอบโจทย์ของเราได้

ถัดไป สำหรับบทที่ก่อนสุดท้ายของหนังสือ เราจะดูแง่มุมขั้นสูงของฟีเจอร์ ของ Rust ที่หลากหลาย