ทุกที่ที่ 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);
}
ที่นี่ เรา 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);
}
การพยายาม 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
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");
}
}
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}");
}
}
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}");
}
}
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() {}
ส่วน x คือ pattern! ดังที่เราทำกับ let เรา match tuple ใน argument
ของฟังก์ชันกับ pattern ได้ Listing 19-7 แบ่งค่าใน tuple เมื่อเราส่งมัน
ให้ฟังก์ชัน
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
โค้ดนี้ 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 ได้ เราจะพูดถึงสองแนวคิดนี้ถัดไป