Generic parameters inference

Consdering the following example code (playground):

use std::marker::PhantomData;

struct Data<T>(PhantomData<T>);
pub trait Marker {}
struct Inner;
impl Marker for Inner {}

trait Id {
    type Id;
}
impl<T> Id for T {
    type Id = T;
}

fn create<T: Marker, U: Id<Id = T> + Marker>() -> Data<T> {
    Data(PhantomData)
}
fn create2<T: Marker, U: Id<Id = T> + Marker>() -> Data<U> {
    Data(PhantomData)
}

fn main() {
    let mut data = create::<_, Inner>();
    data = create();
    data = create2();
}

It generates one error:

error[E0282]: type annotations needed
  --> src/main.rs:24:12
   |
24 |     data = create();
   |            ^^^^^^ cannot infer type for `U`

I'm trying to understand two things:

  • why the call to create() is erroneous, while in the call to create2() all parameters are inferred successfully?
  • is this behaviour stable? This main point is that the error is desirable - I don't want this function to be callable without turbofish (since in real project it is a part of macro-generated code, not usable directly).

I don't see a clear logical explanation for this. My hunch is that one of the parameters is known from the return type matching Data<Inner>, and then the other simpler T is "easier" to deduce than U, so T works and U doesn't.

Type inference in Rust doesn't have strong stability guarantees (besides "shouldn't break crater runs too much"). It has changed in the past, and likely will change in the future.

(p.s. not an American) I associate this with Jeopardy!.
create2 is a regular quiz. The compiler knows the type for data and so can match the return U to it.
create on the other hand is being asked to find a U that matches Id<Id=Inner>. By default there can be many.
We know how to invert;

impl<T> Id for T {
    type Id = T;
}

It is harder for software.

2 Likes