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

ทุกที่ที่ Pattern ถูกใช้ได้

Pattern โผล่ในที่หลายแห่งใน Rust และคุณใช้พวกมันมากโดยไม่รู้ตัว! ส่วนนี้ พูดถึงทุกที่ที่ pattern valid

match Arm

ตามที่พูดถึงในบทที่ 6 เราใช้ pattern ใน arm ของ expression match อย่าง เป็นทางการ expression match ถูกนิยามเป็นคีย์เวิร์ด match, ค่าที่จะ match บน และหนึ่งหรือมากกว่า match arm ที่ประกอบด้วย pattern และ expression ที่จะรันถ้าค่า match pattern ของ arm นั้น แบบนี้:

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

ตัวอย่างเช่น นี่คือ expression match จาก Listing 6-5 ที่ match บนค่า Option<i32> ในตัวแปร x:

match x {
    None => None,
    Some(i) => Some(i + 1),
}

Pattern ใน expression match นี้คือ None และ Some(i) ทางซ้ายของ ลูกศรแต่ละตัว

ข้อกำหนดหนึ่งสำหรับ expression match คือพวกมันต้อง exhaustive ในความ หมายที่ความเป็นไปได้ทั้งหมดสำหรับค่าใน expression match ต้องถูกคำนึง ถึง วิธีหนึ่งในการรับประกันว่าคุณครอบคลุมทุกความเป็นไปได้คือมี pattern catch-all สำหรับ arm สุดท้าย — ตัวอย่างเช่น ชื่อตัวแปรที่ match ค่าใดได้ ไม่สามารถ fail และดังนั้นครอบคลุมทุกกรณีที่เหลือ

pattern เฉพาะ _ จะ match อะไรก็ได้ แต่มันไม่ bind ให้ตัวแปร ดังนั้นมัน มักถูกใช้ใน match arm สุดท้าย Pattern _ มีประโยชน์เมื่อคุณต้องการ ignore ค่าใดที่ไม่ระบุ ตัวอย่างเช่น เราจะครอบคลุม pattern _ ในราย ละเอียดมากขึ้นใน “Ignore ค่าใน Pattern” ภายหลังในบทนี้

let Statement

ก่อนหน้าบทนี้ เราเพียงพูดถึงการใช้ pattern กับ match และ if let อย่างชัดเจน แต่จริง ๆ เราใช้ pattern ในที่อื่นด้วย รวมใน statement let ตัวอย่างเช่น พิจารณา assignment ตัวแปรตรงไปตรงมานี้กับ let:

#![allow(unused)]
fn main() {
let x = 5;
}

ทุกครั้งที่คุณใช้ statement let แบบนี้ คุณกำลังใช้ pattern แม้คุณ อาจไม่ได้สังเกต! เป็นทางการมากขึ้น statement let ดูแบบนี้:

let PATTERN = EXPRESSION;

ใน statement เช่น let x = 5; กับชื่อตัวแปรในช่อง PATTERN ชื่อตัวแปร เป็นเพียงรูปแบบที่ง่ายเป็นพิเศษของ pattern Rust เปรียบเทียบ expression กับ pattern และ assign ชื่อที่มันพบ ดังนั้น ในตัวอย่าง let x = 5;, x คือ pattern ที่หมายถึง “bind สิ่งที่ match ที่นี่ให้ตัวแปร x” เพราะชื่อ x คือ pattern ทั้งหมด pattern นี้มีผลหมายความว่า “bind ทุก อย่างให้ตัวแปร x ไม่ว่าค่าเป็นอะไร”

เพื่อเห็นแง่มุม pattern-matching ของ let ชัดเจนมากขึ้น พิจารณา Listing 19-1 ซึ่งใช้ pattern กับ let เพื่อ destructure tuple

fn main() {
    let (x, y, z) = (1, 2, 3);
}
Listing 19-1: ใช้ pattern เพื่อ destructure tuple และสร้างตัวแปรสามตัวพร้อมกัน

ที่นี่ เรา match tuple กับ pattern Rust เปรียบเทียบค่า (1, 2, 3) กับ pattern (x, y, z) และเห็นว่าค่า match pattern — นั่นคือ มันเห็นว่า จำนวน element เหมือนกันในทั้งสอง — ดังนั้น Rust bind 1 ให้ x, 2 ให้ y และ 3 ให้ z คุณคิด pattern tuple นี้เป็นการ nest pattern ตัวแปร สามตัวภายในมันได้

