Errors in Rust

In Rust, error handling is typically done using the Result type, which represents either success (Ok) or failure (Err). This approach is similar to Go’s explicit error return, but with added type safety and pattern matching capabilities.

use std::fmt;
use std::error::Error;

// By convention, we define our own error types to represent specific error conditions
#[derive(Debug)]
struct TeaError {
    message: String,
}

impl fmt::Display for TeaError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl Error for TeaError {}

// We define our error constants as static strings
static ERR_OUT_OF_TEA: &str = "no more tea available";
static ERR_POWER: &str = "can't boil water";

fn f(arg: i32) -> Result<i32, Box<dyn Error>> {
    if arg == 42 {
        // We use the ? operator to return errors early
        Err(TeaError { message: "can't work with 42".to_string() }.into())
    } else {
        Ok(arg + 3)
    }
}

fn make_tea(arg: i32) -> Result<(), Box<dyn Error>> {
    match arg {
        2 => Err(TeaError { message: ERR_OUT_OF_TEA.to_string() }.into()),
        4 => {
            // We can wrap errors to add context
            Err(TeaError { message: format!("making tea: {}", ERR_POWER) }.into())
        },
        _ => Ok(()),
    }
}

fn main() {
    for i in &[7, 42] {
        // We use pattern matching to handle the Result
        match f(*i) {
            Ok(r) => println!("f worked: {}", r),
            Err(e) => println!("f failed: {}", e),
        }
    }

    for i in 0..5 {
        match make_tea(i) {
            Ok(_) => println!("Tea is ready!"),
            Err(e) => {
                // We can match on the error message to identify specific errors
                match e.to_string().as_str() {
                    ERR_OUT_OF_TEA => println!("We should buy new tea!"),
                    s if s.contains(ERR_POWER) => println!("Now it is dark."),
                    _ => println!("unknown error: {}", e),
                }
            }
        }
    }
}

In this Rust code:

  1. We define a custom TeaError type that implements the Error trait.

  2. Instead of using sentinel errors, we use static strings to represent specific error messages.

  3. Functions that can fail return a Result type, which is either Ok with a value or Err with an error.

  4. We use Box<dyn Error> to allow returning different error types from our functions.

  5. The ? operator is used for early returns of errors, similar to how Go often checks for errors immediately.

  6. In main, we use pattern matching (match) to handle the Result types returned by our functions.

  7. To check for specific errors, we match on the error message string. This is less type-safe than Go’s errors.Is, but it’s a common pattern in Rust when dealing with error messages.

This Rust code demonstrates error handling patterns that are idiomatic to Rust while maintaining a similar structure to the original Go code.