Question

Why is a temporary closure inferred to not implement Fn when it should?

I found some weird behavior: the rust compiler erroneously infers Fn* trait implementation of closure when used as a temp instance.

Here's a simple example:

fn takes_fn_map<I, F, A, B>(_: std::iter::Map<I, F>)
where
    F: Fn(A) -> B,
{
}

fn main() {
    let map_temp_f = vec![0].into_iter().map(|x| x + 1);

    let var_f = |x| x + 1;
    let map_var_f = vec![0].into_iter().map(var_f);

    fn fn_f(x: i32) -> i32 {
        x + 1
    }
    let map_fn_f = vec![0].into_iter().map(fn_f);

    takes_fn_map(map_temp_f); // ! Error
    takes_fn_map(map_var_f); // Ok
    takes_fn_map(map_fn_f); // Ok
}

Playground

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnMut`
  --> src/main.rs:8:46
   |
8  |     let map_temp_f = vec![0].into_iter().map(|x| x + 1);
   |                                              ^^^ this closure implements `FnMut`, not `Fn`
...
18 |     takes_fn_map(map_temp_f); // ! Error
   |     ------------ ---------- the requirement to implement `Fn` derives from here
   |     |
   |     required by a bound introduced by this call
   |
note: required by a bound in `takes_fn_map`
  --> src/main.rs:3:8
   |
1  | fn takes_fn_map<I, F, A, B>(_: std::iter::Map<I, F>) 
   |    ------------ required by a bound in this function
2  | where
3  |     F: Fn(A) -> B
   |        ^^^^^^^^^^ required by this bound in `takes_fn_map`

For more information about this error, try `rustc --explain E0525`.

It seems to be related to this issue, but there's no actual answer.

Is this a compiler error or expected behavior?

 3  71  3
1 Jan 1970

Solution

 1

From another issue linked from the one you found, this appears to still be an open issue. And yes, it is a bug; |x| x + 1 should clearly implement Fn(i32) -> i32 regardless.

Trying my best to summarize: the bug appears when the immediate context infers that the closure implements a particular trait (in this case .map() requires FnMut) it doesn't implement other traits (like Fn) even if it could. With the temporary variable, the requirements are inferred a bit later and that somehow makes the difference.

2024-07-10
kmdreko