Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated documentation to address ? and Option #1713

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
- [Unrecoverable Errors with `panic!`](ch09-01-unrecoverable-errors-with-panic.md)
- [Recoverable Errors with `Result`](ch09-02-recoverable-errors-with-result.md)
- [To `panic!` or Not To `panic!`](ch09-03-to-panic-or-not-to-panic.md)
- [Using `Option` with `Result` and `?`](ch09-04-using-option-with-result.md)

- [Generic Types, Traits, and Lifetimes](ch10-00-generics.md)
- [Generic Data Types](ch10-01-syntax.md)
Expand Down
2 changes: 1 addition & 1 deletion src/ch06-03-if-let.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ if let Some(3) = some_u8_value {

The syntax `if let` takes a pattern and an expression separated by an equal
sign. It works the same way as a `match`, where the expression is given to the
`match` and the pattern is its first arm.
`match` and the pattern is its first arm. Later, in [REF] we will see another approach to handling deeply nested `Option` values concisely, which works well with `if let`.

Using `if let` means less typing, less indentation, and less boilerplate code.
However, you lose the exhaustive checking that `match` enforces. Choosing
Expand Down
4 changes: 2 additions & 2 deletions src/ch09-02-recoverable-errors-with-result.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,10 +465,10 @@ and put the contents into that `String`, and then return it. Of course,
this doesn’t give us the opportunity to show off all of this error handling,
so we did it the hard way at first.

#### The `?` Operator Can Only Be Used in Functions That Return `Result`
#### The `?` Operator in Functions That Return `Result`

The `?` operator can only be used in functions that have a return type of
`Result`, because it is defined to work in the same way as the `match`
`Result` or `Option`. We will see examples of `?` used with `Option` later. In a `Result` return context, it is defined to work in the same way as the `match`
expression we defined in Listing 9-6. The part of the `match` that requires a
return type of `Result` is `return Err(e)`, so the return type of the function
must be a `Result` to be compatible with this `return`.
Expand Down
7 changes: 1 addition & 6 deletions src/ch09-03-to-panic-or-not-to-panic.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,4 @@ incorrect values. The `Result` enum uses Rust’s type system to indicate that
operations might fail in a way that your code could recover from. You can use
`Result` to tell code that calls your code that it needs to handle potential
success or failure as well. Using `panic!` and `Result` in the appropriate
situations will make your code more reliable in the face of inevitable problems.

Now that you’ve seen useful ways that the standard library uses generics with
the `Option` and `Result` enums, we’ll talk about how generics work and how you
can use them in your code.

situations will make your code more reliable in the face of inevitable problems.
213 changes: 213 additions & 0 deletions src/ch09-04-using-option-with-result.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
## `Option` and error handing

The `Option` type handles cases where a value may or may not be present. The absence of a value does not necessarily indicate an error case - `Option` and `Result` are distinct types.

As an example, `HashMap`, which we previously explored [REF], returns an `Option` when we `get` a value from the map. To revisit our example from [REF]:

```rust
use std::collections::HashMap;

fn main() {

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

let team_name = String::from("Blue");
let score = scores
.get(&team_name) // returns an Option<u32>
.map(|i| i.to_string()) // maps Option<u32> to Option<String>
.unwrap_or("Unk".into()); // unwrap with a default for None case

println!("{}",score);
}
```

However, we sometimes want to treat `None` as an error. In these cases, `Option` provides `ok_or` and `ok_or_else`, which map `Option`s to `Result`s. For example, if our league has a defined number of teams, we can ensure that our `HashMap` has a score for every team. In this case, failure to retrieve a value is an *error* indicating some problem in our program, not an expected value to handle. Accordingly, we use `ok_or` to map a `None` to a desired error type.

```rust
use std::collections::HashMap;
use std::error::Error;

#[derive(PartialEq,Eq,Hash)]
enum Teams {
Blue,
Yellow
}

fn init_scores() -> HashMap<Teams,u32> {

let mut scores = HashMap::new();
scores.insert(Teams::Blue, 10);
scores.insert(Teams::Yellow, 50);
scores
}

fn score_for<'a>(scores: &'a HashMap<Teams,u32>, team: Teams) -> Result<&'a u32,String> {
scores.get(&team).ok_or(String::from("Missing team error"))
}

fn main() -> Result<(), Box<dyn Error>> {
let scores = init_scores();
println!("Blue team score: {}",score_for(&scores, Teams::Blue)?);
Ok(())
}
```

## Option chaining with `?`

In [REF] we discussed using the `?` operator as a shortcut for handling `Result` values. Using `?` we can "pretend" that we only need to handle the happy path where all functions return the expected `Ok` value, while preserving the ability to handle errors elsewhere in the program.

Just as we naturally have chained or nested `Result` values, we often find situations where `Option` values are chained or deeply nested. As an example, consider our sports league above. Some teams have mascots, but not all. The mascots may have optional facts about them. We very frequently want to lookup team mascot facts when we have a `League` in hand, so we want a convenient way to reference them.

We might model this situation as in the following:

```rust
use std::collections::HashMap;

#[derive(PartialEq,Eq,Hash)]
enum Teams {
Blue,
Red,
Yellow
}

struct League {
//... fields omitted ... //
teams: HashMap<Teams,Team>,
}

struct Team {
//... fields omitted ..//
mascot: Option<Mascot>,
}

struct Mascot {
// ... fields omitted ..//
facts: Option<String>
}

impl League {
fn team_mascot(&self,team: Teams) -> Option<&str> {
match self.teams.get(&team) {
Some(t) =>
match t.mascot {
Some(ref m) =>
match m.facts {
Some(ref f) => Some(f.as_str()),
None => None
},
None => None
},
None => None
}
}
}
```

This does the job, but the chain of nested `match` statements is pretty hard to read. Unfortunately `if let` doesn't help us much here:

```rust
impl League {
fn team_mascot(&self,team: Teams) -> Option<&str> {
if let Some(t) = self.teams.get(&team) {
if let Some(ref m) = t.mascot {
if let Some(ref f) = m.facts {
Some(f.as_str())
} else {
None
}
} else {
None
}
} else {
None
}
}
}
```

Fortunately, the `?` is specialized to `Option` as well as `Result`, and is defined similarly. If `foo` is of type `Option<T>`, we can think of `?` as rewriting `foo?` to:

```rust
match foo {
Some(v) => v,
None => return None
}
```

There are a couple of things to note here:

1) `?` takes `f` as an owned value rather than by reference
2) If `foo` is `None`, `?` *returns* None, rather than evaluating to a `None` value, as in our examples above.

