Type annotation needed for closure argument under specific circumstances

I would like to understand why I must annotate a type for x here, and why this happens only in a very specific scenario. The type annotation isn't needed if I …

  • use Fn(i32) instead of the alias MyFn,
  • use an operator instead of making a method call, or
  • use a Box<dyn …>.

Why?

trait MyFn: Fn(i32) {}
impl<T: ?Sized> MyFn for T where T: Fn(i32) {}

fn takes_closure<F: MyFn>(_closure: F) {}
fn takes_boxed_closure(_closure: Box<dyn MyFn>) {}

fn takes_closure2<F: Fn(i32)>(_closure: F) {}

fn main() {
    takes_closure(|x| drop(x.abs())); // why is type annotation needed here?
    takes_boxed_closure(Box::new(|x| drop(x.abs()))); // but not here?
    takes_closure2(|x| drop(x.abs())); // and this works too, but why?
    takes_closure(|x| drop(x + x)); // also this works!?
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0282]: type annotations needed
  --> src/main.rs:10:20
   |
10 |     takes_closure(|x| drop(x.abs())); // why is type annotation needed here?
   |                    ^
   |
   = note: type must be known at this point
help: consider giving this closure parameter an explicit type
   |
10 |     takes_closure(|x: _| drop(x.abs())); // why is type annotation needed here?
   |                     +++

For more information about this error, try `rustc --explain E0282`.
error: could not compile `playground` due to previous error


I tried to use #![feature(trait_alias)], and it exhibits the same problem:

-trait MyFn: Fn(i32) {}
-impl<T: ?Sized> MyFn for T where T: Fn(i32) {}
+#![feature(trait_alias)]
+
+trait MyFn = Fn(i32);

(Nightly Playground)

Maybe this is a bug then?

Type inference for closures consists of bunch of special-cases. I'm not sure, but I think what happens here is that take_boxed_closure() and takes_closure2() both hit the special cases and thus the argument's type is inferred, The second takes_closure() is inferred to be i32 later but this does not matter as nothing requires this information early, but with the first we need this type information immediately (because calling a method requires knowing the exact type) but we don't know it because at most we can infer it by applying constraints from takes_closure() late in the process.

I don't understand what you mean. How is it inferred to be i32 later?


See my update on trying to use trait aliases. I think these two should behave identical:

trait MyFn = Fn(i32);
fn takes_closure<F: MyFn>(_closure: F) {}
fn takes_closure2<F: Fn(i32)>(_closure: F) {}

But they do not.

Maybe the implementation is too specific to "recognize" what the trait alias implies?

I feel like this should be reported as a bug, at least regarding the trait alias feature.