Control Flow ที่กระชับด้วย if let และ let...else
syntax if let ให้คุณรวม if และ let เป็นวิธีจัดการค่าที่ match
pattern เดียวที่กระชับกว่า ขณะละเว้นค่าที่เหลือ พิจารณาโปรแกรมใน
Listing 6-6 ที่ match ค่า Option<u8> ในตัวแปร config_max แต่อยาก
execute โค้ดเฉพาะถ้าค่าเป็น variant Some
fn main() {
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {max}"),
_ => (),
}
}
match ที่สนใจแค่การ execute โค้ดเมื่อค่าเป็น Someถ้าค่าเป็น Some เราพิมพ์ค่าใน variant Some โดย bind ค่ากับตัวแปร max
ใน pattern เราไม่อยากทำอะไรกับค่า None เพื่อ satisfy match expression
เราต้องเพิ่ม _ => () หลังประมวลผลแค่ variant เดียว ซึ่งเป็น boilerplate
ที่น่ารำคาญที่ต้องเพิ่ม
แทนนั้น เราเขียนนี้ในแบบสั้นกว่าโดยใช้ if let ได้ โค้ดต่อไปนี้ทำงาน
เหมือนกับ match ใน Listing 6-6:
fn main() {
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {max}");
}
}
syntax if let รับ pattern และ expression ที่คั่นด้วยเครื่องหมายเท่ากับ
มันทำงานแบบเดียวกับ match ที่ expression ให้กับ match และ pattern
คือ arm แรกของมัน ในกรณีนี้ pattern คือ Some(max) และ max bind กับ
ค่าภายใน Some เราใช้ max ใน body ของ block if let ได้แบบเดียวกับที่
เราใช้ max ใน match arm ที่สอดคล้อง โค้ดใน block if let รันเฉพาะถ้าค่า
match pattern
การใช้ if let หมายถึงพิมพ์น้อยลง indent น้อยลง และ boilerplate น้อยลง
อย่างไรก็ตาม คุณเสียการเช็ค exhaustive ที่ match บังคับใช้ ที่รับประกัน
ว่าคุณไม่ลืมจัดการกรณีใด ๆ การเลือกระหว่าง match และ if let ขึ้นกับ
สิ่งที่คุณทำในสถานการณ์ของคุณ และว่าการได้ความกระชับเป็น trade-off ที่
เหมาะสมสำหรับการเสียการเช็ค exhaustive
พูดอีกอย่าง คุณคิดถึง if let เป็น syntax sugar ของ match ที่รันโค้ด
เมื่อค่า match pattern หนึ่งแล้วละเว้นค่าอื่นทั้งหมดได้
เรารวม else กับ if let ได้ block โค้ดที่ไปกับ else เหมือนกับ block
โค้ดที่จะไปกับกรณี _ ใน match expression ที่เทียบเท่ากับ if let
และ else จำการประกาศ enum Coin ใน Listing 6-4 ได้ ที่ variant
Quarter เก็บค่า UsState ด้วย ถ้าเราอยากนับเหรียญที่ไม่ใช่ quarter
ทั้งหมดที่เราเห็น พร้อมประกาศรัฐของ quarter ด้วย เราทำได้ด้วย match
expression แบบนี้:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn main() {
let coin = Coin::Penny;
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {state:?}!"),
_ => count += 1,
}
}
หรือเราใช้ if let และ else expression แบบนี้ได้:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn main() {
let coin = Coin::Penny;
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {state:?}!");
} else {
count += 1;
}
}
อยู่บน “Happy Path” ด้วย let...else
Pattern ที่ใช้บ่อยคือทำการคำนวณบางอย่างเมื่อค่ามีอยู่ และ return ค่า
default ไม่อย่างนั้น ต่อจากตัวอย่างของเรา เหรียญที่มีค่า UsState ถ้า
เราอยากพูดอะไรตลก ๆ ขึ้นกับว่ารัฐบนเหรียญ quarter เก่าแค่ไหน เราอาจ
แนะนำเมธอดบน UsState เพื่อเช็คอายุของรัฐ ดังนี้:
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn describe_state_quarter(coin: Coin) -> Option<String> {
if let Coin::Quarter(state) = coin {
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
} else {
None
}
}
fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
จากนั้น เราอาจใช้ if let match บนชนิดของเหรียญ แนะนำตัวแปร state
ภายใน body ของ condition เหมือนใน Listing 6-7
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn describe_state_quarter(coin: Coin) -> Option<String> {
if let Coin::Quarter(state) = coin {
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
} else {
None
}
}
fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
if letนั่นได้งาน แต่มันผลักงานเข้า body ของ statement if let และถ้างานที่ทำ
ซับซ้อนกว่า มันอาจตามยากว่า branch ระดับบนสุดผูกกันอย่างไร เราใช้ประโยชน์
จากข้อเท็จจริงที่ว่า expression produce ค่า ได้ ทั้งเพื่อ produce state
จาก if let หรือ return เร็ว เหมือนใน Listing 6-8 (คุณทำคล้ายกันกับ
match ก็ได้)
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn describe_state_quarter(coin: Coin) -> Option<String> {
let state = if let Coin::Quarter(state) = coin {
state
} else {
return None;
};
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
}
fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
if let เพื่อ produce ค่าหรือ return เร็วอันนี้ก็ตามได้ยากในแบบของมันเอง! Branch หนึ่งของ if let produce ค่า
และอีกอันคืน return จากฟังก์ชันทั้งหมด
เพื่อทำให้ pattern ที่ใช้บ่อยนี้แสดงออกได้ดีขึ้น Rust มี let...else
syntax let...else รับ pattern ทางซ้ายและ expression ทางขวา คล้าย
if let มาก แต่ไม่มี branch if มีแค่ branch else ถ้า pattern match
มันจะ bind ค่าจาก pattern ใน scope ภายนอก ถ้า pattern ไม่ match
โปรแกรมจะไหลเข้า arm else ซึ่งต้อง return จากฟังก์ชัน
ใน Listing 6-9 คุณเห็นว่า Listing 6-8 หน้าตาเป็นอย่างไรเมื่อใช้
let...else แทน if let
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
impl UsState {
fn existed_in(&self, year: u16) -> bool {
match self {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn describe_state_quarter(coin: Coin) -> Option<String> {
let Coin::Quarter(state) = coin else {
return None;
};
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
}
fn main() {
if let Some(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
let...else เพื่อทำให้ flow ผ่านฟังก์ชันชัดเจนสังเกตว่ามันอยู่บน “happy path” ใน body หลักของฟังก์ชันแบบนี้ โดยไม่มี
control flow ที่ต่างกันอย่างมีนัยสำหรับสอง branch แบบที่ if let ทำ
ถ้าคุณมีสถานการณ์ที่โปรแกรมของคุณมี logic ที่ยาวเกินไปที่จะแสดงด้วย
match จำไว้ว่า if let และ let...else ก็อยู่ในกล่องเครื่องมือ Rust
ของคุณด้วย
สรุป
ตอนนี้เราครอบคลุมวิธีใช้ enum เพื่อสร้าง type custom ที่เป็นได้หนึ่งใน
ชุดของค่า enumerate แล้ว เราแสดงว่า type Option<T> ของ standard
library ช่วยคุณใช้ระบบ type ป้องกัน error อย่างไร เมื่อค่า enum มีข้อมูล
ภายใน คุณใช้ match หรือ if let ดึงและใช้ค่าเหล่านั้นได้ ขึ้นกับว่ามี
กี่กรณีที่คุณต้องจัดการ
โปรแกรม Rust ของคุณตอนนี้แสดงแนวคิดใน domain ของคุณโดยใช้ struct และ enum ได้ การสร้าง type custom เพื่อใช้ใน API ของคุณรับประกัน type safety — compiler จะรับประกันว่าฟังก์ชันของคุณได้รับเฉพาะค่าของ type ที่แต่ละฟังก์ชันคาด
เพื่อให้ API ที่จัดระเบียบดีกับ user ของคุณ ที่ตรงไปตรงมาในการใช้ และ เปิดเผยเฉพาะสิ่งที่ user ของคุณต้องการ ทีนี้มาดู module ของ Rust กัน