summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaylor Yu <tlyu@mit.edu>2021-06-06 23:05:01 -0500
committerTaylor Yu <tlyu@mit.edu>2021-06-06 23:08:57 -0500
commit68d3ac567cd5c23f5593c2f4df51612bca3d09a9 (patch)
tree75619fb6489e1cb7abbde7d35e29ba0b3ce055d2
parent50ab289da6b9eb19a7486c341b00048c516b88c0 (diff)
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.
-rw-r--r--exercises/error_handling/errors5.rs53
-rw-r--r--exercises/error_handling/errors6.rs86
-rw-r--r--exercises/error_handling/errorsn.rs117
-rw-r--r--info.toml61
4 files changed, 173 insertions, 144 deletions
diff --git a/exercises/error_handling/errors5.rs b/exercises/error_handling/errors5.rs
new file mode 100644
index 0000000..1b11800
--- /dev/null
+++ b/exercises/error_handling/errors5.rs
@@ -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 {}
diff --git a/exercises/error_handling/errors6.rs b/exercises/error_handling/errors6.rs
new file mode 100644
index 0000000..cee7250
--- /dev/null
+++ b/exercises/error_handling/errors6.rs
@@ -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))
+ );
+ }
+}
diff --git a/exercises/error_handling/errorsn.rs b/exercises/error_handling/errorsn.rs
deleted file mode 100644
index 5fe212b..0000000
--- a/exercises/error_handling/errorsn.rs
+++ /dev/null
@@ -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 {}
diff --git a/info.toml b/info.toml
index 55875d7..63eb78b 100644
--- a/info.toml
+++ b/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 :)"""
[[exercises]]
-name = "errorsn"
-path = "exercises/error_handling/errorsn.rs"
-mode = "test"
+name = "errors5"
+path = "exercises/error_handling/errors5.rs"
+mode = "compile"
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.
-
+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.
+
+Read more about boxing errors:
+https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/boxing_errors.html
-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 using the `?` operator with boxed errors:
+https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html
+"""
+
+[[exercises]]
+name = "errors6"
+path = "exercises/error_handling/errors6.rs"
+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!
-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"""
+Read more about `.or()` in the `std::result` documentation:
+https://doc.rust-lang.org/std/result/enum.Result.html#method.or"""
# Generics