mtvec
mtvec

Reputation: 18316

Trait associated type lifetime and self

I have a struct that wraps a std::cell::Ref and provides access by reference to the underlying value. Something like this:

use std::cell::Ref;

struct IntAccess<'a> {
    i: Ref<'a, i32>,
}

impl IntAccess<'_> {
    fn get(&self) -> &i32 {
        &self.i
    }
}

This works fine. Since I have multiple structs like this, I'd like to define a common trait:

trait Access {
    type Item;
    fn get(&self) -> Self::Item;
}

However, I get into trouble when trying to implement Access for IntAccess:

impl<'a> Access for IntAccess<'a> {
    type Item = &'a i32;

    fn get(&self) -> Self::Item {
        &self.i
    }
}

It fails with the following error:

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src/main.rs:23:9
   |
23 |         &self.i
   |         ^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime defined here...
  --> src/main.rs:22:12
   |
22 |     fn get(&self) -> Self::Item {
   |            ^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:23:9
   |
23 |         &self.i
   |         ^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined here...
  --> src/main.rs:19:6
   |
19 | impl<'a> Access for IntAccess<'a> {
   |      ^^
note: ...so that the types are compatible
  --> src/main.rs:22:33
   |
22 |       fn get(&self) -> Self::Item {
   |  _________________________________^
23 | |         &self.i
24 | |     }
   | |_____^
   = note: expected `<IntAccess<'a> as Access>`
              found `<IntAccess<'_> as Access>`

I think I kind of get what the compiler is trying to tell me: I'm trying to borrow self.i which has an anonymous lifetime unrelated to the lifetime of Self::Item ('a). So what seems to be needed is to somehow tie the lifetime of self in get to the lifetime 'a. Is there a way to do this?

I have to admit that I don't fully grasp the problem. Since I pass the lifetime 'a to the Ref, and Ref internally stores a reference with this lifetime, it seems to me that it should somehow be possible to get a reference with lifetime 'a out of it without having to explicitly tie self to this lifetime as well. So what am I missing here?

Upvotes: 4

Views: 1240

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 70900

Your understanding is correct. About your doubt, let's name the lifetimes for easier debugging:

impl<'a> Access for IntAccess<'a> {
    type Item = &'a i32;

    fn get<'b>(&'b self) -> Self::Item {
        &self.i
    }
}

self has the type &'b IntAccess<'a>. 'b is shorter than or equal to 'a - that is required for self to be well-formed, i.e. able to exist (otherwise, it would contain a dangling reference).

For that reason, we cannot borrow self for more than 'b - or we could do something like:

let v: IntAccess<'a>;
let inner: &'a i32 = {
    let r: &'b IntAccess<'a> = &v;
    <IntAccess<'a> as Access>::get(r)
}
let mut_r: &mut IntAccess<'a> = &mut v;

And we have both a shared and a mutable reference to (parts of) inner!

Your problem cannot be solved fully without Generic Associated Types. They allow you to parameterize an associated type by a lifetime, making it able to express "I want to return this associated type tied to the lifetime of self":

#![feature(generic_associated_types)]

trait Access {
    type Item<'a>
    where
        Self: 'a;
    fn get<'a>(&'a self) -> Self::Item<'a>;
    // Or, with lifetime elision:
    // fn get(&self) -> Self::Item<'_>;
}

impl<'a> Access for IntAccess<'a> {
    type Item<'b> = &'b i32
    where
        'a: 'b;

    fn get<'b>(&'b self) -> Self::Item<'b> {
        &self.i
    }
}

Playground.

Can we do that on stable? We can emulate that. Instead of implementing Access for IntAccess itself, we will implement it for a reference to it!

trait Access {
    type Item;
    fn get(self) -> Self::Item;
}

impl<'a, 'b> Access for &'b IntAccess<'a> {
    type Item = &'b i32;
    fn get(self) -> &'b i32 {
        &self.i
    }
}

Playground

This does not always work, and thus is not a full replacement to GATs, but is good enough in this case.

Upvotes: 4

Related Questions