Skip to content

Commit 68d3ac5

Browse files
committed
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.
1 parent 50ab289 commit 68d3ac5

File tree

4 files changed

+173
-144
lines changed

4 files changed

+173
-144
lines changed

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: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
// This is a custom error type that we will be using in `parse_pos_nonzero()`.
14+
#[derive(PartialEq, Debug)]
15+
enum ParsePosNonzeroError {
16+
CreationError,
17+
ParseIntError
18+
}
19+
20+
fn parse_pos_nonzero(s: &str)
21+
-> Result<PositiveNonzeroInteger, ParsePosNonzeroError>
22+
{
23+
// TODO: change this to return an appropriate error instead of panicking
24+
// when `parse()` returns an error.
25+
let x: i64 = s.parse().unwrap();
26+
PositiveNonzeroInteger::new(x)
27+
.or(Err(ParsePosNonzeroError::CreationError))
28+
}
29+
30+
// Don't change anything below this line.
31+
32+
#[derive(PartialEq, Debug)]
33+
struct PositiveNonzeroInteger(u64);
34+
35+
#[derive(PartialEq, Debug)]
36+
enum CreationError {
37+
Negative,
38+
Zero,
39+
}
40+
41+
impl PositiveNonzeroInteger {
42+
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
43+
match value {
44+
x if x < 0 => Err(CreationError::Negative),
45+
x if x == 0 => Err(CreationError::Zero),
46+
x => Ok(PositiveNonzeroInteger(x as u64))
47+
}
48+
}
49+
}
50+
51+
#[cfg(test)]
52+
mod test {
53+
use super::*;
54+
55+
#[test]
56+
fn test_parse_error() {
57+
assert_eq!(
58+
parse_pos_nonzero("not a number"),
59+
Err(ParsePosNonzeroError::ParseIntError)
60+
);
61+
}
62+
63+
#[test]
64+
fn test_negative() {
65+
assert_eq!(
66+
parse_pos_nonzero("-555"),
67+
Err(ParsePosNonzeroError::CreationError)
68+
);
69+
}
70+
71+
#[test]
72+
fn test_zero() {
73+
assert_eq!(
74+
parse_pos_nonzero("0"),
75+
Err(ParsePosNonzeroError::CreationError)
76+
);
77+
}
78+
79+
#[test]
80+
fn test_positive() {
81+
assert_eq!(
82+
parse_pos_nonzero("42"),
83+
Ok(PositiveNonzeroInteger(42))
84+
);
85+
}
86+
}

exercises/error_handling/errorsn.rs

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

info.toml

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -499,42 +499,49 @@ It should be doing some checking, returning an `Err` result if those checks fail
499499
returning an `Ok` result if those checks determine that everything is... okay :)"""
500500

501501
[[exercises]]
502-
name = "errorsn"
503-
path = "exercises/error_handling/errorsn.rs"
504-
mode = "test"
502+
name = "errors5"
503+
path = "exercises/error_handling/errors5.rs"
504+
mode = "compile"
505505
hint = """
506-
First hint: To figure out what type should go where the ??? is, take a look
507-
at the test helper function `test_with_str`, since it returns whatever
508-
`read_and_validate` returns and `test_with_str` has its signature fully
509-
specified.
510-
511-
512-
Next hint: There are three places in `read_and_validate` that we call a
513-
function that returns a `Result` (that is, the functions might fail).
514-
Apply the `?` operator on those calls so that we return immediately from
515-
`read_and_validate` if those function calls fail.
516-
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?
517509
518510
Another hint: under the hood, the `?` operator calls `From::from`
519-
on the error value to convert it to a boxed trait object, a Box<dyn error::Error>,
520-
which is polymorphic-- that means that lots of different kinds of errors
521-
can be returned from the same function because all errors act the same
522-
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.
523515
Check out this section of the book:
524516
https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
525517
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
526524
527-
Another another hint: Note that because the `?` operator returns
528-
the *unwrapped* value in the `Ok` case, if we want to return a `Result` from
529-
`read_and_validate` for *its* success case, we'll have to rewrap a value
530-
that we got from the return value of a `?`ed call in an `Ok`-- this will
531-
look like `Ok(something)`.
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+
the errors4.
532536
537+
Below the TODO line, there is an example of using the `.or()` method
538+
on a `Result` to transform one type of error into another. Try using
539+
something similar on the `Result` from `parse()`. You might use the `?`
540+
operator to return early from the function, or you might use a `match`
541+
expression, or maybe there's another way!
533542
534-
Another another another hint: `Result`s must be "used", that is, you'll
535-
get a warning if you don't handle a `Result` that you get in your
536-
function. Read more about that in the `std::result` module docs:
537-
https://doc.rust-lang.org/std/result/#results-must-be-used"""
543+
Read more about `.or()` in the `std::result` documentation:
544+
https://doc.rust-lang.org/std/result/enum.Result.html#method.or"""
538545

539546
# Generics
540547

0 commit comments

Comments
 (0)