ถ้าจำนวน element ใน pattern ไม่ match จำนวน element ใน tuple type โดย รวมจะไม่ match และเราจะได้ error compiler ตัวอย่างเช่น Listing 19-2 แสดงความพยายาม destructure tuple ที่มีสาม element เป็นสองตัวแปร ซึ่งจะ ไม่ทำงาน

fn main() {
    let (x, y) = (1, 2, 3);
}
Listing 19-2: สร้าง pattern ผิดที่ตัวแปรไม่ match จำนวน element ใน tuple

การพยายาม compile โค้ดนี้ให้ผลเป็น error type นี้:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
 --> src/main.rs:2:9
  |
2 |     let (x, y) = (1, 2, 3);
  |         ^^^^^^   --------- this expression has type `({integer}, {integer}, {integer})`
  |         |
  |         expected a tuple with 3 elements, found one with 2 elements
  |
  = note: expected tuple `({integer}, {integer}, {integer})`
             found tuple `(_, _)`

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

เพื่อ fix error เรา ignore หนึ่งหรือมากกว่าของค่าใน tuple โดยใช้ _ หรือ .. ดังที่คุณจะเห็นในส่วน “Ignore ค่าใน Pattern” ได้ ถ้าปัญหาคือ เรามีตัวแปรเยอะเกินไปใน pattern วิธีแก้คือทำให้ type match โดยลบตัวแปร เพื่อให้จำนวนตัวแปรเท่ากับจำนวน element ใน tuple

Expression if let แบบ Conditional

ในบทที่ 6 เราพูดถึงวิธีใช้ expression if let หลัก ๆ เป็นวิธีสั้นในการ เขียนเทียบเท่า match ที่ match เพียงหนึ่งกรณี ตัวเลือก if let มี else ที่ตรงกันบรรจุโค้ดที่จะรันถ้า pattern ใน if let ไม่ match ได้

Listing 19-3 แสดงว่ามันเป็นไปได้ที่จะผสม if let, else if และ expression else if let ด้วย การทำเช่นนั้นให้เราความยืดหยุ่นมากกว่า expression match ที่เราแสดงเพียงหนึ่งค่าเพื่อเปรียบเทียบกับ pattern นอกจากนี้ Rust ไม่ต้องการให้เงื่อนไขในชุดของ if let, else if และ else if let arm เกี่ยวข้องกัน

โค้ดใน Listing 19-3 ตัดสินสีอะไรจะทำพื้นหลังของคุณตามชุดของการ check สำหรับเงื่อนไขหลายอย่าง สำหรับตัวอย่างนี้ เราสร้างตัวแปรกับค่า hardcoded ที่โปรแกรมจริงอาจรับจาก input ของ user

Filename: src/main.rs
fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

    if let Some(color) = favorite_color {
        println!("Using your favorite color, {color}, as the background");
    } else if is_tuesday {
        println!("Tuesday is green day!");
    } else if let Ok(age) = age {
        if age > 30 {
            println!("Using purple as the background color");
        } else {
            println!("Using orange as the background color");
        }
    } else {
        println!("Using blue as the background color");
    }
}
Listing 19-3: ผสม if let, else if, else if let และ else

ถ้า user ระบุสีโปรด สีนั้นถูกใช้เป็นพื้นหลัง ถ้าไม่มีสีโปรดระบุและวัน นี้คือวันอังคาร สีพื้นหลังคือเขียว มิฉะนั้น ถ้า user ระบุอายุของพวกเขา เป็น string และเราสามารถ parse มันเป็นตัวเลขสำเร็จ สีคือม่วงหรือส้ม ขึ้นกับค่าของตัวเลข ถ้าไม่มีเงื่อนไขเหล่านี้ apply สีพื้นหลังคือฟ้า

โครงสร้าง conditional นี้ให้เราสนับสนุนข้อกำหนด complex ด้วยค่า hardcoded ที่เรามีที่นี่ ตัวอย่างนี้จะ print Using purple as the background color

คุณเห็นได้ว่า if let สามารถแนะนำตัวแปรใหม่ที่ shadow ตัวแปรที่มีใน วิธีเดียวกับที่ match arm ทำได้ — บรรทัด if let Ok(age) = age แนะนำตัวแปร age ใหม่ที่บรรจุค่าภายใน variant Ok shadow ตัวแปร age ที่มี นี่ หมายความว่าเราต้องวางเงื่อนไข if age > 30 ภายใน block นั้น — เราไม่ สามารถรวมสองเงื่อนไขนี้เป็น if let Ok(age) = age && age > 30 age ใหม่ที่เราต้องการเปรียบเทียบกับ 30 ไม่ valid จนกระทั่ง scope ใหม่เริ่ม ด้วย curly bracket

