rusttraitslifetimeborrow-checker

Why don't Deref-like traits compose?


In this code, MyDeref matches the definition of Deref from the standard library, and MemoryView (simplified from a more complicated example) almost does too.

pub trait MyDeref {
    type Target: ?Sized;
    fn deref<'a>(&'a self) -> &'a Self::Target;
}

pub trait MemoryView {
    type T;
    fn at_ref<'a>(&'a self) -> &'a Self::T;
}

I expected these traits to compose, such that I could write a blanket implementation of MemoryView covering any type that dereferences to a MemoryView:

impl<V: MemoryView, T: MyDeref<Target=V>> MemoryView for T {
    type T = V::T;
    fn at_ref<'a>(&'a self) -> &'a Self::T {
        self.deref().at_ref()
    }
}

However, I get a compiler error:

error[E0309]: the parameter type `V` may not live long enough
  --> src/lib.rs:14:9
   |
14 |         self.deref().at_ref()
   |         ^^^^^^^^^^^^ ...so that the type `V` will meet its required lifetime bounds

It seems to be saying that the method signature implies that Self and Self::T live longer than 'a, but not that Self::Target lives longer than a.

I'm struggling to imagine how a Self::Target could have a shorter lifetime than Self. It's not possible for the obvious Deref types, such as &'a Target and Box<Target>.

I'm also struggling to write some kind of trait bound that says that Self::Target lives longer than Self.

This blanket implementation feels like it should be possible. Help!


Solution

  • This is one of those cases where you're doing something wrong in such a way that the compiler is so confused that it suggests you do something that doesn't help solve the underlying problem.

    You can make this error go away with (otherwise unnecessary) lifetime GATs, which then allows the compiler to reveal the real problem: V is not constrained in the MemoryView for T implementation. Because V isn't constrained, the compiler can't really see how that relates to the other lifetimes to infer an appropriate lifetime for V... and even if you could fix that, you can't have an unconstrained generic type parameter anyway so it's a moot point.

    The solution is to remove the V parameter entirely. The only reason you're using it here is so you can require that T::Target implements MemoryView -- so just add that bound instead, and then adjust type T in the implementation accordingly:

    impl<T> MemoryView for T
    where
        T: MyDeref,
        <T as MyDeref>::Target: MemoryView,
    {
        type T = <<T as MyDeref>::Target as MemoryView>::T;
    
        fn at_ref<'a>(&'a self) -> &'a Self::T {
            self.deref().at_ref()
        }
    }
    

    (Playground)