5
votes

I've been pulling my hair out over the last week due to this incredibly annoying issue with lifetimes.

The problem occurs when I try to put a reference to a Buffer inside a DataSource, which is then referenced to a DrawCommand. I'm getting the error: vertex_data_source does not live long enough.

src/main.rs:65:23: 65:41 error: 
src/main.rs:65         data_source: &vertex_data_source
                                     ^~~~~~~~~~~~~~~~~~
src/main.rs:60:51: 67:2 note: reference must be valid for the block suffix following statement 3 at 60:50...
src/main.rs:60     let vertices = VertexAttributes::new(&buffer);
src/main.rs:61
src/main.rs:62     let vertex_data_source = factory.create_data_source(vertices);
src/main.rs:63
src/main.rs:64     let command: DrawCommand<ResourcesImpl> = DrawCommand {
src/main.rs:65         data_source: &vertex_data_source
               ...
src/main.rs:62:67: 67:2 note: ...but borrowed value is only valid for the block suffix following statement 4 at 62:66
src/main.rs:62     let vertex_data_source = factory.create_data_source(vertices);
src/main.rs:63
src/main.rs:64     let command: DrawCommand<ResourcesImpl> = DrawCommand {
src/main.rs:65         data_source: &vertex_data_source
src/main.rs:66     };
src/main.rs:67 }

It says vertex_data_source has to be valid for the block suffix following statement 3 at line 60. My interpretation of that error is that vertex_data_source should be defined before line 60. But to create the vertex_data_source in the first place I need access to those VertexAttributes on line 60, so I can't just swap the order around.

I feel like all the 'a lifetimes sprinkled over my code need to be split into 2 or maybe just removed, however I've tried every combination that seemed sensible and I'm not out of ideas.

Below is a greatly simplified example of my code that demonstrates the problem. I would really appreciate a sanity check and hopefully a fresh mind might be able to spot the issue. (every time before a few days of fiddling has produced a fix but this time I'm stumped).

use std::cell::RefCell;
use std::marker::PhantomData;

pub struct DrawCommand<'a, R: Resources<'a>> {
    pub data_source: &'a R::DataSource
}

pub trait Resources<'a> {
    type DataSource: 'a;
    type Buffer: 'a;
}

pub struct DataSource<'a> {
    id: u32,
    attributes: Vec<VertexAttributes<'a, ResourcesImpl<'a>>>,
    current_element_array_buffer_binding: RefCell<Option<Buffer<'a>>>
}

pub struct Buffer<'a> {
    context: &'a GraphicsContextImpl
}

pub struct GraphicsContextImpl;

pub struct ResourcesImpl<'a> {
    phantom: PhantomData<&'a u32> // 'a is the lifetime of the context reference
}

impl<'a> Resources<'a> for ResourcesImpl<'a> {
    type Buffer = Buffer<'a>;
    type DataSource = DataSource<'a>;
}

struct Factory<'a> {
    context: &'a GraphicsContextImpl
}

impl<'a> Factory<'a> {
    /// Creates a buffer
    fn create_buffer<T>(&self) -> Buffer<'a> {
        Buffer {
            context: self.context
        }
    }

    fn create_data_source(&self, attributes: Vec<VertexAttributes<'a, ResourcesImpl<'a>>>) -> DataSource<'a> {
        DataSource {
            id: 0,
            attributes: attributes,
            current_element_array_buffer_binding: RefCell::new(None)
        }
    }
}

fn main() {
    let context = GraphicsContextImpl;
    let factory = Factory {
        context: &context
    };
    let buffer = factory.create_buffer::<u32>();

    let vertices = VertexAttributes::new(&buffer);

    let vertex_data_source = factory.create_data_source(vec!(vertices));

    let command: DrawCommand<ResourcesImpl> = DrawCommand {
        data_source: &vertex_data_source
    };
}

pub struct VertexAttributes<'a, R: Resources<'a>> {
    pub buffer: &'a R::Buffer,
}

impl<'a, R: Resources<'a>> VertexAttributes<'a, R> {
    pub fn new(buffer: &'a R::Buffer) -> VertexAttributes<'a, R> {
        VertexAttributes {
            buffer: buffer
        }
    }
}

Thanks very much in advance.

EDIT:

I've updated the code to better reflect my actual implementation.

By the way - replacing this:

let vertex_data_source = factory.create_data_source(vec!(vertices));

With this:

let vertex_data_source = DataSource {
    id: 0,
    attributes: vec!(vertices),
    current_element_array_buffer_binding: RefCell::new(None)
};

Doesn't solve the issue.

1
This is a pretty long shot, but could it be that since you're moving vertices into create_data_source, and returning with the same lifetime, that lifetime is already over when the function return? - MartinHaTh
I've tried to work this out, but you've simplified the code to such a degree that I can't tell what's going on. The solution is "chop out a heap of seemingly pointless code", but that's unlikely to be what you want. For example, why is attributes passed to create_data_source when it's never used? Why is create_data_source a method on Factory when it never refers to it? Why is 'a used in that method despite involving nothing with that lifetime? I can tell you why you're getting that error, but I can't begin to suggest how to fix it without a clearer idea of what you're doing... - DK.
Here's as far as I got before giving up: is.gd/khBtaO - DK.
Thanks @MartinHaTh that did actually work, but only with the simplified example. I've now updated my test code to actually store the attributes in the DataSource. - neon64
Thanks very much @DK. for the detailed analysis. I've update the code to better reflect what I'm trying to do (now attributes is actually used). create_data_source is a method on a factory because in my actual implementation there will be many different factories (OpenGL factory, DirectX factory etc.). However even if I just create the vertex_data_source outside of the factory the issue still persists so I think the true problem lies in the lifetimes of the DataSource struct itself. - neon64

1 Answers

0
votes

This allows your example to compile:

pub struct DrawCommand<'a : 'b, 'b, R: Resources<'a>> {
    pub data_source: &'b R::DataSource
}

However, I'm finding it extremely difficult to create a more minimal example. As best I can determine, you have an issue because you are declaring that you will hold a reference to an item that itself has a reference, and that those two references need to have a common lifetime ('a). Through some combination of the other lifetimes, this is actually impossible.

Adding a second lifetime allows the reference to the DataSource to differ from the reference of the DataSource itself.

I'm still going to try to create a more minified example.