I was trying to iterate over a subsection of a vector of strings, i.e. a subslice of Vec<String>
. Within each iteration, I wanted to pass the string as a slice to a function.
I didn't notice that Vec::get
returns an Option
, and thought I could just directly iterate over the return value:
fn take_str(s: &str) {
println!("{}", s);
}
fn main() {
let str_vec: Vec<String> = ["one", "two", "three", "uno", "dos", "tres"]
.iter()
.map(|&s| s.into())
.collect();
for s in str_vec.get(0..3) {
take_str(&s);
}
}
error[E0308]: mismatched types
--> src/main.rs:11:18
|
11 | take_str(&s); // Type mismatch: found type `&&[std::string::String]`
| ^^ expected `str`, found `&[String]`
|
= note: expected reference `&str`
found reference `&&[String]`
I was expecting s
to be a String
, but it's actually &[String]
. This is because my for
loop is iterating over the Option
returned by Vec::get()
.
I also wrote the following code, which demonstrates that the for
loop is in fact unwrapping an Option
:
let foo = Option::Some(["foo".to_string()]);
for f in foo {
take_str(&f); // Same error as above, showing `f` is of type `&[String]`
}
This is incredibly confusing; I never expected (until I wrote this code and figured out what it's actually doing) that Option
could be unwrapped by iterating over it. Why is that supported? What use case is there for iterating over an Option
?
What use case is there for iterating over an
Option
?
My favorite reason, in a word, is flatten
:
fn main() {
let results = [Some(1), None, Some(3), None];
let sum: i32 = results.into_iter().flatten().sum();
println!("{}", sum)
}
Before Rust 1.29, you can use flat_map
:
fn main() {
let results = vec![Some(1), None, Some(3), None];
let sum: i32 = results.into_iter().flat_map(|x| x).sum();
println!("{}", sum)
}
Option
can be thought of as a container that can hold exactly zero or one elements. Compare this to a Vec
, which can hold zero or many elements. In a large set of ways, an Option
is a container just like a Vec
!
Implementing IntoIterator
allows Option
to participate in a larger share of APIs.
Note that IntoIterator
is also implemented for Result
, for similar reasons.
This is incredibly confusing
Yes, it is, which is why Clippy has a lint for it:
warning: for loop over `str_vec.get(0..3)`, which is an `Option`. This is more readably written as an `if let` statement
--> src/main.rs:10:14
|
10 | for s in str_vec.get(0..3) {
| ^^^^^^^^^^^^^^^^^
|
= note: `#[warn(clippy::for_loops_over_fallibles)]` on by default
= help: consider replacing `for s in str_vec.get(0..3)` with `if let Some(s) = str_vec.get(0..3)`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
This shows that there are ways that an Option
is not like a container to a programmer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With