Boxing errors in result throws type mismatch

Hi Everyone,

I'm trying rustlings excercise, and I'm stuck at errorsn.rs

My code is this :slight_smile:
// errorsn.rs

// This is a bigger error exercise than the previous ones!

// You can do it! :)

//

// Edit the `read_and_validate` function so that it compiles and

// passes the tests... 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 :)

// I AM NOT DONE

use std::error;

use std::fmt;

use std::io;

// PositiveNonzeroInteger is a struct defined below the tests.

fn read_and_validate(b: &mut dyn io::BufRead) -> Result<PositiveNonzeroInteger, Box<dyn error::Error>> {

    let mut line = String::new();

    b.read_line(&mut line)?;

    let num: i64 =  line.trim().parse()?;

    let answer = PositiveNonzeroInteger::new(num);

    answer

}

// 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_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, CreationError> {

        if value == 0 {

            Err(CreationError::Zero)

        } else if value < 0 {

            Err(CreationError::Negative)

        } else {

            Ok(PositiveNonzeroInteger(value as u64))

        }

    }

}

#[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,

}

#[warn(deprecated)]

impl fmt::Display for CreationError {

    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

        f.write_str((self as &dyn error::Error).description())

    }

}

impl error::Error for CreationError {

    fn description(&self) -> &str {

        match *self {

            CreationError::Negative => "Negative",

            CreationError::Zero => "Zero",

        }

    }

}

And this is the error I'm getting :slight_smile:

! Compiling of exercises/error_handling/errorsn.rs failed! Please try again. Here's the output:
error[E0308]: mismatched types
  --> exercises/error_handling/errorsn.rs:29:5
   |
24 | fn read_and_validate(b: &mut dyn io::BufRead) -> Result<PositiveNonzeroInteger, Box<dyn error::Error>> {
   |                                                  ----------------------------------------------------- expected `std::result::Result<PositiveNonzeroInteger, std::boxed::Box<(dyn std::error::Error + 'static)>>` because of return type
...
29 |     answer
   |     ^^^^^^ expected struct `std::boxed::Box`, found enum `CreationError`
   |
   = note: expected type `std::result::Result<_, std::boxed::Box<(dyn std::error::Error + 'static)>>`
              found type `std::result::Result<_, CreationError>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

I've checked boxing errors and It seem I'm doing it wrong.
Can anyone help me understand this?

The question mark operator handles boxing (via the From conversion trait) and returning the errors for you for read_line and trim - however, you're not converting the (potential) error from PositiveNonzeroInteger::new. There's a few ways you could do this - either manually convert it:

let answer = PositiveNonzeroInteger::new(num);

// The cast is needed, otherwilse you'll get Box<CreationError>
answer.map_err(|e| Box::new(e) as Box<dyn error::Error>)

Or use conversion traits:

let answer = PositiveNonzeroInteger::new(num);

// The Into trait also uses From to convert types in most cases
answer.map_err(|e| e.into())

Or use the question mark operator again:

let answer = PositiveNonzeroInteger::new(num)?;

// answer is now a PositiveNonzeroInteger, not a Result
Ok(answer)

First of all, the CreationError and the Box<dyn std::error::Error + 'static> are different types, so as the Result<T, CreationError> and Result<T, Box<dyn std::error::Error + 'static>. That's why you can't return the former from the function that returns latter.

The Rust hardly converts types implicitly, and never allocates implicitly. The compiler never silently inserts heap-allocation code on your back as it can be costly for system programming languages. But how does the upper failable operations like b.read_line(&mut line)?; returns boxed error? Because, the ? operator do the conversion. Basically expr? will be expanded to code like below:

match expr {
    Ok(ok) => ok,
    Err(err) => return Err(err.into()),
}

The actual semantics is generalized over trait so you can ? on options, but the core concepts remains. As you can see, there's .into() conversion on error, and the boxed dyn error has impl for it.

1 Like