let else Statements

For the common case of matching a pattern and returning from the function, use let else. The “else” case must diverge (return, break, or panic - anything but falling off the end of the block).

fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> {
    // TODO: The structure of this code is difficult to follow -- rewrite it with let-else!
    if let Some(s) = maybe_string {
        if let Some(first_byte_char) = s.chars().next() {
            if let Some(digit) = first_byte_char.to_digit(16) {
                Ok(digit)
            } else {
                Err(String::from("not a hex digit"))
            }
        } else {
            Err(String::from("got empty string"))
        }
    } else {
        Err(String::from("got None"))
    }
}

fn main() {
    println!("result: {:?}", hex_or_die_trying(Some(String::from("foo"))));
}

if-lets can pile up, as shown. The let-else construct supports flattening this nested code. Rewrite the awkward version for students, so they can see the transformation.

The rewritten version is:

#![allow(unused)]
fn main() {
fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> {
    let Some(s) = maybe_string else {
        return Err(String::from("got None"));
    };

    let Some(first_byte_char) = s.chars().next() else {
        return Err(String::from("got empty string"));
    };

    let Some(digit) = first_byte_char.to_digit(16) else {
        return Err(String::from("not a hex digit"));
    };

    Ok(digit)
}
}