Skip to content

Commit

Permalink
add-chapter-on-defaults (#1580)
Browse files Browse the repository at this point in the history
* add-chapter-on-defaults

* add-subtutles
  • Loading branch information
AlexCue987 authored Jul 26, 2022
1 parent 900a0a6 commit efb11b8
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
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

0 comments on commit efb11b8

Please sign in to comment.