Closure taking ownership for only some variables?

I'm working on a framework to easily write rendering loops and managing windows, the example below should be pretty auto-explanatory on how it should work:

    //Create app and main window.
    let mut app = Nucley::new();
    let window = app.create_window()?;
    let window_view = window.view();

    //Load texture from path.
    let texture = Texture::from_path(window_view, Path::new("examples/draw_cube/UV_1k.jpg"));
    let sampler = Sampler::new_default(window_view);

    //Binder holds textures, matrices and other uniforms.
    let mut binder = Binder::new();
    binder.bind(0, &texture);
    binder.bind(1, &sampler);

    //Create brush to draw the shapes.
    let mut brush = Brush::from_path(
        window.view(),
        Path::new(r#"D:\Development\Rust Crates\LDrawy\examples\shared_assets\basic.wgsl"#),
    )?;
    // Add binder information to the brush
    brush.set_binder(0, binder);

    //Create a shape batch and add a triangle to it.
    let mut batch = ShapeBatch::default();
    batch.add_triangle([
        vertex!(-0.5, -0.5, 0.0, Color::SILVER),
        vertex!(0.5, -0.5, 0.0, Color::SILVER),
        vertex!(0.0, 0.5, 0.0, Color::SILVER),
    ]);

    //Bake batches into GPU buffers.
    let buffer = batch.bake_buffers(window.view())?;

    //Setup the window render loop.
    // TODO: Add app to closure
    window.run(move |wnd| {
        let mut frame = wnd.start_frame(None).expect("Issue creating frame.");
        frame.render(wnd, &mut brush, &buffer);
        frame.finish(wnd).expect("Error finishing frame.");
    });

    // Start program.
    app.start(());

However, I'm getting the following issues:

error[E0597]: `texture` does not live long enough
   --> examples\draw_cube\main.rs:113:20
    |
113 |       binder.bind(0, &texture);
    |                      ^^^^^^^^ borrowed value does not live long enough
...
137 | /     window.run(move |wnd| {
138 | |         let mut frame = wnd.start_frame(None).expect("Issue creating frame.");
139 | |         frame.render(wnd, &mut brush, &buffer);
140 | |         frame.finish(wnd).expect("Error finishing frame.");
141 | |     });
    | |______- argument requires that `texture` is borrowed for `'static`
...
145 |   }
    |   - `texture` dropped here while still borrowed

(And basically the same issue with "sampler")

What I'm not understanding here is, both "buffer" and "brush" are being properly moved to the closure but "texture" and "sampler" are not, and it complains about being dropped while still borrowed (Which they should not be dropped since the ownership should be moved to the closure).

Is there anything about lifetimes or how closures take ownership that I'm not 100% understanding here?

Thanks

I think the problem is that texture lives in the scope of the main() function, and binder borrows a reference to texture; the texture is not moved into the binder. Later, the binder is moved into brush, and brush eventually is moved into the closure. But texture is still owned by the main() function :fearful:

1 Like

Put very tersely, you can't move things that are borrowed.

(I'm guessing that's what's going on with the below.)

    //Binder holds textures, matrices and other uniforms.
    let mut binder = Binder::new();
    binder.bind(0, &texture);
    binder.bind(1, &sampler);

So what I'm understanding is: I can't move "texture" and "sampler" into the closure because binder holds a reference to both of them and this causes main() to still own both of them. But because they cannot be moved into the closure, the compiler gives the error.

Is there not a way to force all variables into the closure? Since binder, texture and sampler are moving into the same place it should be okay even if binder holds a reference to both of them right?

But binder does not own texture and sampler, it only borrowed them; they still are owned by main() function. So, there is no reason why they would move along with binder. It's not allowed to move (transfer ownership) of something that you don't own. You can pass on references, yes, but the lifetime of the referenced item then still is determined by the actual owner. And that's the problem here.

If you can't change binder.bind() to take the second argument by-value rather than by-reference, maybe you could move texture and sampler into the closure first and do the bind()ing there?

1 Like

I see what you mean, so there's no way to force moving the ownership for texture and sampler into the closure? (E.g. using the variables inside the closure to read / write them). If there's not a way I can move the declaration into the closure itself which I did not think about to be honest, thanks!

Even if you could build a closure that held the texture and sampler and the binder borrowing them, it wouldn't help. That would be a self-referential struct, which you can't move (couldn't pass to window.run) for the same underlying reason -- you can't move things that have active references. (As soon as you do, the references dangle... plus other borrow-model related problem.)

Look into changing your bind to take ownership or shared ownership (Arc<dyn Uniform> or something; I can't tell what your API is exactly).

1 Like

What about something like:

window.run(move |wnd| {
	let my_texture = texture; // <-- actually move texture into the closure
	binder.bind(0, &my_texture);
	/* [...] */
});

(provided that you can't change bind() to take the second argument by-value)

2 Likes

I see what you mean. In that case since binder cannot take by value the variables (Since it would require duplicating textures between shaders instead of sharing them).

Sadly I also cannot move it into the closure since it's being executed every frame and does not make sense to bind them all the time when it's only required once. Also the problem with using Arc is I'm not able to modify the texture afterwards.

I'm seeing there's no easier solution other than perhaps storing the uniforms in another user created struct and passing the struct to the closure definition (Like I'm doing with wnd argument).

Also the problem with using Arc is I'm not able to modify the texture afterwards.

If you need to "share" an item and still be able to modify it, try Rc<RefCell<T>> for single-threaded context, or Arc<Mutex<T>> (or Arc<RwLock<T>>) for multi-threaded context.

2 Likes

Indeed using in my case Rc<RefCell> fixed the issue. Now binder takes shared ownership over the Rc for both "texture" and "sampler" and everything works properly. Cheers!

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.