Beginner question: Converting enums to str

I'm trying to convert an enum to &str for error handling, but somehow the compiler doesn't like it.

Here is my code:

/// VerificationErrorReason is an enum used to denote the type
/// of verification error

pub enum VerificationErrorReason{
    InvalidProofOfWork,
    InvalidIssuerSignature,
    InvalidContentHash
}

impl From<VerificationErrorReason> for &'static str{
    fn from(reason: VerificationErrorReason) -> Self{
        match reason {
            InvalidProofOfWork => "Proof of work was not valid",
            InvalidIssuerSignature => "Block header signature doesn't match issuer",
            InvalidContentHash => "Block header content hash doesn't match transaction merkle tree root",
        }
    }
}

#[derive(Debug)]
pub struct VerificationError{
    reason: VerificationErrorReason
}

impl VerificationError{
    pub fn new(reason: VerificationErrorReason) -> VerificationError{
        VerificationError{reason: reason}
    }
}


impl Error for VerificationError{
    fn description(&self) -> &str{
        let reason: &str = self.reason as &str;
        format!("Block rejected. Reason: {}", reason)
    }
}

The compiler error points to the reason to str conversion, but I am not sure what to make of the error message:

error[E0308]: mismatched types
  --> /home/ggwilde/dev/rust/stachanov/src/blockchain/errors.rs:79:9
   |
79 |         format!("Block rejected. Reason: {}", reason)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected &str, found struct `std::string::String`
   |
   = note: expected type `&str`
   = note:    found type `std::string::String`
   = note: this error originates in a macro outside of the current crate

error: non-scalar cast: `blockchain::errors::VerificationErrorReason` as `&str`
  --> /home/ggwilde/dev/rust/stachanov/src/blockchain/errors.rs:78:28
   |
78 |         let reason: &str = self.reason as &str;
   |                            ^^^^^^^^^^^^^^^^^^^

I am not sure what I am doing wrong. Is there a canonical way to implement something like this?

Thanks in advance

Error::description returns &str and takes &self as input - this means the lifetime of the returned string is associated with self. In practical terms, this means you cannot create a new String temporary inside description and return that - that's what you're essentially trying to do here (format! returns a new String value).

If you want to provide more information or create dynamic strings, you would do that inside Display::fmt implementation for your enum (Display is required to be implemented by anything that implements Error).

Does that help?

3 Likes

ah, ok - that solves one of the errors. However when I implement the Display trait like this

impl fmt::Display for VerificationError{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let reason: &str = self.reason as &str;
        write!(f, "Block rejected. Reason: {}", reason as &str)
    }
}

I still get the error:

 error: non-scalar cast: `blockchain::errors::VerificationErrorReason` as `&str`
  --> /home/ggwilde/dev/rust/stachanov/src/blockchain/errors.rs:85:28
   |
85 |         let reason: &str = self.reason as &str;
   |                            ^^^^^^^^^^^^^^^^^^^
1 Like

Right, you can't cast like that in Rust. Instead, you should do the following:

// Delete your From<VerificationErrorReason> for &'static str

// implement Display for VER - this allows you to use it in "{}" formats.
impl Display for VerificationErrorReason {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            VerificationErrorReason::InvalidProofOfWork => write!(f, "Proof of work was not valid"),
            VerificationErrorReason::InvalidIssuerSignature => write!(f, "Block header signature doesn't match issuer"),
            VerificationErrorReason::InvalidContentHash => write!(f, "Block header content hash doesn't match transaction merkle tree root")
        }
    }
}

impl Display for VerificationError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        // As mentioned, formatting with {} requires the type impl Display, which you did above.
        write!(f, "Block rejected. Reason: {}", self.reason)
    }
}

Feel free to ask more clarifying questions.

3 Likes

Thanks a lot :slight_smile:

Apparently I understood the From trait completely wrong. I thought it was used to tell the compiler how to cast between different types, similar to the magic cast done by the Deref trait.

Yeah, there're very few casts that are allowed. The other stuff is (explicit) conversions. Deref allows coercion (hence the name: deref coercions), but that's not a cast either :slight_smile:.

1 Like

Can I recomment the error-chain library. It helps encourage good practice.

3 Likes

+1. Only thing I'd suggest is understanding how one would do this manually (and why), after which punting the boilerplate to error-chain is well worth it.

1 Like

Yes I agree - error-chain basically automates what you should know how to do by hand.

1 Like

This is a bit off-topic so feel free not to read it :slight_smile:

Some have argued that "as" (e.g. 16u8 as u64) should be deprecated (or moved to unsafe), and the traits From and TryFrom (not stable yet) be used to convert between integer types, with an associated error when conversions may fail. (I can't remember where so this is unreferenced, sorry about that). They have too much use in the ecosystem to do this, but casting can generally be avoided.

I think narrowing casts make sense to go (optionally) via TryFrom, but not widening.

1 Like

For those coming here from the future: error handling in rust has moved on. Some current crates for error handling:

  • anyhow for an error trait object with ergonomics
  • thiserror or others for generating error boilerplate