In our function example, an early return does not change how our function behaves, but the requirement to own the value contained in the `Option` causes Rust to *move* the value into the function. Because we took `&self` by reference, we do not own it, so this casues an error.

The naïve rewriting of our chain of `match` or `if let` statements in terms of `?` is:

```rust
impl League {
fn team_mascot(&self,team: Teams) -> Option<&str> {
let fact = self.teams.get(&team)?
.mascot?
.facts?
.as_str();
Some(fact)
}
}
```

This generates the following error:

```text
error[E0507]: cannot move out of borrowed content
--> main.rs:27:20
|
27 | let fact = self.teams.get(&team)?
| ^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content
```

It is the line `mascot?` that casues the move; if we look at our rewriting example above, we will see that `Some(v) => v` wants to own the `Mascot` struct. Rewriting our chain of `?` to take references rather that owned values solves the problem:

```rust
impl League {
fn team_mascot(&self,team: Teams) -> Option<&str> {
let fact = self.teams
.get(&team)? // A `&Team` or return if `None`
.mascot.as_ref()? // A `&Mascot` or return
.facts.as_ref()? // A `&String` or return
.as_str(); // Yields a `&str`
Some(fact)
}
}
```

As a reminder, the `as_ref` method maps `Option<T>` to `Option<&T>`, so when `?` pulls out the inner value, we are holding a reference, not an owned value.

### Avoiding early returns with `?` using closures

In our example above, we created a function that pulled a value out of a chain of `Option`s. Sometimes, we just want to pull the final `Option` out of a chain in the flow of some other logic. Unfortunately, if any value in our chain is `None` this will generate an early return of `None` which is not the flow we want, and probably conflicts with our return value.

In this situation, we can construct and evaluate a closure that behaves like our function above, returning an `Option` to use in our flow.

As an example:

```rust
let possible_fact = { || // empty argument list for closure
Some(self.teams.get(&team)? // return `Some` if all values are `Some`
.mascot.as_ref()? // or `None` if any values are `None`
.facts.as_ref()?
.as_str())
}(); // evalutes the closure

if let Some(fact) = possible_fact {
println!("I have a fact: {}", fact);
} else {
println!("No facts available");
}
```

Now that you’ve seen useful ways that the standard library uses generics with
the `Option` and `Result` enums, we’ll talk about how generics work and how you
can use them in your code.