DIY custom errors

I had a go reading 1, 2 and are still lost in creating own errors. I also found this example that – from my point of view – aims to handle different pre-existing errors the same way.

But I’d like to have a set of custom (new) errors that might be returned by a function. These errors relate do different details that should get readable if one wants to.

As I’m just starting and are not too acquainted to Rust in general and its trait-concept in special I tried to define an enum as the error-type-return in this try

use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
#[derive(Debug)]
enum MyError {
    ErrorA,
    ErrorB,
}
#[derive(Debug)]
struct ErrorA {
    detail_a: String,
}
impl Display for ErrorA {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{} - argh!", self.detail_a)
    }
}
#[derive(Debug)]
struct ErrorB {
    detail_b: usize,
}
impl Display for ErrorB {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{} - argh!", self.detail_b)
    }
}
type MyResult<T> = std::result::Result<T, MyError>;
fn test(n: i32) -> MyResult<String> {
    match n {
        0 => Ok("yay".to_string()),
        1 => Err(ErrorA{ detail_a : "nope".to_string() }),
        _ => Err(ErrorB{ detail_b : 42 }),
    }
}
fn main() {
    println!("Hello, world!");
    println!("0 {:?}", test(0));
    println!("1 {:?}", test(1));
    println!("2 {:?}", test(2));
}

It doesn’t even compile :-((

Is there a way to return different error-typs via some enum or do I have to take the long way around and implement my error-structs to completely comply the Error-trait and then return them via Result<String, Box<dyn Error>> ?

For practical cases, you probably want to just use thiserror for structured errors and anyhow/eyre for "better dyn Errors".

For learning though, you probably meant to do this:

enum MyError {
    ErrorA{detail_a: String},
    ErrorB{detail_b: usize},
}

...implement Display on MyError and then create MyError::ErrorA{...} instead of just ErrorA (and same for ErrorB). Or, more idiomatically, make this:

enum MyError {
    ErrorA(ErrorA),
    ErrorB(ErrorB),
}
impl From<ErrorA> for MyError { ... }
impl From<ErrorB> for MyError { ... }

...so that you can use question mark operator on function returning Result<T, ErrorA> or Result<T, ErrorB> to return Err(MyError).

1 Like

Hm, thank you.
Yes, for me it's about getting my own head around and let's see how far this will get :wink:

Not too far. You idiomatical suggestion is appealing but the enum seems rather redundant. And where are detail_a and detail_b?

As in your original code - inside ErrorA and ErrorB correspondingly.

You might be confused by the fact that there are two ErrorA's (and ErrorBs) in this code:

  • MyError::ErrorA is an enum variant, that is, a subset of the enum type.
  • ErrorA is a struct, that is, a type of its own.

Enum variant contains the struct inside it - just like ordinary struct can contain other structs; and since enum variant is namespaced (by the enum name), there are no ambiguity.

Thank you for your advise. I've enlarged it this way

use std::fmt::{Debug, Display, Formatter, Result};
use std::fs::{File, remove_file};
use std::io::Error;

enum MyError {
    ErrorA{ detail_a: String, },
    ErrorB{ detail_b: usize, },
    IoError(Error),
}
impl Display for MyError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        match self {
            MyError::ErrorA { detail_a: a } => write!(f, "{} - argh!", a),
            MyError::ErrorB { detail_b: b } => write!(f, "{} - argh!", b.to_string()),
            MyError::IoError(_ie) => write!(f, "ToDo: IoError"),
        }
    }
}
impl Debug for MyError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        match self {
            MyError::ErrorA { detail_a: a } => write!(f, "{} - argh!", a),
            MyError::ErrorB { detail_b: b } => write!(f, "{} - argh!", b.to_string()),
            MyError::IoError(_ie) => write!(f, "ToDo: IoError"),
        }
    }
}
impl From<Error> for MyError {
    fn from(error: Error) -> Self {
        MyError::IoError(error)
    }
}

type MyResult<T> = std::result::Result<T, MyError>;

fn test(n: i32) -> MyResult<String> {
    match n {
        0 => Ok("yay".to_string()),
        1 => Err(MyError::ErrorA{ detail_a : "nope".to_string() }),
        2 => {
            File::create("foo.txt")?;
        	remove_file("foo.txt")?;
            Ok("Created foo.txt".to_string())
        },
        3 => {
            File::create("/foo.txt")?;
        	remove_file("/foo.txt")?;
            Ok("Created /foo.txt".to_string())
        },
        x => Err(MyError::ErrorB{ detail_b : x as usize }),
    }
}

fn main() {
    println!("Hello, world!");
    println!();
    for x in 0 .. 6 {
        match test(x) {
            Ok(s) => println!("{}\t{}", x, s),
            Err(MyError::ErrorA { detail_a: a }) => println!("{}\tfirst error with message\t{}", x, a),
            Err(MyError::ErrorB { detail_b: b }) => println!("{}\tsecond error with message\t{}", x, b.to_string()),
            Err(MyError::IoError(ie)) => println!("{}\tIoError\t\t\t\t{}", x, ie.kind()),
        }
    }
}

and integrate this approach into my further attempts.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.