0

I am practicing using closures in rust, but the second of these 2 codes does not work... when I extract the closure, the compiler tells me that some traits are not satisfied

this code work

let invalids = s.chars().filter(|c| c == &'a').count();

but not this one

let is_invalid = |c| c == &'a';
let invalids = s.chars().filter(is_invalid).count();

this gives me the following error

error[E0599]: the method `count` exists for struct `Filter<Chars<'_>, [closure@src/main.rs:4:24: 4:48]>`, but its trait bounds were not satisfied
  --> src/main.rs:5:63
   |
4  |   let is_valid_color = |c| c < &'a' && c > &'m';
   |                        ------------------------
   |                        |
   |                        doesn't satisfy `<_ as FnOnce<(&char,)>>::Output = bool`
   |                        doesn't satisfy `_: FnMut<(&char,)>`
5  |   let invalid_colors_count = s.chars().filter(is_valid_color).count();
   |                                                               ^^^^^ method cannot be called on `Filter<Chars<'_>, [closure@src/main.rs:4:24: 4:48]>` due to unsatisfied trait bounds
   |
  ::: /home/ryokugin/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/adapters/filter.rs:15:1
   |
15 | pub struct Filter<I, P> {
   | ----------------------- doesn't satisfy `_: Iterator`
   |
   = note: the following trait bounds were not satisfied:
           `<[closure@src/main.rs:4:24: 4:48] as FnOnce<(&char,)>>::Output = bool`
           which is required by `Filter<Chars<'_>, [closure@src/main.rs:4:24: 4:48]>: Iterator`
           `[closure@src/main.rs:4:24: 4:48]: FnMut<(&char,)>`
           which is required by `Filter<Chars<'_>, [closure@src/main.rs:4:24: 4:48]>: Iterator`
           `Filter<Chars<'_>, [closure@src/main.rs:4:24: 4:48]>: Iterator`
           which is required by `&mut Filter<Chars<'_>, [closure@src/main.rs:4:24: 4:48]>: Iterator`

Thanks :)

Ryokugin
  • 93
  • 7

2 Answers2

3

The answer from @Enselic is correct, but here I'll explain why the error message is so strange.

The method count() exists on the Iterator trait. That means that as long as a type implement Iterator it'll have .count().

Iterator::filter() returns the Filter type. It implements Iterator... conditionally:

impl<I: Iterator, P> Iterator for Filter<I, P>
where
    P: FnMut(&I::Item) -> bool,

This is desugared as (see How does for<> syntax differ from a regular lifetime bound?):

impl<I: Iterator, P> Iterator for Filter<I, P>
where
    P: for<'a> FnMut(&'a I::Item) -> bool,

If we remove the .count()...

let is_invalid = |c| c == &'a';
let invalids = s.chars().filter(is_invalid);

...the compiler hints us:

error: implementation of `FnOnce` is not general enough
 --> src/main.rs:4:30
  |
4 |     let invalids = s.chars().filter(is_invalid);
  |                              ^^^^^^ implementation of `FnOnce` is not general enough
  |
  = note: closure with signature `fn(&'2 char) -> bool` must implement `FnOnce<(&'1 char,)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 char,)>`, for some specific lifetime `'2`

So, instead of a closure that implements for<'a> FnOnce(&'a char) -> bool, we wrote a closure that implements FnOnce(&'a char) -> bool for some lifetime 'a that the compiler inferred.

This is because Rust fails to infer HRTB (Higher-Ranked Trait Bounds, for clause) for closures. See e.g. issue 50973.

Because of that, the Filter instance that filter() returns does not implement Iterator.

For some reason, rustc prefers to show the "traits bounds were not satisfied" error rather than the error for filter(), "implementation is not general enough". I don't know why it does not shows at least both.

If we hint rustc that the closure takes a reference, i.e. |c: &_| ... (and of course |c: &char| ... works too), it infers the HRTB correctly, and thus the error is gone.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
1

You need to assist with type info as Rust can not quite figure it out by itself in this case. The following works, where the type of the closure parameter is explicit:

    let is_invalid = |c: &char| c == &'a';
    let invalids = s.chars().filter(is_invalid).count();
Enselic
  • 4,434
  • 2
  • 30
  • 42