mirror of
https://github.com/notohh/rustlings.git
synced 2024-11-26 07:14:17 -05:00
feature: improve error_handling exercises
Add new exercises errors5 and errors6, to introduce boxed errors and custom error enums more gently. Delete errorsn, because it tried to do too much too soon.
This commit is contained in:
parent
50ab289da6
commit
68d3ac567c
4 changed files with 173 additions and 144 deletions
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 {}
|
86
exercises/error_handling/errors6.rs
Normal file
86
exercises/error_handling/errors6.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// This is a custom error type that we will be using in `parse_pos_nonzero()`.
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum ParsePosNonzeroError {
|
||||||
|
CreationError,
|
||||||
|
ParseIntError
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
.or(Err(ParsePosNonzeroError::CreationError))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_pos_nonzero("not a number"),
|
||||||
|
Err(ParsePosNonzeroError::ParseIntError)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_negative() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_pos_nonzero("-555"),
|
||||||
|
Err(ParsePosNonzeroError::CreationError)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zero() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_pos_nonzero("0"),
|
||||||
|
Err(ParsePosNonzeroError::CreationError)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_positive() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_pos_nonzero("42"),
|
||||||
|
Ok(PositiveNonzeroInteger(42))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {}
|
|
61
info.toml
61
info.toml
|
@ -499,42 +499,49 @@ It should be doing some checking, returning an `Err` result if those checks fail
|
||||||
returning an `Ok` result if those checks determine that everything is... okay :)"""
|
returning an `Ok` result if those checks determine that everything is... okay :)"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "errorsn"
|
name = "errors5"
|
||||||
path = "exercises/error_handling/errorsn.rs"
|
path = "exercises/error_handling/errors5.rs"
|
||||||
mode = "test"
|
mode = "compile"
|
||||||
hint = """
|
hint = """
|
||||||
First hint: To figure out what type should go where the ??? is, take a look
|
Hint: There are two different possible `Result` types produced within
|
||||||
at the test helper function `test_with_str`, since it returns whatever
|
`main()`, which are propagated using `?` operators. How do we declare a
|
||||||
`read_and_validate` returns and `test_with_str` has its signature fully
|
return type from `main()` that allows both?
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
the errors4.
|
||||||
|
|
||||||
|
Below the TODO line, there is an example of using the `.or()` 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!
|
||||||
|
|
||||||
|
Read more about `.or()` in the `std::result` documentation:
|
||||||
|
https://doc.rust-lang.org/std/result/enum.Result.html#method.or"""
|
||||||
|
|
||||||
# Generics
|
# Generics
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue