I found very useful the property of FromIterator<Option<A>> for Option<V>
, but I found a bit surprising how it works on an empty iterator:
println!(
"{:?} should not this be None?",
[Some("a".to_string()), Some("b".to_string())][..0]
.iter()
.cloned()
.collect::<Option<String>>()
); // Some("") should not this be None?
collect empty example
Having a look at the option implementation, if the iterator is empty self.found_none
is never set at true, and Some(v)
is returned, where:
let v: V = FromIterator::from_iter(adapter.by_ref());
In the case of String
is v = ""
, so the result is Some("")
and not None
as I thought.
I started this journey just to avoid an empty-string-as-none problem, but this ruined my plans.
Can someone explain me the ratio behind this behaviour ?
This will be easier to explain if I change Option<String>
to Option<Vec<T>>
.
When you call collect::<Option<Vec<T>>>
on an Iterator<Item = Option<T>>
, then it will check for each item, is it None
, if so return None
, otherwise it is Some(_)
, then unwrap it and put it in the Vec<T>
. This way, if you don't have any values in the iterator, it doesn't see any None
, and so it doesn't return None
.
The important thing to note: calling collect::<Option<_>>
will only return None
if the iterator over Option<_>
returns None
.
i.e.
playground
fn main() {
let arr: [Option<char>; 0] = [];
assert_eq!(Some("".to_string()), arr.iter().cloned().collect::<Option<String>>());
assert_eq!(Some("hi".to_string()), [
Some('h'),
Some('i')
].iter().cloned().collect::<Option<String>>());
assert_eq!(None, [
Some('h'),
Some('i'),
None
].iter().cloned().collect::<Option<String>>());
assert_eq!(None, [
None,
Some('h'),
Some('i'),
].iter().cloned().collect::<Option<String>>());
}
The reason for this behavior is because most collections (including String
) in the std-lib allocate lazily, therefore an empty collection only takes up stack space. In many cases it is useful to lift an Option
or Result
and through a collection. i.e. going from Vec<Option<T>>
to Option<Vec<T>>
or Vec<Result<T, E>>
to Result<Vec<T>, E>
, which can be done like so: vec.into_iter().collect::<Option<Vec<_>>>()
.
1 Like
Thanks @KrishnaSannas,
I understand that, what I missed was the semantic of this behaviour, long story short, I hoped this code worked as solution for an exercism.io exercise:
fn raindrops_func(n: u32) -> String {
let sounds = [(3, "Pling"), (5, "Plang"), (7, "Plong")];
sounds
.iter()
.filter(|&(i, _)| n % i == 0)
.map(|&(_, s)| Some(s))
.collect::<Option<String>>()
.unwrap_or(n.to_string())
}
fn main() {
println!("raindrops({}) = {:?}", 1, raindrops_func(1));
println!("raindrops({}) = {:?}", 10, raindrops_func(10));
}
collect::<Option<String>>()
is Some
if all items are something and None
if at least one is nothing.
Even if an empty list is more nothing than something, Some
is returned the same, because collect::<Option<T>>()
is to be interpreted as collect_all (all()
is true
for an empty iter, like Some
is returned in this case), and not as collect_any (any()
is false
for an empty iter, like None
may be returned on an empty iter), so I had to add an if any()
check and it worked.
1 Like