Skip to content
Lesson 15 of 15

Step 1 of 7 · Reading · ~4 min

Learn

Structs, Enums, and Error Handling

Error Handling: Result and Option

By the end of this lesson you can write a function that can fail, hand the failure back as a value instead of crashing, and deal with both outcomes at the call site with match.

Two enums you already have

The previous lesson showed enums whose variants carry data. Rust's error handling is two such enums, defined in the standard library. T and E are type parameters: placeholders that get a real type wherever the enum is used, the same way Vec<T> becomes Vec<i64>.

enum Option<T> { Some(T), None }        // a value, or nothing
enum Result<T, E> { Ok(T), Err(E) }     // a value, or an error saying why not

Use Option when absence needs no explanation: v.get(99) is None. Use Result when the caller deserves a reason. Both are ordinary values that you return, store and match. Because match must be exhaustive, you cannot reach the success value without also writing the failure arm.

parse returns a Result

str::parse turns text into a number. For an integer type it returns Result<T, ParseIntError>, where ParseIntError is the standard library's error type; printed with {} it reads like invalid digit found in string.

let good = "250".parse::<u8>();   // Ok(250)
let big  = "300".parse::<u8>();   // Err: number too large to fit in target type
let neg  = "-4".parse::<u8>();    // Err: u8 has no negative values
let word = "ten".parse::<u8>();   // Err: invalid digit found in string

The type after :: decides what is valid. Every out-of-range value is an Err; parse never wraps around.

Writing your own fallible function

This function accepts a network port, 1 to 65535, and replaces the library error with its own String message:

fn parse_port(text: &str) -> Result<u16, String> {
    let clean = text.trim();
    let port = match clean.parse::<u16>() {
        Ok(p) => p,
        Err(_) => return Err(format!("bad port: {}", clean)),
    };
    if port == 0 {
        return Err(String::from("port 0 is reserved"));
    }
    Ok(port)
}
callresult
parse_port("8080")Ok(8080)
parse_port(" 443\t")Ok(443)
parse_port("0")Err("port 0 is reserved")
parse_port("70000")Err("bad port: 70000")

Err(_) matches any error and discards what is inside it. The shorter form is .map_err(closure): it leaves an Ok untouched and runs the closure on the value inside an Err. For example, clean.parse::<u16>().map_err(|_| String::from("bad")) has type Result<u16, String>.

The trap: whitespace

parse does not skip spaces, tabs or the \n that read_line keeps:

"443\n".parse::<u16>()          // Err: invalid digit found in string
"443\n".trim().parse::<u16>()   // Ok(443)

? sends the error upward

Inside a function that itself returns Result, expr? means: if expr is Ok(v), carry on with v; if it is Err(e), return Err(e) from this function immediately.

fn parse_pair(a: &str, b: &str) -> Result<(u16, u16), String> {
    let first = parse_port(a)?;
    let second = parse_port(b)?;
    Ok((first, second))
}
// parse_pair("80", "0") is Err("port 0 is reserved"): the second ? returned it.

? only compiles in a function returning Result (or Option), and the error types must match: both are String here. In a main that returns nothing, use match.

Getting a value out

  • .unwrap() gives the value or panics: the program stops with a message. The grader reports that as a runtime error.
  • .unwrap_or(d) gives the value or the default d: "x".parse::<u8>().unwrap_or(0) is 0.

Common mistakes

  • No trim. The line 17 prints error: not a number instead of ok: 17.
  • Wrong type. Parsing as u32 rejects -8, a perfectly good i32.
  • Parsing wider, then as i32. Going through i64 turns 2147483648 into ok: -2147483648; it must be an error.
  • Wrong message or a panic. Passing on the library error prints error: invalid digit found in string; calling .unwrap() crashes on the first bad line.

Your exercise

Fill in parse_int so text holding a valid i32 (ignoring surrounding whitespace) becomes Ok with that value, and anything else becomes Err with the message not a number. main already reads every line, calls your function and prints the outcome. The most likely slip is the range: values that do not fit in i32 are errors, not wrapped numbers.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…