Created
October 7, 2016 14:46
-
-
Save anonymous/dc6f571e4894750f89be03bf16b3c359 to your computer and use it in GitHub Desktop.
Shared via Rust Playground
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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? | |
// | |
// Scroll down for hints :) | |
use std::error; | |
use std::fmt; | |
use std::io; | |
use std::num; | |
#[derive(Debug)] | |
enum ConversionError { | |
Io(io::Error), | |
ParseInt(num::ParseIntError), | |
CreatePositiveNonZeroInt(CreationError), | |
} | |
impl From<io::Error> for ConversionError { | |
fn from(err: io::Error) -> ConversionError { | |
ConversionError::Io(err) | |
} | |
} | |
impl From<num::ParseIntError> for ConversionError { | |
fn from(err: num::ParseIntError) -> ConversionError { | |
ConversionError::ParseInt(err) | |
} | |
} | |
impl From<CreationError> for ConversionError { | |
fn from(err: CreationError) -> ConversionError { | |
ConversionError::CreatePositiveNonZeroInt(err) | |
} | |
} | |
/* | |
impl std::convert::From<ConversionError> for Box<std::error::Error> { | |
} | |
*/ | |
impl fmt::Display for ConversionError { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
write!(f, "uh-oh!") | |
} | |
} | |
impl std::error::Error for ConversionError { | |
fn description(&self) -> &str { | |
// Both underlying errors already impl `Error`, so we defer to their | |
// implementations. | |
match *self { | |
ConversionError::Io(ref err) => err.description(), | |
ConversionError::ParseInt(ref err) => err.description(), | |
ConversionError::CreatePositiveNonZeroInt(_) => "CreatePositiveNonZeroInt", | |
} | |
} | |
fn cause(&self) -> Option<&error::Error> { | |
match *self { | |
// N.B. Both of these implicitly cast `err` from their concrete | |
// types (either `&io::Error` or `&num::ParseIntError`) | |
// to a trait object `&Error`. This works because both error types | |
// implement `Error`. | |
ConversionError::Io(ref err) => Some(err), | |
ConversionError::ParseInt(ref err) => Some(err), | |
ConversionError::CreatePositiveNonZeroInt(_) => None, | |
} | |
} | |
} | |
// PositiveNonzeroInteger is a struct defined below the tests. | |
fn read_and_validate(b: &mut io::BufRead) -> Result<PositiveNonzeroInteger, ConversionError> { | |
let mut line = String::new(); | |
try!(b.read_line(&mut line)); | |
let num: i64 = try!(line.trim().parse()); | |
let answer = try!(PositiveNonzeroInteger::new(num)); | |
Ok(answer) | |
} | |
// This is a test helper function that turns a &str into a BufReader. | |
fn test_with_str(s: &str) -> Result<PositiveNonzeroInteger, Box<error::Error>> { | |
let mut b = io::BufReader::new(s.as_bytes()); | |
read_and_validate(&mut b).map_err(|e| From::from(e)) | |
} | |
#[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, | |
} | |
impl fmt::Display for CreationError { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
f.write_str((self as &error::Error).description()) | |
} | |
} | |
impl error::Error for CreationError { | |
fn description(&self) -> &str { | |
match *self { | |
CreationError::Negative => "Negative", | |
CreationError::Zero => "Zero", | |
} | |
} | |
} | |
// First hint: To figure out what type should go where the ??? is, take a look | |
// at the test helper function `test_with_str`, since it returns whatever | |
// `read_and_validate` returns and`test_with_str` has its signature fully | |
// specified. | |
// Next hint: There are three places in `read_and_validate` that we call a | |
// function that returns a `Result` (that is, the functions might fail). | |
// Wrap those calls in a `try!` macro call so that we return immediately from | |
// `read_and_validate` if those function calls fail. | |
// Another hint: under the hood, the `try!` macro calls `From::from` | |
// on the error value to convert it to a boxed trait object, a Box<error::Error>, | |
// which is polymorphic-- that means that lots of different kinds of errors | |
// can be returned from the same function because all errors act the same | |
// since they all implement the `error::Error` trait. | |
// Check out this section of the book: | |
// https://doc.rust-lang.org/stable/book/error-handling.html#standard-library-traits-used-for-error-handling | |
// Another another hint: Note that because the `try!` macro returns | |
// the *unwrapped* value in the `Ok` case, if we want to return a `Result` from | |
// `read_and_validate` for *its* success case, we'll have to rewrap a value | |
// that we got from the return value of a `try!` call in an `Ok`-- this will | |
// look like `Ok(something)`. | |
// Another another another hint: `Result`s must be "used", that is, you'll | |
// get a warning if you don't handle a `Result` that you get in your | |
// function. Read more about that in the `std::result` module docs: | |
// https://doc.rust-lang.org/std/result/#results-must-be-used |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment