Rust cannot infer type of closure argument

use itertools::Itertools;
struct Bar<T> {
    t: T
}

trait Foo {
    type Elem;
    fn get(&self) -> Vec<Bar<Self::Elem>>;
}

struct Baz;

impl Foo for Baz {
    type Elem = String;

    fn get(&self) -> Vec<Bar<Self::Elem>> {
        vec!["faz"].into_iter().map_into().map(|mut i| {
            i.clear();
            Bar{t: i}
        }).collect()
    }
}

fn main() {}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0282]: type annotations needed
  --> src/main.rs:17:49
   |
17 |         vec!["faz"].into_iter().map_into().map(|mut i| {
   |                                                 ^^^^^ consider giving this closure parameter a type
   |
   = note: type must be known at this point

error: aborting due to previous error

For more information about this error, try `rustc --explain E0282`.
error: Could not compile `playground`.

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

The question is why Rust cannot infer type for i?
I suppose type inference algorithm here works as follows:

  1. see that get returns Vec<Bar<String>>, so collect must return exactly that type
  2. map should return Bar<String>
  3. map returns Bar<T=String>, so i has type String
  4. i has type String, so find clear as inherent or trait method
  5. i has type String, so map_into returns Iterator<Item=String>

Obviously, it is just illustrative example, the point here is that in map closure I mutate argument and assign it after mutation to field of result struct. Note also, that I don't pass element of collection directly to map, there is some return-type polymorphic operation (call to into in map_into) before.

Just trading .map_into() for .map(|a| a.to_string()) clarifies the situation for compiler. So, it seems only the last of the five mentioned steps which is out-of-chain. Maybe the compiler tries it from the other end, resulting in two infers chains (one for steps 1-4 and the other for step 5), which collide somewhere at map_into result (that is at Into<??>)?

If I comment out i.clear(), then compiler becomes happy, but I cannot understand why that expression breaks type inference

Yes.
Even w/o i.clear(); - Bar{t: i.clone()) results in the same. Seems using any trait of i makes the job too big, for simple let j=i; are ok.

But clear comes from String type itself, not some trait implemented for String

Rust just refuses to allow method calls on unknown types, since it cannot reason about its arguments and return types.

Here this wouldn't matter since there are no arguments and the return type isn't used, but it's probably not worth the effort special-casing this in the compiler.

1 Like

Oh, it clarifies situation, but is it just restriction of current compiler implementation, or there are some theoretical causes that prevent compiler from doing that? I think it could be possible to postpone functions calls typecheck until arguments are fully inferred based on just expressions that are simple enought and return types, and then return to functions calls typecheck.

(Not a compiler dev but my reasoning, hopefully about right.)

|mut i| { Bar{t: i} }

is evaluated as generic of for<T> fn(mut i: T) -> Bar<T>, then infer type.

Add in i.clear(); You can't evaluate until you know more but inference is later step.

Note if you use UFCS String::clear(&mut i); the type becomes known. Even like @a-coostic code using Clone::clone(&i) you gain a where T:Clone on the generic.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.