What is StdError and How exactly does ? propagate errors

So I am trying to understand how ? propagate errors and also interaction with Box<dyn Error>

So after reading the part about Error from the Rust book, I come away with the following understanding:

  1. The ? when used on a Result expression will cause early return in case the Result is an Err variant
  2. In the case when the error type of the enclosing function (say ReturnedError) is different from the error type ? is applied on, (say OriginalError), there has to be a conversion. The conversion works if there is an implementation of From<OriginalError> for ReturnedError.
  3. The main function's return type can be Result<(), Box<dyn std::error::Error>> this allows for possibility to return any std::error::Error

So to put this in practice I came up with this code

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Err(CustomError::GenericError("hahaha".to_string()))?;
    Ok(())
}

This fails to compile with following error

error[E0277]: the trait bound `CustomError: StdError` is not satisfied
  --> src/main.rs:86:57
   |
86 |     Err(CustomError::GenericError("hahaha".to_string()))?;
   |                                                         ^ the trait `StdError` is not implemented for `CustomError`
   |
   = help: the following other types implement trait `FromResidual<R>`:
             <Result<T, F> as FromResidual<Result<Infallible, E>>>
             <Result<T, F> as FromResidual<Yeet<E>>>
   = note: required for `Box<dyn StdError>` to implement `From<CustomError>`
   = note: required for `Result<(), Box<dyn StdError>>` to implement `FromResidual<Result<Infallible, CustomError>>`

I am not sure what StdError is and why it has to be implemented for CustomError.

I decided to go with what I have read and assumed that for this to work, I need an implementation of the From trait that will help convert from CustomError to Box<dyn std::error::Error>>. So I added this:

impl From<CustomError> for Box<dyn std::error::Error> {
    fn from(e: CustomError) -> Self {
        Box::new(e)
    }
}

This also fails to compile with the following error

error[E0277]: the trait bound `CustomError: StdError` is not satisfied
  --> src/main.rs:77:9
   |
77 |         Box::new(e)
   |         ^^^^^^^^^^^ the trait `StdError` is not implemented for `CustomError`
   |
   = note: required for the cast from `CustomError` to the object type `dyn StdError`

After following the prompts of my IDE, and googling I finally came up with a version that compiles, which is:

#[derive(Debug)]
enum CustomError {
    GenericError(String),
    SpecificError(String),
}

impl std::fmt::Display for CustomError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CustomError::GenericError(s) => write!(f, "{}", s),
            CustomError::SpecificError(s) => write!(f, "{}", s),
        }
    }
}

impl std::error::Error for CustomError {
    fn description(&self) -> &str {
        match self {
            CustomError::GenericError(s) => s,
            CustomError::SpecificError(s) => s,
        }
    }
}


fn main() -> Result<(), Box<dyn std::error::Error>> {
    Err(CustomError::GenericError("hahaha".to_string()))?;
    Ok(())
}

But I find this even more confusing because there is no implementation of StdError in the version that compiles and yet StdError was showing up in the error messages.

So my question are,

  1. what exactly is StdError
  2. How does it interact with ? because it seems using ? requires it somehow
  3. Why did the version that compiled did so?

Update

So came up with this version that also works

impl Display for CustomError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            CustomError::GenericError(s) => write!(f, "{}", s),
            CustomError::SpecificError(s) => write!(f, "{}", s),
        }
    }
}

impl StdError for CustomError {
    fn description(&self) -> &str {
        match self {
            CustomError::GenericError(s) => s,
            CustomError::SpecificError(s) => s,
        }
    }
}

seems StdError is some sort of alias for std::error::Error?

  1. StdError is usually a typedef (alias) for std::error::Error. It helps avoid name collisions in your own code when you define a concrete error type named Error, which is idiomatic.
  2. A trait must be implemented for a type in order to make it into a trait object. How would you expect Box<dyn Error> to be created from Box<T> if T does not implement Error?
  3. The ? operator has nothing to do with this. It doesn't require the error type to implement Error.
1 Like

Gotcha. I think this was the missing crucial part. So basically a custom error type E needs to have an implementation of the trait std::error::Error in other for it to be compatible with Box<dyn Error>

Once that is the case, then it can be returned. i.e:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Err(Box::new(CustomError::GenericError("hahaha".to_string())))
}

Then using the ? I see that the compiler can automatically Box my custom error for me. ie this works

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Err(CustomError::GenericError("hahaha".to_string()))?
}

Now not sure how that conversion of CustomError to Box<CustomError> was possible. Some generic From implementation that is responsible for automatically boxing any value?

This is not specific to Error, it's true for all traits.

Yes, you can search for it on Box at std - Rust

1 Like

Oh sure. Never said it was specific to Error

Not that helpful. That search returns, I am guessing, close to 100 results.

I assume they are referring to this implementation.

If you scroll towards the bottom of the std::boxed::Box page, you'll see the various From implementations, one of which converts some type E (where E: std::error::Error) into a Box<dyn std::error::Error>.

3 Likes

Note that you can easily derive Error using thiserror - Rust

1 Like

You can narrow that list down by looking for Error. (I typically use the browser's built-in search in these situations.)

1 Like

It's very odd that the compiler complained about StdError rather than std::error::Error. StdError is a common type alias when one imports std::error::Error together with other Error types (e.g. std::io::Error). It's also often created via renamed import (i.e. use std::error::Error as StdError;). But type aliases should not be used in error messages if they are not present in your code. At the very least I would expect to see an impl<T: StdError> From<T> for Box<dyn Error>, or something like that, but there is no StdError in the std::boxed module. Searching all of std, the only uses of StdError are in the os module.

So this is a case of broken diagnostics. It would be nice if you could give a minimal example of a crate which hits this error. Bad diagnostics are treated as a bug in Rust, you could open an issue on github for your case.

4 Likes