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)
}
| call | result |
|---|---|
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 defaultd:"x".parse::<u8>().unwrap_or(0)is0.
Common mistakes
- No trim. The line
17printserror: not a numberinstead ofok: 17. - Wrong type. Parsing as
u32rejects-8, a perfectly goodi32. - Parsing wider, then
as i32. Going throughi64turns2147483648intook: -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…