|
| 1 | +# Unpacking options and defaults |
| 2 | + |
| 3 | +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: |
| 4 | +* do we need eager or lazy evaluation? |
| 5 | +* do we need to keep the original empty value intact, or modify it in place? |
| 6 | + |
| 7 | +## `or()` is chainable, evaluates eagerly, keeps empty value intact |
| 8 | + |
| 9 | +`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. |
| 10 | + |
| 11 | +``` |
| 12 | +#[derive(Debug)] |
| 13 | +enum Fruit { Apple, Orange, Banana, Kiwi, Lemon } |
| 14 | +
|
| 15 | +fn main() { |
| 16 | + let apple = Some(Fruit::Apple); |
| 17 | + let orange = Some(Fruit::Orange); |
| 18 | + let no_fruit: Option<Fruit> = None; |
| 19 | +
|
| 20 | + let first_available_fruit = no_fruit.or(orange).or(apple); |
| 21 | + println!("first_available_fruit: {:?}", first_available_fruit); |
| 22 | + // first_available_fruit: Some(Orange) |
| 23 | +
|
| 24 | + // `or` moves its argument. |
| 25 | + // In the example above, `or(orange)` returned a `Some`, so `or(apple)` was not invoked. |
| 26 | + // But the variable named `apple` has been moved regardless, and cannot be used anymore. |
| 27 | + // println!("Variable apple was moved, so this line won't compile: {:?}", apple); |
| 28 | + // TODO: uncomment the line above to see the compiler error |
| 29 | + } |
| 30 | +``` |
| 31 | + |
| 32 | +## `or_else()` is chainable, evaluates lazily, keeps empty value intact |
| 33 | + |
| 34 | +Another alternative is to use `or_else`, which is also chainable, and evaluates lazily, as is shown in the following example: |
| 35 | + |
| 36 | +``` |
| 37 | +#[derive(Debug)] |
| 38 | +enum Fruit { Apple, Orange, Banana, Kiwi, Lemon } |
| 39 | +
|
| 40 | +fn main() { |
| 41 | + let apple = Some(Fruit::Apple); |
| 42 | + let no_fruit: Option<Fruit> = None; |
| 43 | + let get_kiwi_as_fallback = || { |
| 44 | + println!("Providing kiwi as fallback"); |
| 45 | + Some(Fruit::Kiwi) |
| 46 | + }; |
| 47 | + let get_lemon_as_fallback = || { |
| 48 | + println!("Providing lemon as fallback"); |
| 49 | + Some(Fruit::Lemon) |
| 50 | + }; |
| 51 | +
|
| 52 | + let first_available_fruit = no_fruit |
| 53 | + .or_else(get_kiwi_as_fallback) |
| 54 | + .or_else(get_lemon_as_fallback); |
| 55 | + println!("first_available_fruit: {:?}", first_available_fruit); |
| 56 | + // Providing kiwi as fallback |
| 57 | + // first_available_fruit: Some(Kiwi) |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +## `get_or_insert()` evaluates eagerly, modifies empty value im place |
| 62 | + |
| 63 | +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: |
| 64 | + |
| 65 | +``` |
| 66 | +#[derive(Debug)] |
| 67 | +enum Fruit { Apple, Orange, Banana, Kiwi, Lemon } |
| 68 | +
|
| 69 | +fn main() { |
| 70 | + let mut my_fruit: Option<Fruit> = None; |
| 71 | + let apple = Fruit::Apple; |
| 72 | + let first_available_fruit = my_fruit.get_or_insert(apple); |
| 73 | + println!("my_fruit is: {:?}", first_available_fruit); |
| 74 | + println!("first_available_fruit is: {:?}", first_available_fruit); |
| 75 | + // my_fruit is: Apple |
| 76 | + // first_available_fruit is: Apple |
| 77 | + //println!("Variable named `apple` is moved: {:?}", apple); |
| 78 | + // TODO: uncomment the line above to see the compliler error |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +## `get_or_insert_with()` evaluates lazily, modifies empty value im place |
| 83 | + |
| 84 | +Instead of explicitly providing a value to fall back on, we can pass a closure to `get_or_insert_with`, as follows: |
| 85 | +``` |
| 86 | +#[derive(Debug)] |
| 87 | +enum Fruit { Apple, Orange, Banana, Kiwi, Lemon } |
| 88 | +
|
| 89 | +fn main() { |
| 90 | + let mut my_fruit: Option<Fruit> = None; |
| 91 | + let get_lemon_as_fallback = || { |
| 92 | + println!("Providing lemon as fallback"); |
| 93 | + Fruit::Lemon |
| 94 | + }; |
| 95 | + let first_available_fruit = my_fruit |
| 96 | + .get_or_insert_with(get_lemon_as_fallback); |
| 97 | + println!("my_fruit is: {:?}", first_available_fruit); |
| 98 | + println!("first_available_fruit is: {:?}", first_available_fruit); |
| 99 | + // Providing lemon as fallback |
| 100 | + // my_fruit is: Lemon |
| 101 | + // first_available_fruit is: Lemon |
| 102 | +
|
| 103 | + // If the Option has a value, it is left unchanged, and the closure is not invoked |
| 104 | + let mut my_apple = Some(Fruit::Apple); |
| 105 | + let should_be_apple = my_apple.get_or_insert_with(get_lemon_as_fallback); |
| 106 | + println!("should_be_apple is: {:?}", should_be_apple); |
| 107 | + println!("my_apple is unchanged: {:?}", my_apple); |
| 108 | + // The output is a follows. Note that the closure `get_lemon_as_fallback` is not invoked |
| 109 | + // should_be_apple is: Apple |
| 110 | + // my_apple is unchanged: Some(Apple) |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +### See also: |
| 115 | + |
| 116 | +[`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] |
| 117 | + |
| 118 | +[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html |
| 119 | +[get_or_insert]: https://doc.rust-lang.org/core/option/enum.Option.html#method.get_or_insert |
| 120 | +[get_or_insert_with]: https://doc.rust-lang.org/core/option/enum.Option.html#method.get_or_insert_with |
| 121 | +[moved]: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html |
| 122 | +[or]: https://doc.rust-lang.org/core/option/enum.Option.html#method.or |
| 123 | +[or_else]: https://doc.rust-lang.org/core/option/enum.Option.html#method.or_else |
0 commit comments