Option<T> and Result<T, E> in Depth

Iterating Over Optional Values

Both Option<T> and Result<T, E> implement IntoIterator, behaving as zero-or-one-element containers: Some(v) and Ok(v) yield 1 element, while None and Err(_) yield 0 elements.

Iterator Methods on Option<T> and Result<T, E>

Method Receiver Item Type Yielded Items
opt.iter() / res.iter() &self &T 1 item if Some/Ok, 0 if None/Err
opt.iter_mut() / res.iter_mut() &mut self &mut T 1 item if Some/Ok, 0 if None/Err
opt.into_iter() / res.into_iter() self T 1 item if Some/Ok, 0 if None/Err
opt.as_slice() &Option<T> &[T] Slice of length 1 or 0

Composability With .chain(), .extend(), and .flatten()

Because Option<T> implements IntoIterator<Item = T>, you can pass an Option<T> directly to any API that accepts impl IntoIterator<Item = T>—such as iter.chain(opt) or vec.extend(opt)—without branching on if let Some(x) = opt:

fn build_command_args(
    binary: &str,
    config_file: Option<&str>,
    positional: &[&str],
) -> Vec<String> {
    std::iter::once(binary)
        .chain(config_file) // Appends 1 item if `Some`, or 0 items if `None`!
        .chain(positional.iter().copied())
        .map(String::from)
        .collect()
}

fn main() {
    let with_cfg = build_command_args("app", Some("--config=prod.toml"), &["serve"]);
    let no_cfg = build_command_args("app", None, &["serve"]);

    println!("With config:    {with_cfg:?}");
    println!("Without config: {no_cfg:?}");
}
With config:    ["app", "--config=prod.toml", "serve"]
Without config: ["app", "serve"]

Extending a Collection from an Option<T>

Similarly, calling collection.extend(optional_value) appends the value if Some(x) and is a no-op if None:

fn main() {
    let mut tags = vec!["rust", "2024"];
    let extra_tag: Option<&str> = Some("cli");
    let missing_tag: Option<&str> = None;

    tags.extend(extra_tag);
    tags.extend(missing_tag);

    println!("Tags: {tags:?}");
}
Tags: ["rust", "2024", "cli"]