Is this a known issue/limitation in associated types?

I tried to make this example as minimal as I could. It seems very clear that within the impl Property for MyProperty<T>, Self::State should always be the same type as MyState, but the compiler doesn't seem to know that.

Reframing the trait as T: HasStateFor<Property> makes the error go away.

use std::marker::PhantomData;

trait CanGetStateFrom<T>: Property {
    fn get_state(input: &mut T) -> &mut Self::State;
}

trait Property {
    type State;
    fn update_state<T>(state_source: &mut T) where Self: CanGetStateFrom<T>;
}

struct MyProperty<T> {
    _phantom: PhantomData<T>,
}

struct MyState {
    field: u32,
}

impl<T> CanGetStateFrom<MyState> for MyProperty<T> {
    fn get_state(input: &mut MyState) -> &mut Self::State {
        input
    }
}

impl<T> Property for MyProperty<T> {
    type State = MyState;
    
    fn update_state<T2>(state_source: &mut T2) where Self: CanGetStateFrom<T2> {
        let state = Self::get_state(state_source);
        state.field += 1;
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0609]: no field `field` on type `&mut <MyProperty<T> as Property>::State`
  --> src/lib.rs:31:15
   |
31 |         state.field += 1;
   |               ^^^^^ unknown field

For more information about this error, try `rustc --explain E0609`.
error: could not compile `playground` (lib) due to 1 previous error

Edit: Here is an alternative that does compile. Please explain in any response how my non-compiling code is different from this compiling version: Rust Playground

The associated function get_state() returns a mutable reference to an associated type, which is not guaranteed to be any specific type and thus does not necessarily have a field field.

You can introduce a new trait and bind the associated type to it so that you can perform the desired operation:

So, why does it work if we rewrite it slightly this way, where what you say still holds, but the compiler does recognize that those types match? Rust Playground

Note that this version also compiles:

This shows that the compiler is aware that the return value is Self::State, just apparently unaware that Self::State is (in the same trait impl) defined to be MyState. Again, changing the trait bounds fixes that connection, so it's something the Rust compiler can be aware of in some circumstances.

Because, in the first example above, you now return the associated type of a specific implementation, namely <MyProperty<T> as Property>, which can only have one explicit associated type State, which is MyState, in its implementation.

Your second example does not assume anything about the returned associated type any longer, since you commented out the field access. And any type can always be assigned to a variable, state in this case.

I believe this is what’s going on: In the body of the update_state implementation, the

Self: CanGetStateFrom<T2>

bound included an implicit supertrait bound

Self: Property

(due to trait CanGetStateFrom<T>: Property)

And local bounds do tend to take precedence. Rust’s trait solver isn’t exactly a super “intelligent” kind of thing… and in particular in light of “redundant” bounds it often prefers one over another. The Self: Property bound in question – which does not further specify the Self::State type – taking precedence over the knowledge that “Self: Property” must hold by the fact that we’re within the very trait impl that defines it – that knowledge would come with knowledge of how Self::State is defined – means that the trait solver here fails to simplify Self::State to MyState.

I haven’t looked too deeply into this second example yet, but it’s certainly the case that it doesn’t feature any Self: … bound which might effectively “shadow” any of the implicit knowledge about Self that comes about from being within a trait impl on Self.

1 Like

OK, regarding my first example, in my original (compilation failed) example, Self::MyState was in an impl block for MyProperty<T> and therefore could also only have referred to <MyProperty<T> as Property>'s impl. I could have equivalently written <MyProperty<T> as Property>::State in my impl block. In fact, the compiler accepts such an impl block as an alternative way of writing the same thing, and then proceeds to reject state.field: Rust Playground So, I don't really understand the distinction you're drawing here.

In my second example, the compiler gains the ability to tell that Self::State and MyState are the same type if we drop the where clause, as demonstrated here, in this compiling version: Rust Playground so, clearly the compiler knows Self::State is the same type as MyState in that impl block as long as we don't include where Self: CanGetStateFrom<MyState>. When we include that constraint, it stops seeing those two types as equivalent. My question is: Why?

Thank you so much! This makes perfect sense and explains everything I've seen! Are there any resources where I could learn more about this?

I'm not aware of any. My knowledge comes mostly from playing around with sufficiently many example programs, much like you've just done when you were comparing several minimal-ish examples.

If you do it too much, you might come up with things/consequences even the compiler developers weren't aware of yet :sweat_smile: - compare e. g.: I finally found the cheat-code for disabling the type-checker /s

In other words, it's yet another case of something like Trait impls from where clauses (`ParamEnv`) take precedence over freestanding trait impls · Issue #24066 · rust-lang/rust · GitHub, it seems?

3 Likes

It could also be one of the many other issues linked from from that issue (which I was going to suggest checking too :slight_smile:).


@jhartzell42 is there situation you're still trying to work around for practical purposes? For example, where you don't control the traits but you're hoping for some workaround? (Since you already seem to have found some workarounds for when you do control the traits.)

This is fun! Uncomment the where clause to get a compiler error!

Do you think this is something the compiler developers are aware of, and do you think it would be worth raising as an issue?

1 Like

I think it’s known behavior, and generally pretty hard to improve anything without breaking something else (i.e. some other use-cases that rely on this precedence rule, and presumably are what motivated it in the first place).

Thank you! I wanted (a) to understand and (b) rules that I could follow so I could feel confident I'd not hit this issue immediately again as I continued to work on this trait-heavy codebase that I have a fair amount of control over. So I've gotten everything I was looking for :slight_smile:

1 Like

In any case, on first glance this issue looks closely related:


This issue comment looks perhaps even closer (featuring a bound on Self in a trait impl; …and is from 2018):

the preview above displays the OP; the linked reply is also a cross-post from this forum, anyway:

1 Like