Using Iterator filter with function

In the example below, filter works when passed a closure, but not when passed a function.
What can I do to make the function version work?

fn is_short(s: &str) -> bool {
    s.len() <= 5
}

fn main() {
    let months = "January|February|March|April|May|June|July|August";

    //let short_names: Vec<&str> = months.split('|').filter(|m| m.len() <= 5).collect();
    let short_names: Vec<&str> = months.split('|').filter(is_short).collect();
    println!("short names = {:?}", short_names);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0631]: type mismatch in function arguments
 --> src/main.rs:9:59
  |
1 | fn is_short(s: &str) -> bool {
  | ---------------------------- found signature of `for<'r> fn(&'r str) -> _`
...
9 |     let short_names: Vec<&str> = months.split('|').filter(is_short).collect();
  |                                                           ^^^^^^^^ expected signature of `for<'r> fn(&'r &str) -> _`

error[E0599]: no method named `collect` found for struct `Filter<std::str::Split<'_, char>, for<'r> fn(&'r str) -> bool {is_short}>` in the current scope
    --> src/main.rs:9:69
     |
9    |       let short_names: Vec<&str> = months.split('|').filter(is_short).collect();
     |                                                                       ^^^^^^^ method not found in `Filter<std::str::Split<'_, char>, for<'r> fn(&'r str) -> bool {is_short}>`
     |
     = note: the method `collect` exists but the following trait bounds were not satisfied:
             `<for<'r> fn(&'r str) -> bool {is_short} as FnOnce<(&&str,)>>::Output = bool`
             which is required by `Filter<std::str::Split<'_, char>, for<'r> fn(&'r str) -> bool {is_short}>: Iterator`
             `for<'r> fn(&'r str) -> bool {is_short}: FnMut<(&&str,)>`
             which is required by `Filter<std::str::Split<'_, char>, for<'r> fn(&'r str) -> bool {is_short}>: Iterator`
             `Filter<std::str::Split<'_, char>, for<'r> fn(&'r str) -> bool {is_short}>: Iterator`
             which is required by `&mut Filter<std::str::Split<'_, char>, for<'r> fn(&'r str) -> bool {is_short}>: Iterator`

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0599, E0631.
For more information about an error, try `rustc --explain E0599`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

Your is_short function has the wrong type signature. If you redefine it to take a double reference, the code will work:

fn is_short(s: &&str) -> bool {
    s.len() <= 5
}

An alternative is to use a closure that calls the function. Because it doesn't capture any variables, it will be a zero-sized type, and therefore as efficient to call as above:

let short_names: Vec<&str> = months.split('|').filter(|s| is_short(s)).collect();

So, what's going on here? months.split() is returning an iterator of &str items. filter() adds an additional reference so that it doesn't consume the item from the iterator, even if it's a non-Copy type, making the function argument &&str. When you use a closure, you don't need to specify that type explicitly, and Deref coercion takes care of the impedance mismatch automatically.

I got this information from this compiler error message:

error[E0631]: type mismatch in function arguments
 --> src/main.rs:8:59
  |
1 | fn is_short(s: &str) -> bool {
  | ---------------------------- found signature of `for<'r> fn(&'r str) -> _`
...
8 |     let short_names: Vec<&str> = months.split('|').filter(is_short).collect();
  |                                                           ^^^^^^^^ expected signature of `for<'r> fn(&'r &str) -> _`

In particular, it complains that it was passed a value of type fn(&'r str) -> _, but it expected to be given a value of type fn(&'r &str) -> _.

3 Likes

In general, if you have a simple type mismatch error, it's worth consulting the official documentation of std – you can see here, for example, the signature of Iterator::filter():

fn filter<P>(self, predicate: P) -> Filter<Self, P> where
    P: FnMut(&Self::Item) -> bool

i.e. for an iterator over Items is filtered using a function that takes an &Item. Therefore, if you have an Iterator<Item=&str>, then your filter predicate needs to take a &&str.

3 Likes