Associated type inference

Minimal Example:

pub trait RemoveT<TargetL, List> {
    type AfterRemove;

    fn remove_label(&self) -> Self::AfterRemove;}

impl<TargetL, List, T> RemoveT<TargetL, List> for T {
    type AfterRemove = Nil;

    fn remove_label(&self) -> Self::AfterRemove {
        todo!()}}

// ================================================================================ test

pub struct Foo;
pub struct Nil;

fn main () {
    let x = Vec::<i32>::new();

    let y = (x as RemoveT<Foo, Vec<i32>>).remove_label();
    }
    

Error:

   Compiling playground v0.0.1 (/playground)
warning: trait objects without an explicit `dyn` are deprecated
  --> src/main.rs:20:19
   |
20 |     let y = (x as RemoveT<Foo, Vec<i32>>).remove_label();
   |                   ^^^^^^^^^^^^^^^^^^^^^^ help: use `dyn`: `dyn RemoveT<Foo, Vec<i32>>`
   |
   = note: `#[warn(bare_trait_objects)]` on by default

error[E0191]: the value of the associated type `AfterRemove` (from trait `RemoveT`) must be specified
  --> src/main.rs:20:19
   |
2  |     type AfterRemove;
   |     ----------------- `AfterRemove` defined here
...
20 |     let y = (x as RemoveT<Foo, Vec<i32>>).remove_label();
   |                   ^^^^^^^^^^^^^^^^^^^^^^ help: specify the associated type: `RemoveT<Foo, Vec<i32>, AfterRemove = Type>`

error[E0620]: cast to unsized type: `Vec<i32>` as `dyn RemoveT<Foo, Vec<i32>>`
  --> src/main.rs:20:13
   |
20 |     let y = (x as RemoveT<Foo, Vec<i32>>).remove_label();
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
help: consider using a box or reference as appropriate
  --> src/main.rs:20:14
   |
20 |     let y = (x as RemoveT<Foo, Vec<i32>>).remove_label();
   |              ^

error: aborting due to 2 previous errors; 1 warning emitted

Some errors have detailed explanations: E0191, E0620.
For more information about an error, try `rustc --explain E0191`.
error: could not compile `playground`

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

Question: Why does this error? It is "clear" to me that we should have AfterRemove = Nil

If you callt like this, the type inference works properly:

fn main () {
    let x = Vec::<i32>::new();

    let y = RemoveT::<Foo, Vec<i32>>::remove_label(&x);
}

Trait objects always have to list associated types explicitly: The identity of the underlying type is lost when the trait object is generated, so it’s no longer possible to look up the associated types from the impl block.

2 Likes
  1. Repeated application of this apparently solved my problem.

  2. I have no idea why this worked. Can you please explain how, from a type inference perspective,

works, while what I wrote fails ?

To me, these two lines after some AST simplification, are identical.

Yet the compiler accepts yours and rejects mine. What is going on inside the compiler ?

as RemoveT<Foo, Vec<i32>> is a cast, and the RemoveT<Foo, Vec<i32>> is interpreted as a type, not a trait. The only way to interpret that as a type is as a trait object, and that's why the compiler is complaining it's missing the dyn keyword and that it is unsized. Calling a method that involves an associated type on a trait object also requires that the associated type is specified. It can't be inferred because it would change the type since dyn RemoveT<Foo, Vec<i32>> is not the same as dyn RemoveT<Foo, Vec<i32>, AfterRemove=Nil>.

Edit: To be clear, in expr as RemoveT<Foo, Vec<i32>> the as means a cast but when you do something like <Vec<i32> as RemoveT<Foo, Vec<i32>>>::remove_label(&x) then RemoveT<Foo, Vec<i32>> is interpreted as a trait and used to disambiguate the method call.

3 Likes

How should I read this statement?

It says "I want to call the remove_label function from the impl RemoveT<Foo, Vec<i32>> for Vec<i32> block with &x as its only argument".

Thanks for your detailed response. I think what really threw me off is:

  1. in most of my code, I rarely write <value as T>... ; instead I write use T; value. ...

  2. Due to reading lots of trait programming code, I have been reading/writing <Type as T>::... all over the place.

  3. As a result, I got confused and wrote <x as RemoveT<...>>... which I normally would not write.

Thanks again for detailed explainations!