Skip to content

Commit ec63cad

Browse files
authored
Merge pull request #772 from tlyu/errors-rework
feature: improve error_handling exercises
2 parents 84d8305 + b7ddd09 commit ec63cad

File tree

5 files changed

+196
-155
lines changed

5 files changed

+196
-155
lines changed

exercises/error_handling/result1.rs renamed to exercises/error_handling/errors4.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// result1.rs
2-
// Make this test pass! Execute `rustlings hint result1` for hints :)
1+
// errors4.rs
2+
// Make this test pass! Execute `rustlings hint errors4` for hints :)
33

44
// I AM NOT DONE
55

exercises/error_handling/errors5.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// errors5.rs
2+
3+
// This program uses a completed version of the code from errors4.
4+
// It won't compile right now! Why?
5+
// Execute `rustlings hint errors5` for hints!
6+
7+
// I AM NOT DONE
8+
9+
use std::error;
10+
use std::fmt;
11+
use std::num::ParseIntError;
12+
13+
// TODO: update the return type of `main()` to make this compile.
14+
fn main() -> Result<(), ParseIntError> {
15+
let pretend_user_input = "42";
16+
let x: i64 = pretend_user_input.parse()?;
17+
println!("output={:?}", PositiveNonzeroInteger::new(x)?);
18+
Ok(())
19+
}
20+
21+
// Don't change anything below this line.
22+
23+
#[derive(PartialEq, Debug)]
24+
struct PositiveNonzeroInteger(u64);
25+
26+
#[derive(PartialEq, Debug)]
27+
enum CreationError {
28+
Negative,
29+
Zero,
30+
}
31+
32+
impl PositiveNonzeroInteger {
33+
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
34+
match value {
35+
x if x < 0 => Err(CreationError::Negative),
36+
x if x == 0 => Err(CreationError::Zero),
37+
x => Ok(PositiveNonzeroInteger(x as u64))
38+
}
39+
}
40+
}
41+
42+
// This is required so that `CreationError` can implement `error::Error`.
43+
impl fmt::Display for CreationError {
44+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45+
let description = match *self {
46+
CreationError::Negative => "number is negative",
47+
CreationError::Zero => "number is zero",
48+
};
49+
f.write_str(description)
50+
}
51+
}
52+
53+
impl error::Error for CreationError {}

exercises/error_handling/errors6.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// errors6.rs
2+
3+
// Using catch-all error types like `Box<dyn error::Error>` isn't recommended
4+
// for library code, where callers might want to make decisions based on the
5+
// error content, instead of printing it out or propagating it further. Here,
6+
// we define a custom error type to make it possible for callers to decide
7+
// what to do next when our function returns an error.
8+
9+
// Make these tests pass! Execute `rustlings hint errors6` for hints :)
10+
11+
// I AM NOT DONE
12+
13+
use std::num::ParseIntError;
14+
15+
// This is a custom error type that we will be using in `parse_pos_nonzero()`.
16+
#[derive(PartialEq, Debug)]
17+
enum ParsePosNonzeroError {
18+
Creation(CreationError),
19+
ParseInt(ParseIntError)
20+
}
21+
22+
impl ParsePosNonzeroError {
23+
fn from_creation(err: CreationError) -> ParsePosNonzeroError {
24+
ParsePosNonzeroError::Creation(err)
25+
}
26+
// TODO: add another error conversion function here.
27+
}
28+
29+
fn parse_pos_nonzero(s: &str)
30+
-> Result<PositiveNonzeroInteger, ParsePosNonzeroError>
31+
{
32+
// TODO: change this to return an appropriate error instead of panicking
33+
// when `parse()` returns an error.
34+
let x: i64 = s.parse().unwrap();
35+
PositiveNonzeroInteger::new(x)
36+
.map_err(ParsePosNonzeroError::from_creation)
37+
}
38+
39+
// Don't change anything below this line.
40+
41+
#[derive(PartialEq, Debug)]
42+
struct PositiveNonzeroInteger(u64);
43+
44+
#[derive(PartialEq, Debug)]
45+
enum CreationError {
46+
Negative,
47+
Zero,
48+
}
49+
50+
impl PositiveNonzeroInteger {
51+
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
52+
match value {
53+
x if x < 0 => Err(CreationError::Negative),
54+
x if x == 0 => Err(CreationError::Zero),
55+
x => Ok(PositiveNonzeroInteger(x as u64))
56+
}
57+
}
58+
}
59+
60+
#[cfg(test)]
61+
mod test {
62+
use super::*;
63+
64+
#[test]
65+
fn test_parse_error() {
66+
// We can't construct a ParseIntError, so we have to pattern match.
67+
assert!(matches!(
68+
parse_pos_nonzero("not a number"),
69+
Err(ParsePosNonzeroError::ParseInt(_))
70+
));
71+
}
72+
73+
#[test]
74+
fn test_negative() {
75+
assert_eq!(
76+
parse_pos_nonzero("-555"),
77+
Err(ParsePosNonzeroError::Creation(CreationError::Negative))
78+
);
79+
}
80+
81+
#[test]
82+
fn test_zero() {
83+
assert_eq!(
84+
parse_pos_nonzero("0"),
85+
Err(ParsePosNonzeroError::Creation(CreationError::Zero))
86+
);
87+
}
88+
89+
#[test]
90+
fn test_positive() {
91+
let x = PositiveNonzeroInteger::new(42);
92+
assert!(x.is_ok());
93+
assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap()));
94+
}
95+
}

