Rust Result
Option says "there might be no value". Result says "this might have failed, and here is why". Opening a file, turning text into a number, sending a message: all of these can go wrong, and when they do, you usually want to know what happened. Result is an enum with two variants:
Ok(value): it worked, and here is the result.Err(error): it failed, and here is the error that explains why.
You have already met one. parse returns a Result, because not every piece of text is a number.
fn main() {
let good: Result<i32, _> = "42".parse::<i32>();
let bad: Result<i32, _> = "forty".parse::<i32>();
println!("{:?}", good);
println!("{:?}", bad);
}Ok(42)
Err(ParseIntError { kind: InvalidDigit })Handling both outcomes
Just like with Option, a match lets you handle both cases. The error value usually has a friendly message when printed with {}:
fn main() {
for text in ["120", "12a", ""] {
match text.parse::<u32>() {
Ok(n) => println!("{:?} is the number {}", text, n),
Err(e) => println!("{:?} is not a number: {}", text, e),
}
}
}"120" is the number 120 "12a" is not a number: invalid digit found in string "" is not a number: cannot parse integer from empty string
Asking again instead of crashing
Up to now our input programs stopped when the user typed something wrong. With Result and a loop, we can be kinder and simply ask again:
use std::io;
fn main() {
let age: u32 = loop {
println!("How old are you?");
let mut line = String::new();
io::stdin().read_line(&mut line).expect("could not read");
match line.trim().parse() {
Ok(n) => break n,
Err(_) => println!("Please type your age as a number, like 15."),
}
};
println!("Thank you! You are {}.", age);
}fifteen 15
How old are you? Please type your age as a number, like 15. How old are you? Thank you! You are 15.
Returning your own Result
Your functions can return a Result too. The two types in the angle brackets are the type of a success and the type of an error. A String makes a simple, readable error:
fn withdraw(balance: u32, amount: u32) -> Result<u32, String> {
if amount > balance {
Err(format!("cannot take {} from a balance of {}", amount, balance))
} else {
Ok(balance - amount)
}
}
fn main() {
match withdraw(500, 120) {
Ok(left) => println!("Done, {} left", left),
Err(msg) => println!("Sorry: {}", msg),
}
match withdraw(500, 900) {
Ok(left) => println!("Done, {} left", left),
Err(msg) => println!("Sorry: {}", msg),
}
}Done, 380 left Sorry: cannot take 900 from a balance of 500
unwrap and expect
unwrap() takes the value from an Ok and stops the program on an Err. expect("message") does the same with your own message, which makes it much easier to see what went wrong. Use them in small experiments, or where failure really is impossible, and use match everywhere else.
Practice exercises
- Write a program that asks for two numbers and keeps asking until both are valid.
- Write
fn check_password(p: &str) -> Result<(), String>that fails when the password is shorter than 8 characters. - Print the error you get from parsing "3.5" as an
i32, and then parse it as anf64instead.