Stupid Errors (rustlings exercise on errors)

Hello,
A question about Rustlings!

So this exercise can be solved by returning a wrap-it-all Box<(dyn Error + 'static)> but I still have questions....

  1. I understand that the trait Error was implemented for creation Error, so by pointing with dyn at Error, it encompasses the buffer error, the parsing error and the creation error. But why do we return dyn error + 'static in parenthesis** and why do we add a 'static flag to it? (yes, the compiler helped me a lot...) (I read the book chapter on errors and a gentle introduction to Rust on the same subject but it did not click).
    ** small edit here, just tested and the parenthesis are not necessary apparently!

  2. Also, I tried to make a new error type (SmileyErrors) and an encompassing enum category "StupidErrors".
    I implemented the Error trait and tried and tried to implement the fmt::Display trait for Stupid error, but the compiler complains about:

    note: expected enum std::result::Result<_, CreationError>
    found enum std::result::Result<PositiveNonzeroInteger, StupidErrors>

I obviously missed a big point about custom errors. How can I make this work?

Thank you for reading :slight_smile:

use std::error;
use std::error::Error;
use std::fmt;
use std::io;
use std::num::ParseIntError;

// PositiveNonzeroInteger is a struct defined below the tests.
fn read_and_validate(b: &mut dyn io::BufRead) -> Result<PositiveNonzeroInteger, Box<(dyn Error + 'static)>> {
    let mut line = String::new();
    b.read_line(&mut line)?;
    let num: i64 = line.trim().parse()?;
    let answer = PositiveNonzeroInteger::new(num)?;
    Ok(answer)
}

//
// Nothing below this needs to be modified
//

// This is a test helper function that turns a &str into a BufReader.
fn test_with_str(s: &str) -> Result<PositiveNonzeroInteger, Box<dyn error::Error>> {
    let mut b = io::BufReader::new(s.as_bytes());
    read_and_validate(&mut b)
}

#[test]
fn test_success() {
    let x = test_with_str("42\n");
    assert_eq!(PositiveNonzeroInteger(42), x.unwrap());
}

#[test]
fn test_not_num() {
    let x = test_with_str("eleven billion\n");
    assert!(x.is_err());
}

#[test]
fn test_non_positive() {
    let x = test_with_str("-40\n");
    assert!(x.is_err());
}

#[test]
fn test_bad_smiley() {
    let smulan = test_with_str("😃");
    assert!(smulan.is_err());
}

#[test]
fn test_ioerror() {
    struct Broken;
    impl io::Read for Broken {
        fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
            Err(io::Error::new(io::ErrorKind::BrokenPipe, "uh-oh!"))
        }
    }
    let mut b = io::BufReader::new(Broken);
    assert!(read_and_validate(&mut b).is_err());
    assert_eq!("uh-oh!", read_and_validate(&mut b).unwrap_err().to_string());
}

#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);

impl PositiveNonzeroInteger {
    fn new(value: i64) -> Result<PositiveNonzeroInteger, StupidErrors> {
        if value == 0 {
            Err(CreationError::Zero)
        } else if value < 0 {
            Err(CreationError::Negative)
        } else if value == "😃" {
            Err(SmileyError::Smiling)
        } else if value == "😔" {
            Err(SmileyError::Angry)
        } else {
            Ok(PositiveNonzeroInteger(value as u64))
        }
    }
}

#[derive(Debug)]
enum StupidErrors {
    CreationError,
    SmileyError,
}

impl error::Error for StupidErrors {}

impl fmt::Display for StupidErrors {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(&self)
    }
}


#[test]
fn test_positive_nonzero_integer_creation() {
    assert!(PositiveNonzeroInteger::new(10).is_ok());
    assert_eq!(
        Err(CreationError::Negative),
        PositiveNonzeroInteger::new(-10)
    );
    assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0));
}

#[derive(PartialEq, Debug)]
enum CreationError {
    Negative,
    Zero,
}

impl fmt::Display for CreationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let description = match *self {
            CreationError::Negative => "Number is negative",
            CreationError::Zero => "Number is zero",
        };
        f.write_str(description)
    }
}

impl error::Error for CreationError {}

#[derive(PartialEq, Debug)]
enum SmileyError {
    Smiling,
    Angry,
}

impl fmt::Display for SmileyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let information = match *self {
            SmileyError::Smiling => "This is not a number: 😃 ",
            SmileyError::Angry => "This is obviously not a number: 😔. I am disappointed.",
        };
        f.write_str(information)
    }
}

impl error::Error for SmileyError {}

You didn’t miss anything about errors, you should re-visit the topic of enums :wink:

   |
69 |             Err(CreationError::Zero)
   |                 ^^^^^^^^^^^^^^^^^^^ expected enum `StupidErrors`, found enum `CreationError`

