2
votes

I am really new in rust, and while going through the rustlings exercises I found something I do not fully understand regarding stacked Options.

Given the following code:

let vector = vec![Some(24), None, Some(42)];
let mut iter = vector.iter();
while let Some(Some(number)) = iter.next() {
    println!("Number: {}", number);
}

I would expect to see the following output:

Number: 24
Number: 42

But I guess as soon as rust encounters the None, the while loop exits, only printing the 24

What would be the most idiomatic rust code to iterate and unwrap optional values? The closest that I got would look something like this:

let mut iter = vector.iter();
while let Some(iterNext) = iter.next() {
    if let Some(num) = iterNext {
        println!("Number: {}", num);
    }
}

Or it could also be done by checking the existence in a for loop:

for opt in &vector {
    if opt.is_some() {
        println!("Number: {}", opt.unwrap());
    }
}
2
Indeed, when it encounters the second element, which is Some(None), the while loop breaks. What is "more idiomatic" is more or less a matter of opinion, but I would definitely prefer the if let instead of the is_some / unwrap version. The whole point of if let is so that you can avoid manually checking / unwrapping the option.Jesper

2 Answers

6
votes

Another nice way to write this code is

for num in vector.iter().flatten() {
    println!("{}", num);
}

The flatten() method on an iterator treats each item of the iterator as an iterator, and returns an iterator over all these iterators chained together. Option is an iterator that yields one element if it is Some, or none for None, so flatten() is eactly what we want here.

Of course you could also write this using for_each(), but for code with side effects I generally prefer for loops.

5
votes

I would expect to see the following output: [...]

A while loop that encounters a false condition exits - but that's not specific to Rust, it's just how while loops work.

An idiomatic solution would probably combine your last two snippets:

for opt in &vector {
    if let Some(num) = opt {
        println!("Number: {}", num);
    }
}

Just a simple for loop containing an if let, no unwrapping required.

Another idiomatic variant is to use iterator adapters:

vector
    .iter()
    .filter_map(|opt| opt.as_ref())
    .for_each(|num| println!("{}", num));

Note that here we could use filter(Option::is_some), but then we would be left with options and would have to use unwrap() to get to the values. This is where filter_map() comes in useful: it filters the Some values (after applying the map function), and at the same time extracts the values inside. opt.as_ref() serves to trivially convert &Option<T>, obtained from iterating a vector of options by reference, to Option<&T> which filter_map expects returned.