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

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}"),
        _ => (),
    }
}
Listing 6-6: 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}");
    }
}
Listing 6-7: เช็คว่ารัฐมีอยู่ในปี 1900 หรือไม่ โดยใช้ conditional ซ้อนภายใน 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}");
    }
}
Listing 6-8: ใช้ 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}");
    }
}
Listing 6-9: ใช้ 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 กัน