Collecting non-option items into Option<Vec<>>

Hello,

is there some straightforward way to "convert" iterated Vec<Item> into Option<Vec<Item>> directly on iterator builder pattern as an "one-liner"?

let data: Vec<Item> = ...;
let opt_data: Option<Vec<Item>> = data.iter().filter().map().etc().??? 

some collect:: perhaps?

I know I can do:

let chewed_data = data.iter().filter().map().etc();
let opt_data = if chewed_data.is_empty() { None } else { Some(chewed_data) };

but i'm wondering whether there's shorter/more idiomatic way...

According to docs, i can collect into option from items of Option type, but i need it for non-option Items in this case...

For one thing, if you use iter without a clone somewhere, you can only get Option<Vec<&Item>>.

Well then, you could add something like this: .map(|x| Some(x)) I guess.

Right! I skipped it in my example code, i do this in the iterator "chewing" chain, but it's good to point out to make it clear, thanks.

Ah :man_facepalming: ! Neat and direct workaround.
It feels a bit redundant to retype items only temporarily for my use case, though I guess it isn't much worse than some dedicated iterator builder/collector method that would do target conversion internally somehow.

edit: .map(|x| Some(x)) can even be a bit more shortened to .map(Some)

An important question here is what you need the Option for. Under what circumstances do you want it to be None? If you unconditionally want it to be Some then you can just collect into a Vec<Item> and then wrap in a Some, there isn't any need to do it inside the iterator. If you want a method call to stick at the end of the iterator chain, you can use .into(), which is implemented for Option via its From implementation.

Looks like .map(Some) does not work as needed for my case -> empy iterator collects into Some([]), not None as i'd like.

My use case is -> there's non-empty array/iterable of items. I'd like a function to return either None when no items match filter criteria, or Some(clones) of the items that do...

Writing this in functional style feelt a bit more elegant to me, thus i chose .iter().filter(...).cloned().some_convertion() way...

edit: Using one extra if else statement is not that big deal, i've been just wondering whether builder chain only was possible...

Stepping back a bit from the iterator methods, I think part of why this isn't easy is that you're introducing an extra state and hoping it doesn't happen.

You're looking for -> Option<Vec<_>>, but with the added "oh by the way" of "but it better not ever be Some(vec![])".

From the consumer's perspective, that's not ideal, since as far as rust knows it's still possible for it to be Some(vec![]). Can you elaborate on why you want the option here?

(Arguably you want an Option<NonEmptyVec<T>> here, except that type doesn't exist. But if it did, then conceptually collecting into it would do what you want. Except that due to the coherence rules I'm not sure it could be defined given the existing FromIterator for Option.)

Note that an empty vector doesn't have to allocate -- that's why Vec::new() can be const -- so there's no perf cost to handing out vec![] instead of None::<Vec<_>>.

4 Likes

Thanks for more insights!

I understand the point here, though am not sure if that would apply to my scenario, as the originally intended Option<Vec<_>> has been supposed to be funtion return value - guaranteed to never be the empty vector after sanitization of my incorrectly implemented chain that i run into...

Right, definitely makes sense for clean/proper Rust program. Option came up just as a naive brainstorming and quick implementation put down to code -> having "some/none" filtered items felt nicer compared to having "non-/empty vector".

Most clean approach seems to ineed be to go with returning empty / non-empty vector instead of Option, due to impossible third state when mapping my scenario to Vec vs Option.

Thanks to all again for discusison and great clarifications/detailed argumentations.

1 Like

Obligatory mention of Aiming for correctness with types (or the classic Haskell Parse, don’t validate).

But admittedly in this case there's not a great answer for doing that, since there's no common "non-empty container" or "non-empty iterator" approaches. You could do Option<(T, Vec<T>)>, but that's not particularly ergonomic to use.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.