Everything you need to know about Error Handling in Rust

Money Grows on Trees
3 min readJun 17, 2020

Photo by Luca Bravo on Unsplash

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.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response