Confusing reborrow needed for trait implementation. Is there a better way?

#1

Hi all,
I recently ran into this tiny problem:

static DUMMY: [u8; 30] = [0; 30];

// Lets assume we have a TraitB that serves content with lifetime 'data
trait TraitB<'data> {
    fn get_some_buffer(self: &Self) -> &'data [u8];
}

// Lets further assume there is a TraitA that can serve TraitBs via a closure
trait TraitA<'data> {
    fn foreach_b(self: &Self, f: &mut FnMut(&TraitB<'data>) -> ()) -> ();
}

// Implementation 1 now implements these traits as some kind of proxy.
// So 'data is, in fact, an external lifetime and the implementation 1 just serves as proxy.

#[allow(dead_code)]
struct Impl1B<'data> {
    data: &'data [u8]
}

#[allow(dead_code)]
struct Impl1A {}

impl<'data> TraitB<'data> for Impl1B<'data> {
    fn get_some_buffer(self: &Self) -> &'data [u8] {
        self.data
    }
}

impl<'data> TraitA<'data> for Impl1A {
    fn foreach_b(self: &Self, f: &mut FnMut(&TraitB<'data>) -> ()) -> () {
        let tmp = Impl1B { data: &DUMMY };
        f(&tmp);
    }
}

// Implementation 2, on the other hand, is the actual owner of the data.
// This works, but has the unpleasent side effect that I always need a borrowed Impl2A.
// (see line 59)

#[allow(dead_code)]
struct Impl2B {
    data: Vec<u8>
}

#[allow(dead_code)]
struct Impl2A {
    bs: Vec<Impl2B>
}

impl<'data> TraitB<'data> for &'data Impl2B {
    fn get_some_buffer(self: &Self) -> &'data [u8] {
        &self.data
    }
}

impl<'data> TraitA<'data> for &'data Impl2A {
    fn foreach_b(self: &Self, f: &mut FnMut(&TraitB<'data>) -> ()) -> () {
        f(&&self.bs[0]);
        // ^ Why do I need this double borrow here?
        // Why can't I just write &self.bs[0]?
        // XXX
    }
}

// XXX
// ... Or is there even a smarter way to implement Impl2?
// ... because Impl3 does not seem to work (see line 82)

#[allow(dead_code)]
struct Impl3B {
    data: Vec<u8>
}

#[allow(dead_code)]
struct Impl3A {
    bs: Vec<Impl3B>
}

impl<'data> TraitB<'data> for Impl3B {
    fn get_some_buffer(self: &Self) -> &'data [u8] {
        // &self.data
        // ^ not allowed as 'data is just unrelated
        // I cannot reference the lifetime of self, can I?
        // "cannot infer an appropriate lifetime for borrow expression due to conflicting requirements"
        &DUMMY
    }
}

impl<'data> TraitA<'data> for Impl3A {
    fn foreach_b(self: &Self, f: &mut FnMut(&TraitB<'data>) -> ()) -> () {
        f(&self.bs[0]);
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.40s

I basically use 2 traits to abstract some data access.
The first trait implementation is not the actual owner of the data, while the second one is.
So while I’m satisfied with Impl1, Impl2 is just not very convenient to use as it is defined on the borrowed struct.
I now asked myself whether it is actually needed to implement the traits on borrowed structs?
Impl3 is not working due to the unrelated lifetime 'data in line 82.

Do I miss something here?
Is there a better way to model something like this?

Thanks

0 Likes

#2

To answer your second question:

fn foo(&self) -> &'other T

In practice it means T is guaranteed to stay valid after self is destroyed (the lifetime attached to trait/struct has to exist at the moment of creating the struct, so it’s always longer than self).
So you can’t own a Vec and promise the Vec's contents will outlive its owner.

Lifetime on the trait is very suspicious. That’s probably not what you want. Try removing it. When Rust insists on a lifetime somewhere, try for<'data> syntax instead.

2 Likes

#3

That’s what I understood.

I actually use the lifetime bound to a trait because in case 1, the trait just serves as a reader.
And I want to be able to reference data of the underlying buffer independent of the reader lifetime.
But then there is also the situation 2 where there is no actual underlying buffer and the reader IS the owner of the data.
So in case 2, all I conceptually want to do is tell the compiler that 'data equals the lifetime of the trait implementation.
(So binding the lifetime to the trait is what i actually want except that the second trait implementation should somehow use “his” lifetime)

With my still fairly incomplete understanding of rust this does not really sound too wrong, does it?
I don’t want to introduce boxes here as I deliberately used closures to keep TraitB on the stack.

So is there a better way to implement the traits on Impl2?
I think for<> wouldn’t work here, would it?

0 Likes

#4

Alright, I now understand why implementing the trait on the borrowed object is indeed the “right” thing.
In my case, it got a lot more intuitive when implementing the trait on an object that explicitly models an immutable version.

struct SealedImpl2B<'data> {
    target: &data Impl2B
}

impl<'data> TraitB<'data> for SealedImpl2B<'data> {
...
0 Likes