let’s see why we’re getting this error message.

    fn new(value: i64) -> Result<PositiveNonzeroInteger, StupidErrors> {
        if value == 0 {
            Err(CreationError::Zero)
        } else ...
    }

So you want the function to return a StupidError, you write Err(CreationError::Zero) which obviously contains a CreationError, so far not too surprising the compiler might be unhappy with this, why should a CreationError be a valid StupidError?

enum StupidErrors {
    CreationError,
    SmileyError,
}

Aha! So your intention is that a StupidError can contain a SmileyError. This code does however have nothing to do with your SmileyError type. See, it compiles on its own without any CreationError type around.

The general form of an enum is

enum EnumName {
    ...first alternative... ,
    ...second alternative... ,
    ...ect
}

Where each alternative looks like a struct definition, so for example

enum VeryDiverse {
    FirstAlternativeLikeATupleStruct(i32, f64),
    SecondAlternativeLikeAnOrdinaryStruct{
        someField: String,
        anotherField: (u8, u8)
    },
    ThirdAlternativeLikeAUnitStruct,
}

In your case you only have unit-struct-like alternatives in StupidErrors. They don’t contain any fields at all. Let’s fix that:

enum StupidErrors {
    CreationError(CreationError),
    SmileyError(SmileyError),
}

Now you also need to use these constructors like for example

    fn new(value: i64) -> Result<PositiveNonzeroInteger, StupidErrors> {
        if value == 0 {
            Err(StupidErrors::CreationError(CreationError::Zero))
        } else ...
    }

That’s getting a bit repetetive, also if I would have a function with type

fn some_helper_function() -> Result<(), CreationError>

I couldn’t use the ? operator on it in a context that returns Result<..., StupidErrors>.

To tell the compiler that it can convert e.g. CreationError into StupidErrors with the ? operator and also to make our own life easier with conversions, we need to implement the From trait.

impl From<CreationError> for StupidErrors {
    fn from(e: CreationError) -> Self {
        StupidErrors::CreationError(e)
    }
}

You can try to implement a similar conversion for SmileyError.

Now we can do

    fn new(value: i64) -> Result<PositiveNonzeroInteger, StupidErrors> {
        if value == 0 {
            Err(CreationError::Zero.into())
        } else ...
    }

or

    fn new(value: i64) -> Result<PositiveNonzeroInteger, StupidErrors> {
        if value == 0 {
            Err(CreationError::Zero)?
        } else ...
    }

(I feel like the second one might be bad style, but I’m no expert in Rust style)


Finally you can start fixing the handful of other type errors your program contains.


If all those From implementations feel a bit tedious, there’s help. The derive_more crate allows you to generate those as follows:

use derive_more::From;

/*
...

*/

#[derive(Eq, PartialEq, Debug, From)] // I also added `Eq` for good measure
enum StupidErrors {
    CreationError(CreationError),
    SmileyError(SmileyError),
}

(full documentation for From)

derive_more is not available in the playground. You need to add it as a dependency to your Cargo.toml if you want to use it.

3 Likes

Box<dyn Error> actually works fine. The parantheses are not actually needed here and 'static is implicit if you leave it out and means roughly that the error type does not contain any short-lived references, important since we want to propagate the error up the stack.


An additional note on your other type errors: You try to implement Display for StupidErrors. In order to print just the contained error, you need to match on the StupidErrors

impl fmt::Display for StupidErrors {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            StupidErrors::CreationError(e) => e.fmt(f),
            StupidErrors::SmileyError(e) => e.fmt(f),
        }
    }
}

Perhaps you also want to express that it’s contained in a stupid error. In this case you may do something like

impl fmt::Display for StupidErrors {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            StupidErrors::CreationError(e) => {
                write!(f, "Encountered stupid error regarding creation: {}", e)
            }
            StupidErrors::SmileyError(e) => {
                write!(f, "Encountered stupid error regarding smileys: {}", e)
            }
        }
    }
}

For both versions (with or without extra formatting), there’s also ways to abbreviate this using derive_more. see here.

1 Like

Thank you very much for this great reply :heart: :orange_heart: :yellow_heart: :green_heart:!
I fell asleep in line of duty yesterday, but let me try to implement the correct enums right now :laughing:

Edit 1:

That was a major misconception I had apparently. Thank you for clarifying.

Edit 2, or new post for simplicity.
When I wrapped my head around and implemented all your changes, I noticed that I would not get rid of one error so easily, caused by the line:

