Idiomatic way to return an array of things?

Suppose I am writing a function that takes a bunch of string and filters out the "bad" ones.

The function then returns some strings, but there is a chance that all the strings are filtered out.

So I am wondering, should I use to Option here? I could see it making sense that the function either returns the None option variant when all strings are "bad", and it returns the Some variant when the strings are "good".

The function signature could look something like this:

pub fn filter_strings(inputs: Vec<String>) -> Option<Vec<String>> {
    // Todo...

But is there really any point in making this an Option?

Would this just make things unnecessarily more complicated since the consumer kind of needs to now check for the None variant and also for the case where it returns Some empty vector?

Is it simpler and more "idiomatic Rust" to just return a vector of strings here?

pub fn filter_strings(inputs: Vec<String>) -> Vec<String> {
    // Todo...

Or is there some advantage to using Option here that I am missing?

Yes. Probably the most common (although not the only) use of the Option type is for error handling, where None represents some exceptional circumstance that needs to be handled separately from the Some case.

So if an empty vector is likely to cause trouble for users of your function, e.g. you encourage use of the pattern filtered_result[0], then using an Option<Vec<String>> makes sense. Otherwise, you could just return an unwrapped Vec<String> and make it clear in the documentation that the filtered result could be empty.

impl Iterator<Item = String> might be useful - this is an idiomatic way to return a sequence of things since it doesn't force the caller to allocate (as with a Vec). I'm not sure what your use is, but if you're processing the elements one by one afterwards or something like that, you can completely avoid collecting, and iterator methods will automatically handle the some/no elements cases.

1 Like

Yes, that pretty much sums it up. An empty collection perfectly means that all elements were filtered out. There's no need to over-complicate things here.

People often forget that an empty collection of T is the identity element for collection of T.


That doesn't help much: the returned Some(Vec) could still be empty. For this pattern to work reliably, one would need a separate, guaranteed-non-empty vector type (likely a newtype around Vec).

There's no need for that. Again, being empty is a perfectly normal state of being for a Vec.

1 Like

I agree. Option<Vec<T>> only makes sense if there is a semantic difference between None and Some(vec![]). If these cases should be treated the same, then the Option just adds unnecessary unwrapping ceremony when using the interface.

Returning an empty Vec is just the "null object pattern".


Yes, a specific non-empty type would be better for that use case. There's even a crate for it, apparently.

Consider also: Iterator::filter

pub fn string_filter(&String) -> bool {
  // ...

fn it_filters_the_strings() {
  let strings = vec![...];
  let filtered = strings.clone().into_iter()
  assert!(filtered.len() <= strings.len());

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.