Everything you need to know about Error Handling in Rust
Unrecoverable Errors
panic keyword (causes the program to terminate)
fn main() {
panic!("Terminate!");
println!("Hello world!");
}
For the above code snippet, we get the following output:
Finished dev [unoptimized + debuginfo] target(s) in 1.30s
Running `target/debug/playground`
thread 'main' panicked at 'Terminate', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Note: “Hello world!” is not printed since everything beneath the panic is unreachable
By default, before panic occurs, the program begins unwinding (basically it goes back up the stack cleaning up memory)
Recoverable Errors
Below are 2 enum types that are helpful in error handling (Result & Option)
enum Result<T, E> {
Ok(T),
Err(E)
}
enum Option<T> {
Some(T),
None
}
Let’s do some error handling!
fn main() { let result = File::open("myfile.txt"); let result = match result {
Ok(file) => file,
Err(e) => {
panic!("could not open file: {:?}", e)
}
}}
Well, we can also replace the match block with unwrap since both Result & Option implement the method
fn main() { // same behavior as above code
let result = File::open("newFile.txt").unwrap();}
The function header is unwrap(self) -> T in both cases and if self is None then unwrap panics
There is also expect function that is like unwrap but in the case of panic it uses the provided message
fn main() { // same behavior as above code but allows us to provide a custom panic message
let result = File::open("newFile.txt").expect("newFile does not exist");}
Note: (don’t use unwrap or expect if you want robust error handling and error propagation)
But what if I want to handle or propagate errors?
We can do error handling using pattern matching
fn open_file(file_name: String) -> Result<String, Error> {
let result = File::open(file_name);
let mut file = match result {
Ok(file) => file,
Err(e) => return Err(e)
};
let mut string = String::new();
match file.read_to_string(&mut string) {
Ok(_) => Ok(string),
Err(e) => Err(e)
}
}fn main() {
let file_name = String::from("hello_world.txt");
match open_file(file_name) {
Ok(data) => { println!("{}", data); }
Err(e) => { println!("Error opening file: {}", e); }
}
}
but it gets too verbose and maybe something more concise would be good
? operator to the rescue
? can only be placed after a Result value and it works similar to the match expression in the open_file function in that if the value of the Result is an Ok then the Ok will be returned from the expression. However, if the value of the Result is an Err, the error will be returned from the whole function and propagated to the calling code
fn open_file(file_name: String) -> Result<String, Error> {
let mut string = String::new();
File::open(file_name)?.read_to_string(&mut string)?;
Ok(string)
}fn main() {
let file_name = String::from("hello_world.txt")?;
}
In the case of when Err is returned, ? operator converts the received error to the error type defined in the return type of the function. ? does this by looking for the from method in the received error type that converts itself to the returned error type.