Why are type annotations needed?

When I have functions depending on common data, a pattern I often use is:

impl Data {
    fn new() -> Self {
        Self { x: vec![1.; 10] }
    }

    fn f(&self, _x: &[f64], _y: &mut [f64]) {
        /* todo */
    }
    // ...
}

However, if I have a generic function run such as:

trait FDF { /* todo */ }

impl<F> FDF for F where F: FnMut(&[f64], &mut [f64]) {}

fn run(_fdf: impl FDF) { /* todo */ }

the call

let d = Data::new();
run(|x, y| d.f(x, y));

fails to compile. I have to annotate the closure:

let d = Data::new();
run(|x: &[f64], y: &mut [f64]| d.f(x, y));

Any reason why I have to do that? (There is a single possibility for the type of the closure, whence my surprise.)

you need the annotation not because it's ambiguous, but because the type inference fails in this case.

there are many cases where the types seem to be obvious to humans, but the type inference algorithm is not able to figure it out.

in this particular case, it's a known limitation when it comes to inferring the type of closures with higher ranked lifetimes. in fact, you don't need the full type annotation, just the argument types in the "shape" of references should be enough:

run(|x: &_, y: &mut  _| d.f(x, y));

also, this particular case have a simple workaround: if you add the FnMut() bound directly, it serves a "hint" to the type checker and helps the inference, e.g.

fn run(_fdf: impl FDF + FnMut(&[f64], &mut [f64])) { /* todo */ }

alternatively, make the FnMut() trait as the supertrait of FDF would also work:

trait FDF: FnMut(&[f64], &mut [f64]) { /* todo */ }

(But note that this precludes you from implementing FDF for any other type because you can’t impl any of the Fn traits on stable.)

Thanks for your reply. Indeed, the goal of the trait FDF is to be implemented for other types. Great that "shape" annotations are enough—they are much lighter. Do you know whether the next-gen type engine will eventually “solve” this? It doesn't at the moment.

This comes down to method calls being complicated, basically, because of the possibility of inherent methods calls on different nested types.

For example, .checked_abs() on a NonZero might be one of 6 different methods: https://doc.rust-lang.org/std/num/struct.NonZero.html#method.checked_abs. Of course those are all analogous signatures, but the compiler isn't allowed to assume that.

Calling a trait function, especially using the non-method form, is allowed in more places like this because the trait means it knows the signature without needing a particularly-precise receiver type.

This actually comes more to closure inference being quirky, since it has no problem inferring if the FnMut bound is explicit.