impl PositiveNonzeroInteger {
    fn new(value: i64) -> Result<PositiveNonzeroInteger, StupidError> {

The funtion is expecting an i64, so I could not send a string or a slice!

So earlier in the program we had:

let answer = PositiveNonzeroInteger::new(num)?;... Would it accepte to parse a custom made value??)

So I tried to make answer a custom value and that would take into account the different types (and leave to later a potential problem with PositiveNonzeroInteger::new(num)?;
This time I did not forget to give the enum fields like you said.

enum CustomValue<> {
    SomeInteger64(i64),
    SomeString(String),
}

impl std::str::FromStr for CustomValue {
    fn from_str(s: &str) -> std::string::String {
        s.to_string()
    }
}

and implement the conversion to use .into()

impl From<i64> for CustomValue {
    fn from(numb: i64) -> Self {
        CustomValue::SomeInteger64(numb)
    }
}
impl From<String> for CustomValue {
    fn from(s: String) -> Self {
        CustomValue::SomeString(s)
    }
}

But since I am examining the value in that control flow, I need to do the other way around, right?

impl PositiveNonzeroInteger {
    fn new(value: CustomValue) -> Result<PositiveNonzeroInteger, StupidError> {
        if value == 0 {
            // the whole stuff can be replaced by .into()
            Err(StupidError::CreationError(CreationError::Zero))
        } else if value < 0 {
            //A value-to-value conversion that consumes the input value. The opposite of From.
            Err(CreationError::Negative.into())
       ........

I see in learn Rust by example that I should use a struct and not an enum to access the original fields.
At which point I realise I am too far in the rabbit hole, and might ask if I should not break the StupidError chain reaction? Or can it be saved?

Hi,
if you'd like to access the inner value of the actual variant of the enum you need pattern matching on the value like so:

enum CustomValue {
    SomeInteger64(i64),
    SomeString(String),
}

impl PositiveNonzeroInteger {
    fn new(value: CustomValue) -> Result<PositiveNonzeroInteger, StupidError> {
        match value {
            CustomValue::SomeInteger64(integer) => {
                /* do the validation on the interger value like if interger == 0 ... */
            },
            CustomValue::SomeString(string) => {
                /* do something with the string value */
            }
        }
    }
}
1 Like

Oh great thank you :orange_heart:! I feel I learn a lot from dissecting those exercises :slight_smile:, so maybe unnecessary but that's quite cool.

Good morning Ladies and Gentlemen,

"// Edit the read_and_validate function ONLY. Don't create any Errors
// that do not already exist." ... the instructions said said,

"// So many things could go wrong!", it warned... You bet!

"// Nothing below this needs to be modified" it said... We'll see!

But OMG, it compiles :dancer:t5: :partying_face: :champagne:! Thank you for all the guidance seriously. I learned a lot by disfiguring this exercise.

I had no idea you could double match inside a match.

Last question and I promise I will do something useful: why would we want to dereference self?

impl fmt::Display for CreationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let description = match *self {
            CreationError::Negative => "Number is negative",
            CreationError::Zero => "Number is zero",
            CreationError::Horror => "What are you doing exactly?",
        };
        f.write_str(description)
    }
}
// errorsn.rs
// This is a bigger error exercise than the previous ones!
// You can do it! :)
//
// Edit the `read_and_validate` function ONLY. Don't create any Errors
// that do not already exist.
//
// So many things could go wrong!
//
// - Reading from stdin could produce an io::Error
// - Parsing the input could produce a num::ParseIntError
// - Validating the input could produce a CreationError (defined below)
//
// How can we lump these errors into one general error? That is, what
// type goes where the question marks are, and how do we return
// that type from the body of read_and_validate?
//
// Execute `rustlings hint errorsn` for hints :)

use std::error;
use std::error::Error;
use std::fmt;
use std::io;
use std::string::String;
// PositiveNonzeroInteger is a struct defined below the tests.
fn read_and_validate(b: &mut dyn io::BufRead) -> Result<PositiveNonzeroInteger, Box<dyn Error>> {
    let mut line = String::new();
    b.read_line(&mut line)?;
    let num = line.trim().parse()?;
    let answer = PositiveNonzeroInteger::new(num)?;
    Ok(answer)
}

//
// Nothing below this needs to be modified
//

// This is a test helper function that turns a &str into a BufReader.
fn test_with_str(s: &str) -> Result<PositiveNonzeroInteger, Box<dyn error::Error>> {
    let mut b = io::BufReader::new(s.as_bytes());
    read_and_validate(&mut b)
}

#[test]
fn test_success() {
    let x = test_with_str("42\n");
    assert_eq!(PositiveNonzeroInteger(42), x.unwrap());
}

#[test]
fn test_not_num() {
    let x = test_with_str("eleven billion\n");
    assert!(x.is_err());
}

#[test]
fn test_non_positive() {
    let x = test_with_str("-40\n");
    assert!(x.is_err());
}

#[test]
fn test_bad_smiley() {
    let smulan = test_with_str("😃");
    assert!(smulan.is_err());
}

#[test]
fn test_ioerror() {
    struct Broken;
    impl io::Read for Broken {
        fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
            Err(io::Error::new(io::ErrorKind::BrokenPipe, "uh-oh!"))
        }
    }
    let mut b = io::BufReader::new(Broken);
    assert!(read_and_validate(&mut b).is_err());
    assert_eq!("uh-oh!", read_and_validate(&mut b).unwrap_err().to_string());
}

#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);

impl PositiveNonzeroInteger {
    fn new(value: CustomValue) -> Result<PositiveNonzeroInteger, StupidError> {
        match value {
            CustomValue::SomeInteger64(int) => {
                if int == 0 {
                    // the whole stuff can be replaced by .into()
                    Err(CreationError::Zero.into())
                } else if int < 0 {
                    //A value-to-value conversion that consumes the input value. The opposite of From.
                    Err(StupidError::CreationError(CreationError::Negative))
                } else {
                    Ok(PositiveNonzeroInteger(int as u64))
                }
            }
            CustomValue::SomeString(string) => {
                if string == "😃" {
                    Err(StupidError::SmileyError(SmileyError::Smiley))
                } else {
                    Err(StupidError::SmileyError(SmileyError::Angry))
                }
            }
            _ => Err(StupidError::CreationError(CreationError::Negative)),
        }
    }
}


#[test]
fn test_positive_nonzero_integer_creation() {
    assert!(PositiveNonzeroInteger::new(CustomValue::SomeInteger64(10)).is_ok());
    assert_eq!(
        Err(StupidError::CreationError(CreationError::Negative.into())),
        PositiveNonzeroInteger::new(CustomValue::SomeInteger64(-10))
    );
    assert_eq!(Err(StupidError::CreationError(CreationError::Zero.into())), PositiveNonzeroInteger::new(CustomValue::SomeInteger64(0)));
}

enum CustomValue<> {
    SomeInteger64(i64),
    SomeString(String),
}

impl std::str::FromStr for CustomValue {
    type Err = CreationError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let possible_number_or_string = match s.parse::<i32>() {
            Ok(q) => CustomValue::SomeInteger64(q as i64),
            Err(_) => {
                match s.parse() {
                    Ok(string) => CustomValue::SomeString(string),
                    Err(_) => return Err(CreationError::Horror),
                }
            }
        };
        Ok(possible_number_or_string)
    }
}

impl From<i64> for CustomValue {
    fn from(numb: i64) -> Self {
        CustomValue::SomeInteger64(numb)
    }
}

impl From<String> for CustomValue {
    fn from(s: String) -> Self {
        CustomValue::SomeString(s)
    }
}


#[derive(PartialEq, Debug)]
enum StupidError {
    CreationError(CreationError),
    SmileyError(SmileyError),
}

impl fmt::Display for StupidError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            StupidError::CreationError(e) => {
                write!(f, "Encountered stupid error regarding creation: {}", e)
            }
            StupidError::SmileyError(e) => {
                write!(f, "Encountered stupid error regarding smileys: {}", e)
            }
        }
    }
}

