Why doesn't Rust know the concrete type in this trait impl?

When attempting to compile this code, I get the following error:

error[E0308]: mismatched types
  --> src/lib.rs:72:9
67 |     fn get_states<'a>(&'a self) -> <(Bar, T) as MultiState<'a>>::Refs
   |                                    ---------------------------------- expected `<(Bar, T) as MultiState<'a>>::Refs` because of return type
72 |         (&foo.bar, t)
   |         ^^^^^^^^^^^^^ expected associated type, found tuple
   = note: expected associated type `<(Bar, T) as MultiState<'a>>::Refs`
                        found tuple `(&Bar, &T)`
   = note: consider constraining the associated type `<(Bar, T) as MultiState<'a>>::Refs` to `(&Bar, &T)` or calling a method that returns `<(Bar, T) as MultiState<'a>>::Refs`
   = note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html

Why isn't Rust smart enough to realize that, for all 'a, <(Bar, T) as MultiState<'a>>::Refs is equal to (&'a Bar, &'a T), as I've written? Is there any way that I can coax Rust into realizing that these types are equal?

1 Like

I don't think it's entirely obvious that T: 'a and Bar: 'a for the same 'a. Your concrete impls are only for T: 'a, U: 'a - but your T declared on line 66 in impl<T, ...> MultiStateContext<(Bar, T)> for C { has no such bound. It could be entirely possible that T does not satisfy T: 'a for the 'a in get_states.

Since it the requirement for (Bar, T): MultiState<'a> isn't necessarily fulfilled by your above impl statements (if the lifetimes don't match, then it won't be fulfilled), the compiler can't guarantee that the associated types you get for (Bar, T) come from your above impl statement.

I don't have any good solutions to this, and it looks like you're tackling a hard problem here. Hope this helps clarify exactly what the compiler's complaining about, regardless.

Edit: actually, I do have a suggestion here. If you make the trait MultiStateContext generic over the lifetime 'a, as well as States, then you could potentially get this to work:

pub trait MultiStateContext<'a, States: MultiState<'a>> {
    /// Gets immutable references to all of the states.
    fn get_states(&'a self) -> States::Refs;
    /// Gets mutable references to all of the states.
    fn get_states_mut(&'a mut self) -> States::Muts;

Then the concrete implementation could specify T: 'a for that same 'a, and you'd be automatically using your above implementations.

The only downside is that to allow MultiStateContext to be used with any lifetime, you'd have to specify

where T: for<'a> MultiStateContext<'a, States>

(read: for any lifetime 'a, T satisfies MultiStateContext with 'a) rather than

where T: MultiStateContext<States>

This is a fairly common trick to get out of lifetime troubles like this - if the trait itself is generic over the lifetime, then you can get all the type parameters to work right at the trait level, rather than trying to manage both the trait and the method level. The most commonly used trait which does this is serde::Deserialize.

Thanks for the thorough investigation, @daboross! Your response helped me fix a number of related issues that weren't the core issue.

It turns out that Rust is smart enough to know that the types match if we use bare functions. By embedding bare functions inside of the method implementations and calling them, I got it to compile.

EDIT: I've submitted it as a Rust issue.

1 Like

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