9

I am trying to iterate on on Option<Vec<>>.

#[derive(Debug)]
pub struct Person {
    pub name: Option<String>,
    pub age: Option<u64>,
}

#[derive(Debug)]
pub struct Foo {
    pub people: Option<Vec<Person>>,
}

Naively I am using

for i in foo.people.iter() {
    println!("{:?}", i);
}

Instead of iterating over all the elements of the Vec, I am actually displaying the whole Vec. It is like I am iterating over the only reference of the Option.

Using the following, I am iterating over the Vec content:

for i in foo.people.iter() {
    for j in i.iter() {
        println!("{:?}", j);
    }
}

I am not sure this is the most pleasant syntax, I believe you should unwrap the Option first to actually iterate on the collection.

Then I don't see where you can actually use Option::iter, if you always have a single reference.

Here is the link to the playground.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Xavier T.
  • 40,509
  • 10
  • 68
  • 97
  • 1
    What's the question? – Lee Dec 01 '16 at 10:19
  • I don't understand in which case you should use Option<>.iter(), I got maybe wrongly the feeling that it will always return a single item. – Xavier T. Dec 01 '16 at 10:30
  • @XavierT. `Option::iter` is designed for cases where you want to treat the option as a zero-or-one-element container. It doesn't seem very useful to me either, but that's the idea. – user4815162342 Dec 01 '16 at 14:18
  • @user4815162342 `let foo: Vec – Shepmaster Dec 01 '16 at 14:22

4 Answers4

22

As mentioned in comments to another answer, I would use the following:

// Either one works
foo.people.iter().flatten()
foo.people.iter().flat_map(identity)

The iter method on Option<T> will return an iterator of one or zero elements.

flatten takes each element (in this case &Vec<Person>) and flattens their nested elements.

This is the same as doing flat_map with identity, which takes each element (in this case &Vec<Person>) and flattens their nested elements.

Both paths result in an Iterator<Item = &Person>.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Lukazoid
  • 19,016
  • 3
  • 62
  • 85
  • Note if `foo.people` is a `Option` over a stacked borrow (aka `foo.people: Option<&&Vec>` , then the reference should be *unstacked* using `.cloned()`. e.g. `foo.people.cloned().iter().flatten()` – Ben Mar 26 '22 at 21:47
  • @Ben Do you know another way to do this in the case of a "stacked borrow"? I'd like to iterate over a `Option<&HashMap<...,...>>` and treat `None` as empty HashMap without having to copy the content. – Splines Jul 23 '23 at 22:01
11

Option has an iter method that "iterates over the possibly contained value", i.e. provides either the single value in the Option (if option is Some), or no values at all (if the option is None). As such it is useful if you want to treat the option as a container where None means the container is empty and Some means it contains a single element.

To iterate over the underlying element's values, you need to switch from foo.people.iter() to either foo.people.unwrap().iter() or foo.people.unwrap_or_else(Vec::new).iter(), depending on whether you want the program to panic or to not iterate when encountering None people.

Compilable example in the playground.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • 3
    instead of `unwrap` one could also simply wrap `if let Some(ref v) = foo.people {}` around the iteration – oli_obk Dec 01 '16 at 10:44
  • @ker That's another option (no pun intended), but it does require an additional indentation level around the loop. `unwrap()` is still useful when the code actually expects the option to be non-`None` and wants to assert that. – user4815162342 Dec 01 '16 at 10:47
  • 7
    If I always wanted to iterate over the people I would probably do `foo.people.iter().flat_map(|v|v.iter())`, `None` would give an empty iterator while otherwise we would give all the elements of the vector. – Lukazoid Dec 01 '16 at 14:05
  • 5
    @Lukazoid no need for `v.iter()`; just `v` will work. – Shepmaster Dec 01 '16 at 14:23
  • @Shepmaster I didn't spot that `flat_map` handles `IntoIterator`, even better then. – Lukazoid Dec 01 '16 at 15:01
  • @Lukazoid You should answer that. `foo.people.iter().flat_map(identity)` is a better way to iterate IMO. – Boiethios May 10 '20 at 20:50
  • @Boiethios You mean the `identity` function added 2 years after my comment? Edit: Oh you mean add as an additional answer? Sorry I misunderstood – Lukazoid May 10 '20 at 22:55
  • @Lukazoid I meant the solution, whatever if you type `|v| v` or `identity`: that's still beter than an `unwrap` or an `unwrap_or_else(Vec::new)`. – Boiethios May 11 '20 at 06:04
  • @Boiethios It's not (stylistically) better than an `unwrap` because it has different behavior. I argue that it's not better than `unwrap_or_else` either because `unwrap_or_else` makes it clearer what's going on without relying on `Option` acting like a container. (Also, `Vec::new()` is guaranteed not to allocate, so there should be no performance lost in the `None` case.) Judging by the generated assembly ([playground](https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=0cb79cdfaad105f1ea06e89684c33231)), the `unwrap_or_else` version is more efficient as well. – user4815162342 May 11 '20 at 06:28
6

Use Option::as_deref and Option::unwrap_or_default:

for i in foo.people.as_deref().unwrap_or_default() {
    println!("{:?}", i);
}

Option::as_deref converts &Option<Vec<T>> into Option<&[T]>, then unwrap_or_default returns that &[T] or the default (an empty slice). You can then iterate on that directly.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
1

If you don't need an actual value with an IntoIterator implementation, you can just use an explicit if let instead:

if let Some(x) = foo.people {
    for i in x {
        // work with i here
    }
}
Aplet123
  • 33,825
  • 1
  • 29
  • 55