2
votes

I'd like to do something along the following lines:

trait GetRef<'a> {
    fn get_ref(&self) -> &'a [u8];
}

struct Foo<'a> {
    buf: &'a [u8]
}

impl <'a> GetRef<'a> for Foo<'a> {
    fn get_ref(&self) -> &'a [u8] {
        &self.buf[1..]
    }
}

struct Bar {
    buf: Vec<u8>
}

// this is the part I'm struggling with:
impl <'a> GetRef<'a> for Bar {
    fn get_ref(&'a self) -> &'a [u8] {
        &self.buf[1..]
}

The point of the explicit lifetime variable in the GetRef trait is to allow the return value of get_ref() on a Foo object to outlive the Foo itself, tying the return value's lifetime to that of the lifetime of Foo's buffer.

However, I haven't found a way to implement GetRef for Bar in a way that the compiler accepts. I've tried several variations of the above, but can't seem to find one that works. Is there any there any reason that this fundamentally cannot be done? If not, how can I do this?

2

2 Answers

4
votes

Tying a trait lifetime variable to &self lifetime

Not possible.

Is there any there any reason that this fundamentally cannot be done?

Yes. An owning vector is something different than a borrowed slice. Your trait GetRef only makes sense for things that already represent a “loan” and don't own the slice. For an owning type like Bar you can't safely return a borrowed slice that outlives Self. That's what the borrow checker prevents to avoid dangling pointers.

What you tried to do is to link the lifetime parameter to the lifetime of Self. But the lifetime of Self is not a property of its type. It just depends on the scope this value was defined in. And that's why your approach cannot work.

Another way of looking at it is: In a trait you have to be explicit about whether Self is borrowed by a method and its result or not. You defined the GetRef trait to return something that is not linked to Self w.r.t. lifetimes. So, no borrowing. So, it's not implementable for types that own the data. You can't create a borrowed slice referring to a Vec's elements without borrowing the Vec.

If not, how can I do this?

Depends on what exactly you mean by “this”. If you want to write a “common denominator” trait that can be implemented for both borrowed and owning slices, you have to do it like this:

trait GetRef {
    fn get_ref(&self) -> &[u8];
}

The meaning of this trait is that get_ref borrows Self and returns a kind of “loan” because of the current lifetime elision rules. It's equivalent to the more explicit form

trait GetRef {
    fn get_ref<'s>(&self) -> &'s [u8];
}

It can be implemented for both types now:

impl<'a> GetRef for Foo<'a> {
    fn get_ref(&self) -> &[u8] { &self.buf[1..] }
}

impl GetRef for Bar {
    fn get_ref(&self) -> &[u8] { &self.buf[1..] }
}
0
votes

You could make different lifetimes for &self and result in your trait like that:

trait GetRef<'a, 'b> {
    fn get_ref(&'b self) -> &'a [u8];
}

struct Foo<'a> {
    buf: &'a [u8]
}

impl <'a, 'b> GetRef<'a, 'b> for Foo<'a> {
    fn get_ref(&'b self) -> &'a [u8] {
        &self.buf[1..]
    }
}

struct Bar {
    buf: Vec<u8>
}

// Bar, however, cannot contain anything that outlives itself
impl<'a> GetRef<'a, 'a> for Bar {
    fn get_ref(&'a self) -> &'a [u8] {
        &self.buf[1..]
    }
}


fn main() {
    let a = vec!(1 as u8, 2, 3);
    let b = a.clone();
    let tmp;
    {
        let x = Foo{buf: &a};
        tmp = x.get_ref();
    }
    {
        let y = Bar{buf: b};
        // Bar's buf cannot outlive Bar
        // tmp = y.get_ref();
    }
}