impl error::Error for StupidError {}

#[derive(PartialEq, Debug)]
enum CreationError {
    Negative,
    Zero,
    Horror,
}

impl fmt::Display for CreationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let description = match *self {
            CreationError::Negative => "Number is negative",
            CreationError::Zero => "Number is zero",
            CreationError::Horror => "What are you doing exactly?",
        };
        f.write_str(description)
    }
}

impl error::Error for CreationError {}

impl From<CreationError> for StupidError {
    fn from(e: CreationError) -> Self {
        StupidError::CreationError(e)
    }
}

#[derive(Debug, PartialEq)]
enum SmileyError {
    Smiley,
    Angry,
}

impl fmt::Display for SmileyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let description = match *self {
            SmileyError::Angry => "Anger leads to hate",
            SmileyError::Smiley => "Did I say something funny?",
        };
        f.write_str(description)
    }
}

impl error::Error for SmileyError {}

impl From<SmileyError> for StupidError {
    fn from(e: SmileyError) -> Self {
        StupidError::SmileyError(e)
    }
}

In this case self has the type &CreationError. (Notice the &self in the method signature). And the match statement is currently matching on CreationError::* so a dereference is needed.

Alternatively if you remove the dereference, the match arms could instead match on &CreationError::*

Thanks for helping me out :slight_smile:
It does compile with or without the dereference, why do you think :face_with_monocle:?

I think in this case, its compiler helping you out and performing the dereference automatically for you. This actually works as well which is two dereferences.

match &&1 {
    1 => {}
    _ => {}
}
2 Likes

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.