Learn
Best Practices
Never Throw

Prefer Result Type over try-catch

Context

In various programming languages, managing errors through exceptions, try-catch, or throws can obfuscate a function's behavior, making the code less predictable and harder to comprehend, as it's unclear whether, or what kind of, an exception will be thrown.

Opinion

Given the context, we prefer using the Result type over try-catch or throws for error handling. Result types clearly indicate a function's outcome and encapsulate the value or error, enhancing code predictability and readability.

How to Implement

use thiserror::Error;
 
// Example of a function that never throws an error
fn get_user() -> Result<(), UserError> {
    Err(UserError::NotFound)
}
 
#[derive(Error, Debug)]
pub enum UserError {
    #[error("User not found")]
    NotFound,
}
 
// Other example of a function that never throws an error
fn get_api() -> Result<(), ApiError> {
    Err(ApiError::BadResponse)
}
 
#[derive(Error, Debug)]
pub enum ApiError {
    #[error("Bad response")]
    BadResponse,
}
 
// Program
fn main() -> Result<(), ProgramError> {
    get_user().map_err(ProgramError::User)?;
    get_api().map_err(ProgramError::Api)?;
    Ok(())
}
 
// Union of errors
pub enum ProgramError {
    User(UserError),
    Api(ApiError),
}