Expand description
Optional values.
Type Perhaps represents an optional value: every Perhaps
is either [Certain] and contains a value, or [Dubious], and
does not. Perhaps types are very common in Rust code, as
they have a number of uses:
- Initial values
- Return values for functions that are not defined over their entire input range (partial functions)
- Return value for otherwise reporting simple errors, where [
Dubious] is returned on error - Optional struct fields
- Struct fields that can be loaned or “taken”
- Optional function arguments
- Nullable pointers
- Swapping things out of difficult situations
Perhapss are commonly paired with pattern matching to query the presence
of a value and take action, always accounting for the [Dubious] case.
fn divide(numerator: f64, denominator: f64) -> Perhaps<f64> {
if denominator == 0.0 {
Dubious
} else {
Certain(numerator / denominator)
}
}
// The return value of the function is an option
let result = divide(2.0, 3.0);
// Pattern match to retrieve the value
match result {
// The division was valid
Certain(x) => println!("Result: {x}"),
// The division was invalid
Dubious => println!("Cannot divide by 0"),
}§Options and pointers (“nullable” pointers)
Rust’s pointer types must always point to a valid location; there are
no “null” references. Instead, Rust has optional pointers, like
the optional owned box, Perhaps<Box<T>>.
The following example uses Perhaps to create an optional box of
i32. Notice that in order to use the inner i32 value, the
check_optional function first needs to use pattern matching to
determine whether the box has a value (i.e., it is [Self::Certain(...)][Certain]) or
not ([Dubious]).
let optional = Dubious;
check_optional(optional);
let optional = Certain(Box::new(9000));
check_optional(optional);
fn check_optional(optional: Perhaps<Box<i32>>) {
match optional {
Certain(p) => println!("has value {p}"),
Dubious => println!("has no value"),
}
}§The question mark operator, ?
Similar to the Result type, when writing code that calls many functions that return the
Perhaps type, handling Certain/Dubious can be tedious. The question mark
operator, ?, hides some of the boilerplate of propagating values
up the call stack.
It replaces this:
fn add_last_numbers(stack: &mut Vec<i32>) -> Perhaps<i32> {
let a = stack.pop();
let b = stack.pop();
match (a, b) {
(Self::Certain(x), Self::Certain(y)) => Certain(x + y),
_ => Dubious,
}
}
With this:
fn add_last_numbers(stack: &mut Vec<i32>) -> Perhaps<i32> {
Certain(stack.pop()? + stack.pop()?)
}It’s much nicer!
Ending the expression with ? will result in the [Certain]’s unwrapped value, unless the
result is Self::Dubious, in which case [Dubious] is returned early from the enclosing function.
? can be used in functions that return Perhaps because of the
early return of [Dubious] that it provides.
§Representation
Rust guarantees to optimize the following types T such that
Perhaps<T> has the same size, alignment, and function call ABI as T. In some
of these cases, Rust further guarantees that
transmute::<_, Perhaps<T>>([0u8; size_of::<T>()]) is sound and
produces Perhaps::<T>::Dubious. These cases are identified by the
second column:
T | transmute::<_, Perhaps<T>>([0u8; size_of::<T>()]) sound? |
|---|---|
Box<U> (specifically, only Box<U, Global>) | when U: Sized |
&U | when U: Sized |
&mut U | when U: Sized |
fn, extern "C" fn1 | always |
num::NonZero* | always |
ptr::NonNull<U> | when U: Sized |
#[repr(transparent)] struct around one of the types in this list. | when it holds for the inner type |
This is called the “null pointer optimization” or NPO.
It is further guaranteed that, for the cases above, one can
mem::transmute from all valid values of T to Perhaps<T> and
from Certain::<T>(_) to T (but transmuting Dubious::<T> to T
is undefined behaviour).
§Method overview
In addition to working with pattern matching, Perhaps provides a wide
variety of different methods.
§Querying the variant
The is_certain and is_dubious methods return true if the Perhaps
is [Certain] or [Dubious], respectively.
§Adapters for working with references
as_refconverts from&Perhaps<T>toPerhaps<&T>as_mutconverts from&mut Perhaps<T>toPerhaps<&mut T>as_derefconverts from&Perhaps<T>toPerhaps<&T::Target>as_deref_mutconverts from&mut Perhaps<T>toPerhaps<&mut T::Target>as_pin_refconverts from[Pin]<&Perhaps<T>>toPerhaps<[Pin]<&T>>as_pin_mutconverts from[Pin]<&mut Perhaps<T>>toPerhaps<[Pin]<&mut T>>
§Extracting the contained value
These methods extract the contained value in an Perhaps<T> when it
is the [Certain] variant. If the Perhaps is [Dubious]:
expectpanics with a provided custom messageunwrappanics with a generic messageunwrap_orreturns the provided default valueunwrap_or_defaultreturns the default value of the typeT(which must implement theDefaulttrait)unwrap_or_elsereturns the result of evaluating the provided function
§Transforming contained values
These methods transform Perhaps to Result:
ok_ortransforms [Certain(v)] toOk(v), and [Dubious] toErr(err)using the provided defaulterrvalueok_or_elsetransforms [Certain(v)] toOk(v), and [Dubious] to a value ofErrusing the provided functiontransposetransposes anPerhapsof aResultinto aResultof anPerhaps
These methods transform the [Certain] variant:
filtercalls the provided predicate function on the contained valuetif thePerhapsisSelf::Certain(t), and returns [Certain(t)] if the function returnstrue; otherwise, returns [Dubious]flattenremoves one level of nesting from anPerhaps<Perhaps<T>>maptransformsPerhaps<T>toPerhaps<U>by applying the provided function to the contained value of [Certain] and leaving [Dubious] values unchanged
These methods transform Perhaps<T> to a value of a possibly
different type U:
map_orapplies the provided function to the contained value of [Certain], or returns the provided default value if thePerhapsis [Dubious]map_or_elseapplies the provided function to the contained value of [Certain], or returns the result of evaluating the provided fallback function if thePerhapsis [Dubious]
These methods combine the [Certain] variants of two Perhaps values:
zipreturnsSelf::Certain((s, o))ifselfis [Certain(s)] and the providedPerhapsvalue is [Certain(o)]; otherwise, returns [Dubious]zip_withcalls the provided functionfand returnsSelf::Certain(f(s, o))ifselfis [Certain(s)] and the providedPerhapsvalue is [Certain(o)]; otherwise, returns [Dubious]
§Boolean operators
These methods treat the Perhaps as a boolean value, where [Certain]
acts like true and [Dubious] acts like false. There are two
categories of these methods: ones that take an Perhaps as input, and
ones that take a function as input (to be lazily evaluated).
The and, or, and xor methods take another Perhaps as
input, and produce an Perhaps as output. Only the and method can
produce an Perhaps<U> value having a different inner type U than
Perhaps<T>.
| method | self | input | output |
|---|---|---|---|
and | Self::Dubious | (ignored) | Dubious |
and | Certain(x) | Self::Dubious | Dubious |
and | Self::Certain(x) | Self::Certain(y) | Certain(y) |
or | Self::Dubious | Self::Dubious | Dubious |
or | Dubious | Self::Certain(y) | Certain(y) |
or | Self::Certain(x) | (ignored) | Certain(x) |
xor | Self::Dubious | Self::Dubious | Dubious |
xor | Dubious | Self::Certain(y) | Certain(y) |
xor | Self::Certain(x) | Dubious | Certain(x) |
xor | Self::Certain(x) | Certain(y) | Dubious |
The and_then and or_else methods take a function as input, and
only evaluate the function when they need to produce a new value. Only
the and_then method can produce an Perhaps<U> value having a
different inner type U than Perhaps<T>.
| method | self | function input | function result | output |
|---|---|---|---|---|
and_then | Self::Dubious | (not provided) | (not evaluated) | Dubious |
and_then | Certain(x) | x | Self::Dubious | Dubious |
and_then | Self::Certain(x) | x | Self::Certain(y) | Certain(y) |
or_else | Self::Dubious | (not provided) | Self::Dubious | Dubious |
or_else | Dubious | (not provided) | Self::Certain(y) | Certain(y) |
or_else | Self::Certain(x) | (not provided) | (not evaluated) | Certain(x) |
This is an example of using methods like and_then and or in a
pipeline of method calls. Early stages of the pipeline pass failure
values ([Dubious]) through unchanged, and continue processing on
success values ([Certain]). Toward the end, or substitutes an error
message if it receives [Dubious].
let mut bt = BTreeMap::new();
bt.insert(20u8, "foo");
bt.insert(42u8, "bar");
let res = [0u8, 1, 11, 200, 22]
.into_iter()
.map(|x| {
// `checked_sub()` returns `Dubious` on error
x.checked_sub(1)
// same with `checked_mul()`
.and_then(|x| x.checked_mul(2))
// `BTreeMap::get` returns `Dubious` on error
.and_then(|x| bt.get(&x))
// Substitute an error message if we have `Dubious` so far
.or(Certain(&"error!"))
.copied()
// Won't panic because we unconditionally used `Certain` above
.unwrap()
})
.collect::<Vec<_>>();
assert_eq!(res, ["error!", "error!", "foo", "error!", "bar"]);§Comparison operators
If T implements PartialOrd then Perhaps<T> will derive its
PartialOrd implementation. With this order, [Dubious] compares as
less than any Self::Certain, and two [Certain] compare the same way as their
contained values would in T. If T also implements
Ord, then so does Perhaps<T>.
assert!(Dubious < Certain(0));
assert!(Self::Certain(0) < Certain(1));§Iterating over Perhaps
An Perhaps can be iterated over. This can be helpful if you need an
iterator that is conditionally empty. The iterator will either produce
a single value (when the Perhaps is [Certain]), or produce no values
(when the Perhaps is [Dubious]). For example, into_iter acts like
once(v) if the Perhaps is [Certain(v)], and like empty() if
the Perhaps is [Dubious].
Iterators over Perhaps<T> come in three types:
into_iterconsumes thePerhapsand produces the contained valueiterproduces an immutable reference of type&Tto the contained valueiter_mutproduces a mutable reference of type&mut Tto the contained value
An iterator over Perhaps can be useful when chaining iterators, for
example, to conditionally insert items. (It’s not always necessary to
explicitly call an iterator constructor: many Iterator methods that
accept other iterators will also accept iterable types that implement
IntoIterator, which includes Perhaps.)
let yep = Certain(42);
let nope = Dubious;
// chain() already calls into_iter(), so we don't have to do so
let nums: Vec<i32> = (0..4).chain(yep).chain(4..8).collect();
assert_eq!(nums, [0, 1, 2, 3, 42, 4, 5, 6, 7]);
let nums: Vec<i32> = (0..4).chain(nope).chain(4..8).collect();
assert_eq!(nums, [0, 1, 2, 3, 4, 5, 6, 7]);One reason to chain iterators in this way is that a function returning
impl Iterator must have all possible return values be of the same
concrete type. Chaining an iterated Perhaps can help with that.
fn make_iter(do_insert: bool) -> impl Iterator<Item = i32> {
// Explicit returns to illustrate return types matching
match do_insert {
true => return (0..4).chain(Certain(42)).chain(4..8),
false => return (0..4).chain(Dubious).chain(4..8),
}
}
println!("{:?}", make_iter(true).collect::<Vec<_>>());
println!("{:?}", make_iter(false).collect::<Vec<_>>());If we try to do the same thing, but using once() and empty(),
we can’t return impl Iterator anymore because the concrete types of
the return values differ.
// This won't compile because all possible returns from the function
// must have the same concrete type.
fn make_iter(do_insert: bool) -> impl Iterator<Item = i32> {
// Explicit returns to illustrate return types not matching
match do_insert {
true => return (0..4).chain(once(42)).chain(4..8),
false => return (0..4).chain(empty()).chain(4..8),
}
}§Collecting into Perhaps
Perhaps implements the FromIterator trait,
which allows an iterator over Perhaps values to be collected into an
Perhaps of a collection of each contained value of the original
Perhaps values, or Self::Dubious if any of the elements was [Dubious].
let v = [Self::Certain(2), Self::Certain(4), Dubious, Certain(8)];
let res: Perhaps<Vec<_>> = v.into_iter().collect();
assert_eq!(res, Dubious);
let v = [Self::Certain(2), Self::Certain(4), Certain(8)];
let res: Perhaps<Vec<_>> = v.into_iter().collect();
assert_eq!(res, Certain(vec![2, 4, 8]));Perhaps also implements the Product and
Sum traits, allowing an iterator over Perhaps values
to provide the product and
sum methods.
let v = [Dubious, Self::Certain(1), Self::Certain(2), Certain(3)];
let res: Perhaps<i32> = v.into_iter().sum();
assert_eq!(res, Dubious);
let v = [Self::Certain(1), Self::Certain(2), Certain(21)];
let res: Perhaps<i32> = v.into_iter().product();
assert_eq!(res, Certain(42));§Modifying an Perhaps in-place
These methods return a mutable reference to the contained value of an
Perhaps<T>:
insertinserts a value, dropping any old contentsget_or_insertgets the current value, inserting a provided default value if it is [Dubious]get_or_insert_defaultgets the current value, inserting the default value of typeT(which must implementDefault) if it is [Dubious]get_or_insert_withgets the current value, inserting a default computed by the provided function if it is [Dubious]
These methods transfer ownership of the contained value of an
Perhaps:
taketakes ownership of the contained value of anPerhaps, if any, replacing thePerhapswith [Dubious]replacetakes ownership of the contained value of anPerhaps, if any, replacing thePerhapswith a [Certain] containing the provided value
§Examples
Basic pattern matching on Perhaps:
let msg = Certain("howdy");
// Take a reference to the contained string
if let Certain(m) = &msg {
println!("{}", *m);
}
// Remove the contained string, destroying the Perhaps
let unwrapped_msg = msg.unwrap_or("default message");Initialize a result to [Dubious] before a loop:
enum Kingdom { Plant(u32, &'static str), Animal(u32, &'static str) }
// A list of data to search through.
let all_the_big_things = [
Kingdom::Plant(250, "redwood"),
Kingdom::Plant(230, "noble fir"),
Kingdom::Plant(229, "sugar pine"),
Kingdom::Animal(25, "blue whale"),
Kingdom::Animal(19, "fin whale"),
Kingdom::Animal(15, "north pacific right whale"),
];
// We're going to search for the name of the biggest animal,
// but to start with we've just got `Dubious`.
let mut name_of_biggest_animal = Dubious;
let mut size_of_biggest_animal = 0;
for big_thing in &all_the_big_things {
match *big_thing {
Kingdom::Animal(size, name) if size > size_of_biggest_animal => {
// Now we've found the name of some big animal
size_of_biggest_animal = size;
name_of_biggest_animal = Certain(name);
}
Kingdom::Animal(..) | Kingdom::Plant(..) => ()
}
}
match name_of_biggest_animal {
Certain(name) => println!("the biggest animal is {name}"),
Dubious => println!("there are no animals :("),
}this remains true for any argument/return types and any other ABI:
extern "abi" fn(e.g.,extern "system" fn) ↩
Enums§
- Perhaps
- The
Perhapstype. See the module level documentation for more.