Improve your Error Handling in Rust

Improve your Error Handling in Rust

Rust has very cool functionalities you can implement to improve your code and your Error Handling.

Table of contents:

  1. Rust shortcut for propagating errors

  2. Use a custom ErrorKind

  3. Alias is cool for 'Result'

Rust shortcut for propagating errors

This functionality is so basic in Rust Error Handling that everyone should know it. I included it in this post if there are readers who are learning Rust.

'?' Rust shortcut

'?' is a Rust shortcut for propagating errors, you can place it just after a 'Result' value. If the 'Result' value is 'Ok' the program will continue and you will get access to the value. But if the 'Result' value is an Error, the 'Err' value will be automatically returned and the value will be propagated to the calling function as a basic 'return'.

use std::num::ParseIntError;

// Convert a string to an u64
fn my_string_to_nb(string: &str) -> Result<u64, ParseIntError>{
    let my_int = string.parse::<u64>()?; // I use here the shortcut
    Ok(my_int)
}

fn main() {
    // 'Result<Ok>' example
    let str_test = "25"; // Can be convert to u64
    let res = my_string_to_nb(str_test); // I catch here the result
    if let Ok(_e) = res {
        println!("{}", _e); // Output: "25"
    }

    //'Result<Err>' example
    let str_test = "Hello World!"; // Can not be convert to u64
    let res = my_string_to_nb(str_test); // I catch here the result
    if let Err(_e) = res {
        println!("{}", _e); // Output: "invalid digit found in string"
    }
}

As you see, Rust did a good job, and this shortcut is very useful. But it is more if you want to propagate an error to the first calling function for example.

// Return an error to main
fn this_return_an_error() -> Result<(), String> {
    Err("This is an error!".to_string()) // Return an error
}

fn again_an_error() -> Result<(), String> {
    let _ = this_return_an_error()?; // I use the shortcut
    Ok(())
}

fn you_guess_what_it_does() -> Result<(), String> {
    let _ = again_an_error()?; // I use the shortcut
    Ok(())
}

fn main() {
    let res = you_guess_what_it_does(); // And I catch the result
    if let Err(_e) = res {
        println!("{}", _e); // Output: "This is an error!"
    }
    // Whoa this is cool ;)
}

With this shortcut, you can do a more readable Error Handling!

Use a custom ErrorKind

Depending on your project and what you want to do, it can be important to have custom Errors. I use an enum to return my custom ErrorKind. If you want a description of your error, you can give a value to your type, for example, an int or a string.

#[derive(Debug)]
enum ErrorKind {
    FirstError(), // No value
    SecondError(String), // Give an error description
    ThirdError(u8), // Give an error number
}

fn this_return_errors(nb: u32) -> Result<(), ErrorKind> {
    match nb {
        1 => Err(ErrorKind::FirstError()),
        2 => Err(ErrorKind::SecondError("Error description".to_string())),
        _ => Err(ErrorKind::ThirdError(5)),
    }
}

fn main() {
    let nb = 1; // You can change this value
    let res = this_return_errors(nb);
    if let Err(_e) = res {
        println!("{:?}", _e); // Output: "FirstError"
    }
}

For beginners, using custom Errors for your project is very important for more readability. You will easily see where the error comes from and a reader will understand your code. Use precise Error names!

Alias is cool for 'Result'

Now for each function, if you have a custom ErrorKind or not, you know that you will return something like that => 'Result<(), ErrorKind>'. But if you always return the same thing like 'Err(ErrorKind)' you don't need to specify it. Let me explain, you can create a 'MyResult' type which is an alias of 'Result<T, ErrorKind>' where you don't need to specify the 'Err' type.

#[derive(Debug)]
enum ErrorKind {
    SomeError(),
}

// Create an alias for Result
type MyResult<T> = Result<T, ErrorKind>;

fn this_return_an_error(b: bool) -> MyResult<String> { // Wow a custom Type
    match b {
        true => Ok("Hello World!".to_string()),
        false => Err(ErrorKind::SomeError()),
    }
}

fn main() {
    let res = this_return_an_error(false);
    println!("{:?}", res); // Output: "Err(SomeError)"
}

This is a cool functionality, it's useful if you declare over and over the same result in a file. It's more for cosmetics but I like to use it in my code.

Conclusion

Error Handling is very important so with these tips, you can truly improve your code whether you are a beginner or not.

  • '?' as Rust shortcut

  • Custom ErrorKind

  • Alias for 'Result'

Thank you for reading this post and good programming session to you!

Did you find this article valuable?

Support Deniz Uzuntok's Blog by becoming a sponsor. Any amount is appreciated!