mirror of
https://github.com/notohh/rustlings.git
synced 2024-11-25 14:57:32 -05:00
Merge pull request #772 from tlyu/errors-rework
feature: improve error_handling exercises
This commit is contained in:
commit
ec63cadadb
5 changed files with 196 additions and 155 deletions
|
@ -1,5 +1,5 @@
|
|||
// result1.rs
|
||||
// Make this test pass! Execute `rustlings hint result1` for hints :)
|
||||
// errors4.rs
|
||||
// Make this test pass! Execute `rustlings hint errors4` for hints :)
|
||||
|
||||
// I AM NOT DONE
|
||||
|
53
exercises/error_handling/errors5.rs
Normal file
53
exercises/error_handling/errors5.rs
Normal file
|
@ -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 {}
|
95
exercises/error_handling/errors6.rs
Normal file
95
exercises/error_handling/errors6.rs
Normal file
|
@ -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`?"""
|
||||
|
||||
[[exercises]]
|
||||
name = "errorsn"
|
||||
path = "exercises/error_handling/errorsn.rs"
|
||||
name = "errors4"
|
||||
path = "exercises/error_handling/errors4.rs"
|
||||
mode = "test"
|
||||
hint = """
|
||||
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).
|
||||
Apply the `?` operator on those calls so that we return immediately from
|
||||
`read_and_validate` if those function calls fail.
|
||||
`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 :)"""
|
||||
|
||||
[[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`
|
||||
on the error value to convert it to a boxed trait object, a Box<dyn 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.
|
||||
on the error value to convert it to a boxed trait object, a
|
||||
`Box<dyn 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/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
|
||||
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 `?`ed call in an `Ok`-- this will
|
||||
look like `Ok(something)`.
|
||||
Read more about boxing errors:
|
||||
https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/boxing_errors.html
|
||||
|
||||
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
|
||||
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"""
|
||||
[[exercises]]
|
||||
name = "errors6"
|
||||
path = "exercises/error_handling/errors6.rs"
|
||||
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
|
||||
|
||||
|
@ -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!
|
||||
"""
|
||||
|
||||
# OPTIONS / RESULTS
|
||||
# OPTIONS
|
||||
|
||||
[[exercises]]
|
||||
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
|
||||
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
|
||||
|
||||
[[exercises]]
|
||||
|
@ -920,7 +930,7 @@ hint = """
|
|||
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 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.
|
||||
|
||||
If you're trying to return a string as an error, note that neither `str`
|
||||
|
|
Loading…
Reference in a new issue