result and ?

Result<T> is an enum to describe possible errors. It has two variants:

  • Ok(T): A value T was found
  • Err(e): An error was found with a value e

In short words, the expected outcome is Ok, while the unexpected outcome is Err.

  1. 🌟🌟
2. // FILL in the blanks and FIX the errors
3. use std::num::ParseIntError;

5. fn multiply(n1_str: &str, n2_str: &str) -> __ {
6. let n1 = n1_str.parse::<i32>();
7. let n2 = n2_str.parse::<i32>();
8. Ok(n1.unwrap() * n2.unwrap())
9. }

11. fn main() {
12. let result = multiply("10", "2");
13. assert_eq!(result, __);

15. let result = multiply("t", "2");
16. assert_eq!(result.__, 8);

18. println!("Success!");
19. }

?

? is almost exactly equivalent to unwrap, but ? returns instead of panic on Err.

  1. 🌟🌟
2. use std::num::ParseIntError;

4. // IMPLEMENT multiply with ?
5. // DON'T use unwrap here
6. fn multiply(n1_str: &str, n2_str: &str) -> __ {
7. }

9. fn main() {
10. assert_eq!(multiply("3", "4").unwrap(), 12);
11. println!("Success!");
12. }
  1. 🌟🌟
2. use std::fs::File;
3. use std::io::{self, Read};

5. fn read_file1() -> Result<String, io::Error> {
6. let f = File::open("hello.txt");
7. let mut f = match f {
8. Ok(file) => file,
9. Err(e) => return Err(e),
10. };

12. let mut s = String::new();
13. match f.read_to_string(&mut s) {
14. Ok(_) => Ok(s),
15. Err(e) => Err(e),
16. }
17. }

19. // FILL in the blanks with one code line
20. // DON'T change any code lines
21. fn read_file2() -> Result<String, io::Error> {
22. let mut s = String::new();

24. __;

26. Ok(s)
27. }

29. fn main() {
30. assert_eq!(read_file1().unwrap_err().to_string(), read_file2().unwrap_err().to_string());
31. println!("Success!");
32. }

map & and_then

map and and_then are two common combinators for Result<T, E> (also for Option<T>).

  1. 🌟🌟
1. use std::num::ParseIntError;

3. // FILL in the blank in two ways: map, and then
4. fn add_two(n_str: &str) -> Result<i32, ParseIntError> {
5. n_str.parse::<i32>().__
6. }

8. fn main() {
9. assert_eq!(add_two("4").unwrap(), 6);

11. println!("Success!");
12. }
  1. 🌟🌟🌟
1. use std::num::ParseIntError;

3. // With the return type rewritten, we use pattern matching without `unwrap()`.
4. // But it's so Verbose...
5. fn multiply(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {
6. match n1_str.parse::<i32>() {
7. Ok(n1)  => {
8. match n2_str.parse::<i32>() {
9. Ok(n2)  => {
10. Ok(n1 * n2)
11. },
12. Err(e) => Err(e),
13. }
14. },
15. Err(e) => Err(e),
16. }
17. }

19. // Rewriting `multiply` to make it succinct
20. // You should use BOTH of  `and_then` and `map` here.
21. fn multiply1(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {
22. // IMPLEMENT...
23. }

25. fn print(result: Result<i32, ParseIntError>) {
26. match result {
27. Ok(n)  => println!("n is {}", n),
28. Err(e) => println!("Error: {}", e),
29. }
30. }

32. fn main() {
33. // This still presents a reasonable answer.
34. let twenty = multiply1("10", "2");
35. print(twenty);

37. // The following now provides a much more helpful error message.
38. let tt = multiply("t", "2");
39. print(tt);

41. println!("Success!");
42. }

Type alias

Using std::result::Result<T, ParseIntError> everywhere is verbose and tedious, we can use alias for this purpose.

At a module level, creating aliases can be particularly helpful. Errors found in a specific module often has the same Err type, so a single alias can succinctly defined all associated Results. This is so useful even the std library supplies one: io::Result.

  1. 🌟
1. use std::num::ParseIntError;

3. // FILL in the blank
4. type __;

6. // Use the above alias to refer to our specific `Result` type.
7. fn multiply(first_number_str: &str, second_number_str: &str) -> Res<i32> {
8. first_number_str.parse::<i32>().and_then(|first_number| {
9. second_number_str.parse::<i32>().map(|second_number| first_number * second_number)
10. })
11. }

13. // Here, the alias again allows us to save some space.
14. fn print(result: Res<i32>) {
15. match result {
16. Ok(n)  => println!("n is {}", n),
17. Err(e) => println!("Error: {}", e),
18. }
19. }

21. fn main() {
22. print(multiply("10", "2"));
23. print(multiply("t", "2"));

25. println!("Success!");
26. }

Using Result in fn main

Typically the main function will look like this:

1. fn main() {
2. println!("Hello World!");
3. }

However main is also able to have a return type of Result. If an error occurs within the main function it will return an error code and print a debug representation of the error( Debug trait ).

The following example shows such a scenario:

2. use std::num::ParseIntError;

4. fn main() -> Result<(), ParseIntError> {
5. let number_str = "10";
6. let number = match number_str.parse::<i32>() {
7. Ok(number)  => number,
8. Err(e) => return Err(e),
9. };
10. println!("{}", number);
11. Ok(())
12. }

You can find the solutions here(under the solutions path), but only use it when you need it :)