ข้อเสียของการใช้ expression if let คือ compiler ไม่ check exhaustiveness ขณะที่กับ expression match มัน check ถ้าเราละ block else สุดท้ายและดังนั้นพลาดการจัดการบางกรณี compiler จะไม่เตือนเรา เกี่ยวกับ bug logic ที่เป็นไปได้

while let Conditional Loop

คล้ายกับการสร้างของ if let loop conditional while let อนุญาตให้ loop while รันตราบใดที่ pattern ยัง match ใน Listing 19-4 เราแสดง loop while let ที่รอข้อความที่ส่งระหว่างเธรด แต่ในกรณีนี้ check Result แทน Option

fn main() {
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        for val in [1, 2, 3] {
            tx.send(val).unwrap();
        }
    });

    while let Ok(value) = rx.recv() {
        println!("{value}");
    }
}
Listing 19-4: ใช้ loop while let เพื่อ print ค่าตราบใดที่ rx.recv() return Ok

ตัวอย่างนี้ print 1, 2 แล้ว 3 เมธอด recv รับข้อความตัวแรกออก จากฝั่ง receiver ของ channel และ return Ok(value) เมื่อเราเห็น recv ครั้งแรกในบทที่ 16 เรา unwrap error โดยตรง หรือเรา interact กับมันเป็น iterator โดยใช้ loop for อย่างไรก็ตาม ดังที่ Listing 19-4 แสดง เราใช้ while let ได้ด้วย เพราะเมธอด recv return Ok แต่ละครั้งที่ข้อความ มา ตราบใดที่ sender มี และแล้วผลิต Err เมื่อฝั่ง sender disconnect

Loop for

ใน loop for ค่าที่ตามคีย์เวิร์ด for โดยตรงคือ pattern ตัวอย่างเช่น ใน for x in y, x คือ pattern Listing 19-5 สาธิตวิธีใช้ pattern ใน loop for เพื่อ destructure หรือ break apart tuple เป็นส่วนของ loop for

fn main() {
    let v = vec!['a', 'b', 'c'];

    for (index, value) in v.iter().enumerate() {
        println!("{value} is at index {index}");
    }
}
Listing 19-5: ใช้ pattern ใน loop for เพื่อ destructure tuple

โค้ดใน Listing 19-5 จะ print ต่อไปนี้:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2

เรา adapt iterator โดยใช้เมธอด enumerate เพื่อให้มันผลิตค่าและ index สำหรับค่านั้น วางใน tuple ค่าแรกที่ผลิตคือ tuple (0, 'a') เมื่อค่านี้ ถูก match กับ pattern (index, value) index จะเป็น 0 และ value จะ เป็น 'a' print บรรทัดแรกของ output

Parameter ฟังก์ชัน

Parameter ฟังก์ชันเป็น pattern ได้เช่นกัน โค้ดใน Listing 19-6 ซึ่ง ประกาศฟังก์ชันชื่อ foo ที่รับหนึ่ง parameter ชื่อ x ของ type i32 ควรดูคุ้นเคยแล้ว

fn foo(x: i32) {
    // code goes here
}

fn main() {}
Listing 19-6: signature ฟังก์ชันที่ใช้ pattern ใน parameter

ส่วน x คือ pattern! ดังที่เราทำกับ let เรา match tuple ใน argument ของฟังก์ชันกับ pattern ได้ Listing 19-7 แบ่งค่าใน tuple เมื่อเราส่งมัน ให้ฟังก์ชัน

Filename: src/main.rs
fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({x}, {y})");
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}
Listing 19-7: ฟังก์ชันที่มี parameter ที่ destructure tuple

โค้ดนี้ print Current location: (3, 5) ค่า &(3, 5) match pattern &(x, y) ดังนั้น x คือค่า 3 และ y คือค่า 5

เราใช้ pattern ใน list parameter closure ในวิธีเดียวกับใน list parameter ฟังก์ชันได้เพราะ closure คล้ายกับฟังก์ชัน ดังที่พูดถึงในบทที่ 13

ที่จุดนี้ คุณเห็นวิธีหลายอย่างในการใช้ pattern แต่ pattern ไม่ทำงาน เหมือนกันในทุกที่ที่เราใช้ได้ ในที่บางที่ pattern ต้อง irrefutable; ใน สถานการณ์อื่น พวกมันเป็น refutable ได้ เราจะพูดถึงสองแนวคิดนี้ถัดไป