Unable expressing correct lifetimes using AsRef

I have a container Frame, for example the following:

pub struct Frame<T: AsRef<[u8]>> {
    inner: T,
}

and it has the following functions:

impl<T: AsRef<[u8]>> Frame<T> {
    pub fn new_unchecked(frame: T) -> Self {
        Frame { inner: frame }
    }
    
    pub fn payload(&self) -> &'_ [u8] {
        self.inner.as_ref()
    }
}

I also have a struct that is a more higher level representation of the Frame:

pub struct Repr<'frame> {
    payload: &'frame [u8],
}

The representation is created using the following function:

impl<'frame> Repr<'frame> {
    pub fn parse<T: AsRef<[u8]>>(frame: &Frame<T>) -> Self {
        let payload = frame.payload();
        Repr {
            payload
        }
    }
}

However this does not compile because an explicit lifetime is required for Frame.

When I change the implementation of Frame, for accessing the payload, to the following:

impl<'f, T: AsRef<[u8]> + ?Sized> Frame<&'f T> {
    pub fn payload(&self) -> &'f [u8] {
        self.inner.as_ref()
    }
}

and define the parse function of Repr as:

impl<'frame> Repr<'frame> {
    pub fn parse<T: AsRef<[u8]> + ?Sized>(frame: &Frame<&'frame T>) -> Repr<'frame> {
        let payload = frame.payload();
        Repr { payload }
    }
}

then the compiler is happy!

However, now it is only possible to call parse with Frame that is created using an immutable slice. There is no implementation for a mutable slice now. However, sometimes I need a frame that is created with a mutable slice, because I need to decrypt things in place.

I have totally no idea how to handle this. How do you create a parse function that is still generic over T: AsRef<[u8]> and can accept a mutable and immutable slice?

This is when the compiler is happy (however only accepts immutable slices etc.).
This is when the compiler is not happy.

When the compiler said

help: add explicit lifetime `'frame` to the type of `frame`:

It meant to add a lifetime to the parameter named frame (not the type called Frame), and gave the example:

 `&'frame Frame<T>`

And making this change does work.

 impl<'frame> Repr<'frame> {
-    pub fn parse<T: AsRef<[u8]>>(frame: &Frame<T>) -> Repr<'frame> {
+    pub fn parse<T: AsRef<[u8]>>(frame: &'frame Frame<T>) -> Repr<'frame> {
         let payload = frame.payload();

Without the change, someone could call

let _ = Repr<'static>::parse(&not_a_static_borrow);

and you will have promised to make a Repr<'static> out of their non-'staticly borrowed Frame.

3 Likes

Thank you! I tried that as well and in some cases it helps.
However, in this case I'm not sure what to do.

Because for me, Frame is just a temporary container and is not really needed for creating a longer lived Repr.

Well, there you're trying to smuggle an inner lifetime out of the generic type T. Which made me think maybe you did just want Frame to be a thinner wrapper around &[u8].

However, in your OP you said you need this to work for mutable slices sometimes. So you're really trying to abstract over &[u8] and &mut [u8].

And indeed, this can be challenging or impossible. For example the reason this works:

impl<'f, T: AsRef<[u8]> + ?Sized> Frame<&'f T> {
    pub fn payload(&self) -> &'f [u8] {
        // Do some calculations to calculate the start and end of the payload
        self.inner.as_ref()
    }
}

is that shared references implement Copy, so you can get the &'f T out from behind &'anon self and return a &'f [u8]. But &mut is explicitly not Copy nor Clone. There's no sound way to smuggle a &'long U out from a &'short &'long mut U.

The great limitations in abstracting over & and &mut is generally why there's an Iter and an IterMut, AsRef and AsMut, etc.


Do you need to keep the Frame around after creating the Repr? Changing payload to consume self will work around the nested references and returning of borrowed values. Playground.

It's not possible to make payload consuming. I guess that when the Frame is using a mutable slice I will first deconstruct it after the mutating operations have been done on the frame. Then I can construct a new Frame with an immutable slice that I will pass to the parse function. I think that this is the best option for now in my case. Thanks for the help!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.