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
input.parse::<u32>()returnsResult<u32, ParseIntError>.ParseIntError(its full path isstd::num::ParseIntError) is the standard library's error type for failed integer parsing.Ok(qty)matches a success and names the numberqty.Err(e)matches a failure and names the errore.- The compiler checks that you handled both. You can't forget the error case by accident.
-1fails because au32can't be negative: the-is an "invalid digit".
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
- Leaving out the
Errarm. Amatchmust cover every variant:
fn main() {
match "5".parse::<i32>() {
Ok(n) => println!("{n}"),
}
}
error[E0004]: non-exhaustive patterns: `Err(_)` not covered
- Parsing a line with its newline still on it.
read_linekeeps the\n, and"42\n"is not a number (invalid digit found in string). Call.trim()before.parse(). - Printing the error with
{:?}for users.ParseIntError { kind: InvalidDigit }is for developers. Use{}in messages people read.
Your task
A shop receives order quantities, one per line. Read every line, and for each one:
- if it parses as a
u32(after trimming), add it tototal - otherwise print
skipping "<line>": <error>with the trimmed line and the error's{}message
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:
"level 5"for a valid number like5"invalid level '<trimmed input>': <error>"otherwise, for example"invalid level 'seven': invalid digit found in string"
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:
new balance: <n>if the withdrawal worksdeclined: <message>ifwithdrawreturns an errornot a number: <error>if the line isn't a validu32
You'll need a match inside a match.
Previous: panic! or Result? Next: unwrap, expect and safe defaults