exercises/error_handling/errorsn.rs

Lines changed: 0 additions & 117 deletions
This file was deleted.

info.toml

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -490,42 +490,61 @@ hint = """
490490
If other functions can return a `Result`, why shouldn't `main`?"""
491491

492492
[[exercises]]
493-
name = "errorsn"
494-
path = "exercises/error_handling/errorsn.rs"
493+
name = "errors4"
494+
path = "exercises/error_handling/errors4.rs"
495495
mode = "test"
496496
hint = """
497-
First hint: To figure out what type should go where the ??? is, take a look
498-
at the test helper function `test_with_str`, since it returns whatever
499-
`read_and_validate` returns and `test_with_str` has its signature fully
500-
specified.
501-
502-
503-
Next hint: There are three places in `read_and_validate` that we call a
504-
function that returns a `Result` (that is, the functions might fail).
505-
Apply the `?` operator on those calls so that we return immediately from
506-
`read_and_validate` if those function calls fail.
497+
`PositiveNonzeroInteger::new` is always creating a new instance and returning an `Ok` result.
498+
It should be doing some checking, returning an `Err` result if those checks fail, and only
499+
returning an `Ok` result if those checks determine that everything is... okay :)"""
507500

501+
[[exercises]]
502+
name = "errors5"
503+
path = "exercises/error_handling/errors5.rs"
504+
mode = "compile"
505+
hint = """
506+
Hint: There are two different possible `Result` types produced within
507+
`main()`, which are propagated using `?` operators. How do we declare a
508+
return type from `main()` that allows both?
508509
509510
Another hint: under the hood, the `?` operator calls `From::from`
510-
on the error value to convert it to a boxed trait object, a Box<dyn error::Error>,
511-
which is polymorphic-- that means that lots of different kinds of errors
512-
can be returned from the same function because all errors act the same
513-
since they all implement the `error::Error` trait.
511+
on the error value to convert it to a boxed trait object, a
512+
`Box<dyn error::Error>`, which is polymorphic-- that means that lots of
513+
different kinds of errors can be returned from the same function because
514+
all errors act the same since they all implement the `error::Error` trait.
514515
Check out this section of the book:
515516
https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
516517
518+
This exercise uses some concepts that we won't get to until later in the
519+
course, like `Box` and the `From` trait. It's not important to understand
520+
them in detail right now, but you can read ahead if you like.
521+
522+
Read more about boxing errors:
523+
https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/boxing_errors.html
524+
525+
Read more about using the `?` operator with boxed errors:
526+
https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html
527+
"""
528+
529+
[[exercises]]
530+
name = "errors6"
531+
path = "exercises/error_handling/errors6.rs"
532+
mode = "test"
533+
hint = """
534+
This exercise uses a completed version of `PositiveNonzeroInteger` from
535+
errors4.
517536
518-
Another another hint: Note that because the `?` operator returns
519-
the *unwrapped* value in the `Ok` case, if we want to return a `Result` from
520-
`read_and_validate` for *its* success case, we'll have to rewrap a value
521-
that we got from the return value of a `?`ed call in an `Ok`-- this will
522-
look like `Ok(something)`.
537+
Below the line that TODO asks you to change, there is an example of using
538+
the `map_err()` method on a `Result` to transform one type of error into
539+
another. Try using something similar on the `Result` from `parse()`. You
540+
might use the `?` operator to return early from the function, or you might
541+
use a `match` expression, or maybe there's another way!
523542
543+
You can create another function inside `impl ParsePosNonzeroError` to use
544+
with `map_err()`.
524545
525-
Another another another hint: `Result`s must be "used", that is, you'll
526-
get a warning if you don't handle a `Result` that you get in your
527-
function. Read more about that in the `std::result` module docs:
528-
https://doc.rust-lang.org/std/result/#results-must-be-used"""
546+
Read more about `map_err()` in the `std::result` documentation:
547+
https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err"""
529548

530549
# Generics
531550

@@ -561,7 +580,7 @@ ReportCard struct generic, but also the correct property - you will need to chan
561580
of the struct slightly too...you can do it!
562581
"""
563582

564-
# OPTIONS / RESULTS
583+
# OPTIONS
565584

566585
[[exercises]]
567586
name = "option1"
@@ -603,15 +622,6 @@ statement. How can this be avoided? The compiler shows the correction
603622
needed. After making the correction as suggested by the compiler, do
604623
read: https://doc.rust-lang.org/std/keyword.ref.html"""
605624

606-
[[exercises]]
607-
name = "result1"
608-
path = "exercises/error_handling/result1.rs"
609-
mode = "test"
610-
hint = """
611-
`PositiveNonzeroInteger::new` is always creating a new instance and returning an `Ok` result.
612-
It should be doing some checking, returning an `Err` result if those checks fail, and only
613-
returning an `Ok` result if those checks determine that everything is... okay :)"""
614-
615625
# TRAITS
616626

617627
[[exercises]]
@@ -920,7 +930,7 @@ hint = """
920930
Follow the steps provided right before the `TryFrom` implementation.
921931
You can also use the example at https://doc.rust-lang.org/std/convert/trait.TryFrom.html
922932
923-
You might want to look back at the exercise errorsn (or its hints) to remind
933+
You might want to look back at the exercise errors5 (or its hints) to remind
924934
yourself about how `Box<dyn Error>` works.
925935
926936
If you're trying to return a string as an error, note that neither `str`

0 commit comments

Comments
 (0)