RustArena

Step 2 of 7

Handling a Result with match

A Result is an enum, so the most basic way to look inside is the tool you already know for enums: match. Each arm handles one variant and pulls its value out.

fn main() {
    let inputs = ["3", "12", "two", "", "-1"];
    for input in inputs {
        match input.parse::<u32>() {
            Ok(qty) => println!("{input:?}: ordered {qty}"),
            Err(e) => println!("{input:?}: {e}"),
        }
    }
}
"3": ordered 3
"12": ordered 12
"two": invalid digit found in string
"": cannot parse integer from empty string
"-1": invalid digit found in string

Parse errors

Printing a ParseIntError with {} gives a message meant for people; {:?} shows its inner structure, meant for developers.

Input parsed as u8 {} {:?}
"" cannot parse integer from empty string ParseIntError { kind: Empty }
"4x" invalid digit found in string ParseIntError { kind: InvalidDigit }
"300" number too large to fit in target type ParseIntError { kind: PosOverflow }

Other types have their own errors: parsing an f64 can fail with a ParseFloatError (invalid float literal), a bool with a ParseBoolError (anything but exactly true or false).

Handling your own Results

Your own functions work the same way. With withdraw from the last step:

fn withdraw(balance: u32, amount: u32) -> Result<u32, String> {
    if amount > balance {
        return Err(String::from("insufficient funds"));
    }
    Ok(balance - amount)
}

fn main() {
    let mut balance = 100;
    for amount in [40, 80, 60] {
        match withdraw(balance, amount) {
            Ok(new_balance) => {
                balance = new_balance;
                println!("withdrew {amount}, balance {balance}");
            }
            Err(msg) => println!("declined {amount}: {msg}"),
        }
    }
}
withdrew 40, balance 60
declined 80: insufficient funds
withdrew 60, balance 0

if let and let...else

When you only care about one side, if let and let...else (from the enums module) work on Result too:

fn port_from(text: &str) -> u16 {
    let Ok(port) = text.parse::<u16>() else {
        println!("bad port {text:?}, using 8080");
        return 8080;
    };
    port
}

fn main() {
    println!("{}", port_from("3000"));
    println!("{}", port_from("99999"));
    if let Err(e) = "12.5.1".parse::<f64>() {
        println!("version is not a number: {e}");
    }
}
3000
bad port "99999", using 8080
8080
version is not a number: invalid float literal

Common mistakes

fn main() {
    match "5".parse::<i32>() {
        Ok(n) => println!("{n}"),
    }
}
error[E0004]: non-exhaustive patterns: `Err(_)` not covered

Your task

A shop receives order quantities, one per line. Read every line, and for each one:

At the end print total: <total>. For the input 3, 5, abc, an empty line and 2, the output is:

skipping "abc": invalid digit found in string
skipping "": cannot parse integer from empty string
total: 10

Challenge 2

A game reads the level to start on as text. Write fn level_label(input: &str) -> String that parses the trimmed input as a u8 and returns:

Challenge 3

An ATM reads one line: the amount to withdraw from a balance of 100. Use the withdraw function in the starter code. Print:

You'll need a match inside a match.

Previous: panic! or Result? Next: unwrap, expect and safe defaults