Merge pull request #772 from tlyu/errors-rework
feature: improve error_handling exercises
This commit is contained in:
commit
ec63cadadb
|
@ -1,5 +1,5 @@
|
||||||
// result1.rs
|
// errors4.rs
|
||||||
// Make this test pass! Execute `rustlings hint result1` for hints :)
|
// Make this test pass! Execute `rustlings hint errors4` for hints :)
|
||||||
|
|
||||||
// I AM NOT DONE
|
// I AM NOT DONE
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
// errors5.rs
|
||||||
|
|
||||||
|
// This program uses a completed version of the code from errors4.
|
||||||
|
// It won't compile right now! Why?
|
||||||
|
// Execute `rustlings hint errors5` for hints!
|
||||||
|
|
||||||
|
// I AM NOT DONE
|
||||||
|
|
||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
|
||||||
|
// TODO: update the return type of `main()` to make this compile.
|
||||||
|
fn main() -> Result<(), ParseIntError> {
|
||||||
|
let pretend_user_input = "42";
|
||||||
|
let x: i64 = pretend_user_input.parse()?;
|
||||||
|
println!("output={:?}", PositiveNonzeroInteger::new(x)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't change anything below this line.
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
struct PositiveNonzeroInteger(u64);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum CreationError {
|
||||||
|
Negative,
|
||||||
|
Zero,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PositiveNonzeroInteger {
|
||||||
|
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
|
||||||
|
match value {
|
||||||
|
x if x < 0 => Err(CreationError::Negative),
|
||||||
|
x if x == 0 => Err(CreationError::Zero),
|
||||||
|
x => Ok(PositiveNonzeroInteger(x as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is required so that `CreationError` can implement `error::Error`.
|
||||||
|
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 {}
|
|
@ -0,0 +1,95 @@
|
||||||
|
// errors6.rs
|
||||||
|
|
||||||
|
// Using catch-all error types like `Box<dyn error::Error>` isn't recommended
|
||||||
|
// for library code, where callers might want to make decisions based on the
|
||||||
|
// error content, instead of printing it out or propagating it further. Here,
|
||||||
|
// we define a custom error type to make it possible for callers to decide
|
||||||
|
// what to do next when our function returns an error.
|
||||||
|
|
||||||
|
// Make these tests pass! Execute `rustlings hint errors6` for hints :)
|
||||||
|
|
||||||
|
// I AM NOT DONE
|
||||||
|
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
|
||||||
|
// This is a custom error type that we will be using in `parse_pos_nonzero()`.
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum ParsePosNonzeroError {
|
||||||
|
Creation(CreationError),
|
||||||
|
ParseInt(ParseIntError)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParsePosNonzeroError {
|
||||||
|
fn from_creation(err: CreationError) -> ParsePosNonzeroError {
|
||||||
|
ParsePosNonzeroError::Creation(err)
|
||||||
|
}
|
||||||
|
// TODO: add another error conversion function here.
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_pos_nonzero(s: &str)
|
||||||
|
-> Result<PositiveNonzeroInteger, ParsePosNonzeroError>
|
||||||
|
{
|
||||||
|
// TODO: change this to return an appropriate error instead of panicking
|
||||||
|
// when `parse()` returns an error.
|
||||||
|
let x: i64 = s.parse().unwrap();
|
||||||
|
PositiveNonzeroInteger::new(x)
|
||||||
|
.map_err(ParsePosNonzeroError::from_creation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't change anything below this line.
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
struct PositiveNonzeroInteger(u64);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum CreationError {
|
||||||
|
Negative,
|
||||||
|
Zero,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PositiveNonzeroInteger {
|
||||||
|
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
|
||||||
|
match value {
|
||||||
|
x if x < 0 => Err(CreationError::Negative),
|
||||||
|
x if x == 0 => Err(CreationError::Zero),
|
||||||
|
x => Ok(PositiveNonzeroInteger(x as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_error() {
|
||||||
|
// We can't construct a ParseIntError, so we have to pattern match.
|
||||||
|
assert!(matches!(
|
||||||
|
parse_pos_nonzero("not a number"),
|
||||||
|
Err(ParsePosNonzeroError::ParseInt(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_negative() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_pos_nonzero("-555"),
|
||||||
|
Err(ParsePosNonzeroError::Creation(CreationError::Negative))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zero() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_pos_nonzero("0"),
|
||||||
|
Err(ParsePosNonzeroError::Creation(CreationError::Zero))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_positive() {
|
||||||
|
let x = PositiveNonzeroInteger::new(42);
|
||||||
|
assert!(x.is_ok());
|
||||||
|
assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,117 +0,0 @@
|
||||||
// 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 :)
|
|
||||||
|
|
||||||
// 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, ???> {
|
|
||||||
let mut line = String::new();
|
|
||||||
b.read_line(&mut line);
|
|
||||||
let num: i64 = line.trim().parse();
|
|
||||||
let answer = PositiveNonzeroInteger::new(num);
|
|
||||||
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_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 {
|
|
||||||
let description = match *self {
|
|
||||||
CreationError::Negative => "Number is negative",
|
|
||||||
CreationError::Zero => "Number is zero",
|
|
||||||
};
|
|
||||||
f.write_str(description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for CreationError {}
|
|
82
info.toml
82
info.toml
|
@ -490,42 +490,61 @@ hint = """
|
||||||
If other functions can return a `Result`, why shouldn't `main`?"""
|
If other functions can return a `Result`, why shouldn't `main`?"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "errorsn"
|
name = "errors4"
|
||||||
path = "exercises/error_handling/errorsn.rs"
|
path = "exercises/error_handling/errors4.rs"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
First hint: To figure out what type should go where the ??? is, take a look
|
`PositiveNonzeroInteger::new` is always creating a new instance and returning an `Ok` result.
|
||||||
at the test helper function `test_with_str`, since it returns whatever
|
It should be doing some checking, returning an `Err` result if those checks fail, and only
|
||||||
`read_and_validate` returns and `test_with_str` has its signature fully
|
returning an `Ok` result if those checks determine that everything is... okay :)"""
|
||||||
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).
|
|
||||||
Apply the `?` operator on those calls so that we return immediately from
|
|
||||||
`read_and_validate` if those function calls fail.
|
|
||||||
|
|
||||||
|
[[exercises]]
|
||||||
|
name = "errors5"
|
||||||
|
path = "exercises/error_handling/errors5.rs"
|
||||||
|
mode = "compile"
|
||||||
|
hint = """
|
||||||
|
Hint: There are two different possible `Result` types produced within
|
||||||
|
`main()`, which are propagated using `?` operators. How do we declare a
|
||||||
|
return type from `main()` that allows both?
|
||||||
|
|
||||||
Another hint: under the hood, the `?` operator calls `From::from`
|
Another hint: under the hood, the `?` operator calls `From::from`
|
||||||
on the error value to convert it to a boxed trait object, a Box<dyn error::Error>,
|
on the error value to convert it to a boxed trait object, a
|
||||||
which is polymorphic-- that means that lots of different kinds of errors
|
`Box<dyn error::Error>`, which is polymorphic-- that means that lots of
|
||||||
can be returned from the same function because all errors act the same
|
different kinds of errors can be returned from the same function because
|
||||||
since they all implement the `error::Error` trait.
|
all errors act the same since they all implement the `error::Error` trait.
|
||||||
Check out this section of the book:
|
Check out this section of the book:
|
||||||
https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
|
https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
|
||||||
|
|
||||||
|
This exercise uses some concepts that we won't get to until later in the
|
||||||
|
course, like `Box` and the `From` trait. It's not important to understand
|
||||||
|
them in detail right now, but you can read ahead if you like.
|
||||||
|
|
||||||
Another another hint: Note that because the `?` operator returns
|
Read more about boxing errors:
|
||||||
the *unwrapped* value in the `Ok` case, if we want to return a `Result` from
|
https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/boxing_errors.html
|
||||||
`read_and_validate` for *its* success case, we'll have to rewrap a value
|
|
||||||
that we got from the return value of a `?`ed call in an `Ok`-- this will
|
|
||||||
look like `Ok(something)`.
|
|
||||||
|
|
||||||
|
Read more about using the `?` operator with boxed errors:
|
||||||
|
https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html
|
||||||
|
"""
|
||||||
|
|
||||||
Another another another hint: `Result`s must be "used", that is, you'll
|
[[exercises]]
|
||||||
get a warning if you don't handle a `Result` that you get in your
|
name = "errors6"
|
||||||
function. Read more about that in the `std::result` module docs:
|
path = "exercises/error_handling/errors6.rs"
|
||||||
https://doc.rust-lang.org/std/result/#results-must-be-used"""
|
mode = "test"
|
||||||
|
hint = """
|
||||||
|
This exercise uses a completed version of `PositiveNonzeroInteger` from
|
||||||
|
errors4.
|
||||||
|
|
||||||
|
Below the line that TODO asks you to change, there is an example of using
|
||||||
|
the `map_err()` method on a `Result` to transform one type of error into
|
||||||
|
another. Try using something similar on the `Result` from `parse()`. You
|
||||||
|
might use the `?` operator to return early from the function, or you might
|
||||||
|
use a `match` expression, or maybe there's another way!
|
||||||
|
|
||||||
|
You can create another function inside `impl ParsePosNonzeroError` to use
|
||||||
|
with `map_err()`.
|
||||||
|
|
||||||
|
Read more about `map_err()` in the `std::result` documentation:
|
||||||
|
https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err"""
|
||||||
|
|
||||||
# Generics
|
# Generics
|
||||||
|
|
||||||
|
@ -561,7 +580,7 @@ ReportCard struct generic, but also the correct property - you will need to chan
|
||||||
of the struct slightly too...you can do it!
|
of the struct slightly too...you can do it!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# OPTIONS / RESULTS
|
# OPTIONS
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "option1"
|
name = "option1"
|
||||||
|
@ -603,15 +622,6 @@ statement. How can this be avoided? The compiler shows the correction
|
||||||
needed. After making the correction as suggested by the compiler, do
|
needed. After making the correction as suggested by the compiler, do
|
||||||
read: https://doc.rust-lang.org/std/keyword.ref.html"""
|
read: https://doc.rust-lang.org/std/keyword.ref.html"""
|
||||||
|
|
||||||
[[exercises]]
|
|
||||||
name = "result1"
|
|
||||||
path = "exercises/error_handling/result1.rs"
|
|
||||||
mode = "test"
|
|
||||||
hint = """
|
|
||||||
`PositiveNonzeroInteger::new` is always creating a new instance and returning an `Ok` result.
|
|
||||||
It should be doing some checking, returning an `Err` result if those checks fail, and only
|
|
||||||
returning an `Ok` result if those checks determine that everything is... okay :)"""
|
|
||||||
|
|
||||||
# TRAITS
|
# TRAITS
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
|
@ -920,7 +930,7 @@ hint = """
|
||||||
Follow the steps provided right before the `TryFrom` implementation.
|
Follow the steps provided right before the `TryFrom` implementation.
|
||||||
You can also use the example at https://doc.rust-lang.org/std/convert/trait.TryFrom.html
|
You can also use the example at https://doc.rust-lang.org/std/convert/trait.TryFrom.html
|
||||||
|
|
||||||
You might want to look back at the exercise errorsn (or its hints) to remind
|
You might want to look back at the exercise errors5 (or its hints) to remind
|
||||||
yourself about how `Box<dyn Error>` works.
|
yourself about how `Box<dyn Error>` works.
|
||||||
|
|
||||||
If you're trying to return a string as an error, note that neither `str`
|
If you're trying to return a string as an error, note that neither `str`
|
||||||
|
|
Loading…
Reference in New Issue