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

add-chapter-on-defaults #1580

Merged
merged 2 commits into from
Jul 26, 2022
Merged
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 @@ -160,6 +160,7 @@
- [Unpacking options with `?`](error/option_unwrap/question_mark.md)
- [Combinators: `map`](error/option_unwrap/map.md)
- [Combinators: `and_then`](error/option_unwrap/and_then.md)
- [Defaults: `or`, `or_else`, `get_or_insert`, 'get_or_insert_with`](error/option_unwrap/defaults.md)
- [`Result`](error/result.md)
- [`map` for `Result`](error/result/result_map.md)
- [aliases for `Result`](error/result/result_alias.md)
Expand Down
123 changes: 123 additions & 0 deletions src/error/option_unwrap/defaults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Unpacking options and defaults

The is more than one way to unpack an `Option` and fall back on a default if it is `None`. To choose the one that meets our needs, we need to consider the following:
* do we need eager or lazy evaluation?
* do we need to keep the original empty value intact, or modify it in place?

## `or()` is chainable, evaluates eagerly, keeps empty value intact

`or()`is chainable and eagerly evaluates its argument, as is shown in the following example. Note that because `or`'s arguments are evaluated eagerly, the variable passed to `or` is moved.

```
#[derive(Debug)]
enum Fruit { Apple, Orange, Banana, Kiwi, Lemon }

fn main() {
let apple = Some(Fruit::Apple);
let orange = Some(Fruit::Orange);
let no_fruit: Option<Fruit> = None;

let first_available_fruit = no_fruit.or(orange).or(apple);
println!("first_available_fruit: {:?}", first_available_fruit);
// first_available_fruit: Some(Orange)

// `or` moves its argument.
// In the example above, `or(orange)` returned a `Some`, so `or(apple)` was not invoked.
// But the variable named `apple` has been moved regardless, and cannot be used anymore.
// println!("Variable apple was moved, so this line won't compile: {:?}", apple);
// TODO: uncomment the line above to see the compiler error
}
```

## `or_else()` is chainable, evaluates lazily, keeps empty value intact

Another alternative is to use `or_else`, which is also chainable, and evaluates lazily, as is shown in the following example:

```
#[derive(Debug)]
enum Fruit { Apple, Orange, Banana, Kiwi, Lemon }

fn main() {
let apple = Some(Fruit::Apple);
let no_fruit: Option<Fruit> = None;
let get_kiwi_as_fallback = || {
println!("Providing kiwi as fallback");
Some(Fruit::Kiwi)
};
let get_lemon_as_fallback = || {
println!("Providing lemon as fallback");
Some(Fruit::Lemon)
};

let first_available_fruit = no_fruit
.or_else(get_kiwi_as_fallback)
.or_else(get_lemon_as_fallback);
println!("first_available_fruit: {:?}", first_available_fruit);
// Providing kiwi as fallback
// first_available_fruit: Some(Kiwi)
}
```

## `get_or_insert()` evaluates eagerly, modifies empty value im place

To make sure that an `Option` contains a value, we can use `get_or_insert` to modify it in place with a fallback value, as is shown in the following example. Note that `get_or_insert` eagerly evaluaes its parameter, so variable `apple` is moved:

```
#[derive(Debug)]
enum Fruit { Apple, Orange, Banana, Kiwi, Lemon }

fn main() {
let mut my_fruit: Option<Fruit> = None;
let apple = Fruit::Apple;
let first_available_fruit = my_fruit.get_or_insert(apple);
println!("my_fruit is: {:?}", first_available_fruit);
println!("first_available_fruit is: {:?}", first_available_fruit);
// my_fruit is: Apple
// first_available_fruit is: Apple
//println!("Variable named `apple` is moved: {:?}", apple);
// TODO: uncomment the line above to see the compliler error
}
```

## `get_or_insert_with()` evaluates lazily, modifies empty value im place

Instead of explicitly providing a value to fall back on, we can pass a closure to `get_or_insert_with`, as follows:
```
#[derive(Debug)]
enum Fruit { Apple, Orange, Banana, Kiwi, Lemon }

fn main() {
let mut my_fruit: Option<Fruit> = None;
let get_lemon_as_fallback = || {
println!("Providing lemon as fallback");
Fruit::Lemon
};
let first_available_fruit = my_fruit
.get_or_insert_with(get_lemon_as_fallback);
println!("my_fruit is: {:?}", first_available_fruit);
println!("first_available_fruit is: {:?}", first_available_fruit);
// Providing lemon as fallback
// my_fruit is: Lemon
// first_available_fruit is: Lemon

// If the Option has a value, it is left unchanged, and the closure is not invoked
let mut my_apple = Some(Fruit::Apple);
let should_be_apple = my_apple.get_or_insert_with(get_lemon_as_fallback);
println!("should_be_apple is: {:?}", should_be_apple);
println!("my_apple is unchanged: {:?}", my_apple);
// The output is a follows. Note that the closure `get_lemon_as_fallback` is not invoked
// should_be_apple is: Apple
// my_apple is unchanged: Some(Apple)
}
```

### See also:

[`closures`][closures], [`get_or_insert`][get_or_insert], [`get_or_insert_with`][get_or_insert_with], ,[`moved variables`][moved], [`or`][or], [`or_else`][or_else]

[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
[get_or_insert]: https://doc.rust-lang.org/core/option/enum.Option.html#method.get_or_insert
[get_or_insert_with]: https://doc.rust-lang.org/core/option/enum.Option.html#method.get_or_insert_with
[moved]: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
[or]: https://doc.rust-lang.org/core/option/enum.Option.html#method.or
[or_else]: https://doc.rust-lang.org/core/option/enum.Option.html#method.or_else