Error Handling
- Rust has a robust error handling system
Panic
- Rust has a
panic!macro to stop the program when an error occurs
fn main() {
panic!("crash and burn");
}Runtime Error:
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
panic!macro can be used to generate a backtrace
Result Enum
- Rust uses the
Resultenum to handle errors
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("Problem opening the file: {:?}", error)
},
};
}matchkeyword is used to handle theResultenumResulthas two variants:OkandErrOkvariant indicates success and contains a valueErrvariant indicates failure and contains an error message
use std::{fs::File, io::ErrorKind};
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error)
}
},
};
}- Nested
matchexpressions can be used to handle different error types ErrorKindenum is used to handle different error types- when
ErrorKindisNotFound,createis used to create the file namedhello.txt
Closure
- Rust has a
unwrapmethod to handle errors
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {:?}", error);
})
} else {
panic!("Problem opening the file: {:?}", error);
}
});
}unwrap_or_elsemethod is used to handle errors
Expect
- Rust has an
expectmethod to handle errors
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open the file");
}? Operator
- Rust has a
?operator to handle errors
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
fn main() {}?operator is used to handle errors
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
fn main() {}?operator can be used to handle errors in a more concise wayf.read_to_string(&mut s)?will returnOk(s)if successful, otherwise it will returnErr(e)
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
fn main() {}
?operator can be chained to handle errors in a more concise wayFile::open("hello.txt")?.read_to_string(&mut s)?will returnOk(s)if successful, otherwise it will returnErr(e)
use std::fs::File;
use std::io;
use std::io::Read;
fn main() {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}the ? operator can only be used in a function that returns Result or
Option (or another type that implements FromResidual)
use std::fs::File;
use std::io;
use std::io::Read;
fn main() -> Result<(), io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(())
}?operator can only be used in a function that returnsResultorOptionmainfunction returnsResultwithOkvariant
Panic vs Result vs Expect
-
In general the default should be using the
Resultenum and error propagation, prevents the program from crashing, and error propagation allows the caller to decide how to handle the error -
panic!macro should be used when the program is in an unrecoverable state, used in example code, or when the program is in a state where it's not possible to recover -
expect!method is used in prototype code, quick tests (when the program is in a state where it's not possible to recover) -
unwrap!method is used in prototype code, when something is expected to beOkbut fails
Creating Custom Types for Validation
- Rust allows creating custom types for validation
loop {
let guess: i32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
if guess < 1 || guess > 100 {
println!("The secret number will be between 1 and 100.");
continue;
}
match guess.cmp(&secret_number) {
// --snip--
}
}- this works here, but doesn't scale well
struct Guess {
value: i32,
}
impl Guess {
fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
Guess { value }
}
fn value(&self) -> i32 {
self.value
}
}- this is a better way to handle validation