Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Expected associated type, found `u32`" when using the lifetime of a parameter as trait parameter in where bound

Tags:

rust

I tried to compile this code (Playground):

trait Family<'a> {
    type Out;
}

struct U32Family;
impl<'a> Family<'a> for U32Family {
    type Out = u32;
}


trait Iterator {
    type Item;
    fn next<'s>(&'s mut self) -> <Self::Item as Family<'s>>::Out
    where
        Self::Item: Family<'s>;
}


struct Foo;
impl Iterator for Foo {
    type Item = U32Family;

    fn next<'s>(&'s mut self) -> <Self::Item as Family<'s>>::Out
    where
        Self::Item: Family<'s>,
    {
        0u32  // <-- in real code, this is somehow calculated
    }
}

But sadly, it results in this error:

error[E0308]: mismatched types
  --> src/main.rs:28:9
   |
24 |     fn next<'s>(&'s mut self) -> <Self::Item as Family<'s>>::Out
   |                                  ------------------------------- expected `<U32Family as Family<'s>>::Out` because of return type
...
28 |         0u32
   |         ^^^^ expected associated type, found u32
   |
   = note: expected type `<U32Family as Family<'s>>::Out`
              found type `u32`

I really don't understand why. Obviously, in this code snippet, <U32Family as Family<'s>>::Out is exactly u32. But Rust seems to think that it's not always the same. Why? And how can I make it compile?

Some notes:

  • There are a bunch of similar situations where a similar error occurs, but I think this is different from everything I've seen so far.
  • I cannot use type Out: for<'a> Family<'a>;. So that's not a workaround that works for me.
  • If I remove the lifetime parameter of Family, everything works.
  • If I replace Family<'s> with Family<'static> in the function signature, everything works.

EDIT: I can work around this problem by adding:

impl U32Family {
    fn from<'a>(v: u32) -> <Self as Family<'a>>::Out {
        v
    }
}

Then I can just say Self::Item::from(0u32) in the body of next(). (Playground)

I think it's clear why the error in next() is gone: U32Family::from always takes u32 as argument. Hardcoded. Never changing. The bigger question about this workaround is: why does the from() method compile fine? So in from() the compiler somehow knows that <Self as Family<'a>>::Out is always u32, but if I try the same in next(), somehow the compiler doesn't understand that <Self::Item as Family<'s>>::Out is u32. Now I'm even more confused.

EDIT2: first, I suspected that specialization is the problem. For example, you might write:

impl Family<'static> for U32Family {
    type Out = char;
}

Then of course, the compiler would be right in assuming that u32 is not always the same as <Self::Item as Family<'s>>::Out for any 's. However, I think this is not the problem.

First of all, impls that can be specialized need to be marked with the default keyword. I did not do that, so I should be able to assume the associated type is in fact u32 (the RFC talks about something very similar). But additionally, specialization based on lifetimes is not allowed.

So by now I tend to think this is a compiler error. But I'd love to get another answer!

like image 867
Lukas Kalbertodt Avatar asked Aug 01 '18 17:08

Lukas Kalbertodt


1 Answers

I think the problem is that it is a "coincidence" that <Self::Item as Family<'s>>::Out is u32 for all 's. The compiler can prove it for any 's you want, but it can't even express the concept that it is true for all 's.

The work-around you have found is the right approach: add a method to U32Family which converts a u32 into a <Self as Family<'a>>::Out. The body of the method is entirely inside the scope of 'a, so the compiler can prove that the conversion is type-correct for that 'a, and therefore that the method is type-correct. Then, at the call-site, you're telling the compiler to use its knowledge about the method.

like image 107
apt1002 Avatar answered Oct 22 '22 17:10

apt1002