Lifetime unintentionally bound to temporary struct

I tried to compile the following code:

struct ResponseBufferInAnotherModule<'res> {
    buf: Box<[u8]>,
    some_data: &'res str,
}

impl<'res> ResponseBufferInAnotherModule<'res> {
    fn set_bool(&mut self, v: bool) {
        self.buf[1] = if v { 0x01 } else { 0x00 };
    }

    fn finish(&mut self) -> &[u8] {
        &self.buf[1..2]
    }
}

struct ResponseSerializer<'res> {
    buffer: ResponseBufferInAnotherModule<'res>,
}

impl<'bld, 'res> ResponseSerializer<'res> {
    fn create_response_builder(&'res mut self) -> MyResponseBuilder<'bld, 'res> {
        MyResponseBuilder {
            buffer: &mut self.buffer,
        }
    }
}

struct Response<'res> {
    data: &'res [u8],
}

struct MyResponseBuilder<'bld, 'res> {
    buffer: &'bld mut ResponseBufferInAnotherModule<'res>,
}

impl<'bld, 'res> MyResponseBuilder<'bld, 'res> {
    fn build_with_bool(&'bld mut self, v: bool) -> Response<'res> {
        self.buffer.set_bool(v);
        Response {
            data: self.buffer.finish(),
        }
    }
}

fn process<'res>(serializer: &'res mut ResponseSerializer<'res>) -> Response<'res> {
    let mut builder = serializer.create_response_builder();
    builder.build_with_bool(true)
}

But I got the following error:

error: lifetime may not live long enough
  --> src/main.rs:39:9
   |
36 |   impl<'bld, 'res> MyResponseBuilder<'bld, 'res> {
   |        ----  ---- lifetime `'res` defined here
   |        |
   |        lifetime `'bld` defined here
...
39 | /         Response {
40 | |             data: self.buffer.finish(),
41 | |         }
   | |_________^ method was supposed to return data with lifetime `'res` but it is returning data with lifetime `'bld`
   |
   = help: consider adding the following bound: `'bld: 'res`

error: could not compile `playground` (bin "playground") due to 1 previous error

Rust Playground

In this program, I tried to build a response using a temporary builder named MyResponseBuilder. The generated response itself is a reference to a byte slice owned by ResponseBufferInAnotherModule. Since the response and the builder require different lifetimes, I named them as 'res and 'bld respectively. The error message advises adding 'bld: 'res, but I want 'bld to be shorter than 'res.

How can I compile this code?

You can't return a lifetime shorter than 'bld from a method that accepts &'bld mut self. See &'a mut self and Self aliasing more generally - Learning Rust (quinedot.github.io) and After NLL: Moving from borrowed data and the sentinel pattern · baby steps (smallcultfollowing.com) for contextual information.

That means, 'res and 'bld are the same lifetime.

I don't have a good answer for how to fix the code, because I don't know the intention of the code.

1 Like

Something that might work (?) is removing the intermediate MyResponseBuilder struct. Implement build_with_bool directly on ResponseSerializer: Rust Playground (rust-lang.org)

This is my best guess at something that can still fit your intended API constraints while being expressible in Rust. The problem with this is that once you call process once, you can't use the input ResponseSerializer ever again. This is what the links above are describing.

Im addition to the "borrowed forever" problems (which may be fixable), this can't work as written:

because the Response<'_> borrows the local MyResponseBuilder<'_, '_>. Sometimes things like that are possible when reborrowing references directly, but they are not possible when going through struct-borrowing methods like you have.

Here are some changes that compile, but only make sense if finish being called twice is okay.

(On mobile I might have missed something; untested beyond compiling.)

Even if it works, there's a good chance some larger refactor would be a better choice.

ResponseBufferInAnotherModule is a state-aware struct and consists of methods which panic when its state is incorrect. The responsibility of MyResponseBuilder is to provide safer interface for process() by encapsulating state-related complexity. My intention of this design is to hide the complexity of ResponseBufferInAnotherModule from process() by introducing MyResponseBuilder.

The correct way might be to pass around ResponseBufferInAnotherModule instead of referencing it:

use std::mem;

struct ResponseBufferInAnotherModule<'res> {
    buf: Box<[u8]>,
    some_data: &'res str,
}

impl<'res> ResponseBufferInAnotherModule<'res> {
    fn set_bool(&mut self, v: bool) {
        self.buf[1] = if v { 0x01 } else { 0x00 };
    }

    fn finish(&mut self) -> &[u8] {
        &self.buf[1..2]
    }
}

struct ResponseSerializer<'res> {
    buffer: Option<ResponseBufferInAnotherModule<'res>>,
}

impl<'res> ResponseSerializer<'res> {
    fn create_response_builder(mut self) -> MyResponseBuilder<'res> {
        let buffer = mem::replace(&mut self.buffer, None);
        MyResponseBuilder { buffer }
    }
}

struct Response<'res> {
    buffer: ResponseBufferInAnotherModule<'res>,
}

impl<'res> Response<'res> {
    fn finish(&mut self) -> &[u8] {
        self.buffer.finish()
    }
}

struct MyResponseBuilder<'res> {
    buffer: Option<ResponseBufferInAnotherModule<'res>>,
}

impl<'res> MyResponseBuilder<'res> {
    fn build_with_bool(self, v: bool) -> Response<'res> {
        let mut b = self.buffer.unwrap();
        b.set_bool(v);
        Response { buffer: b }
    }
}

fn process<'res>(serializer: ResponseSerializer<'res>) -> Response<'res> {
    let builder = serializer.create_response_builder();
    builder.build_with_bool(true)
}

This way, Response contains correctly set ResponseBufferInAnotherModule value. Caller of process() can get the generated byte slice by calling finish() method or reuse ResponseBufferInAnotherModule for next tasks.

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.