5

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.

neon64
  • 1,486
  • 2
  • 12
  • 13
  • 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 Jul 24 '15 at 09:20
  • 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. Jul 24 '15 at 10:36
  • Here's as far as I got before giving up: http://is.gd/khBtaO – DK. Jul 24 '15 at 10:38
  • 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 Jul 24 '15 at 22:40
  • 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 Jul 24 '15 at 22:42

1 Answers1

0

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thank you so much that is exactly what I'm looking for! I did think it was an issue with the lifetime of the reference to the datasource needing to be different to the data it points at but I didn't know how to implement it. Specifically: I didn't know about the `'a: 'b` syntax. I'm assuming that declares that `'a` must be greater or equal to `'b`? – neon64 Jul 25 '15 at 03:15
  • @neon64 Glad to help, although I'm still upset I can't make a smaller example to demonstrate the core problem. [This question succinctly describes the lifetime syntax in question](http://stackoverflow.com/q/30768063/155423). – Shepmaster Jul 25 '15 at 03:18