Unexpected "borrowed data escapes outside of closure" for generic

error[E0521]: borrowed data escapes outside of closure
  --> rend3-egui/src/lib.rs:75:25
   |
74 |         builder.build(move |mut ctx| {
   |                             -------
   |                             |
   |                             `ctx` is a reference that is only valid in the closure body
   |                             has type `NodeExecutionContext<'_, '1, '_>`
75 |             let rpass = ctx.encoder_or_pass.take_rpass(rpass_handle);
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                         |
   |                         `ctx` escapes the closure body here
   |                         argument requires that `'1` must outlive `'static`
   |
   = note: requirement occurs because of a mutable reference to `RenderGraphEncoderOrPass<'_, '_>`
   = note: mutable references are invariant over their type parameter
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

This is an unusual variation of a common problem. This code used to work. The project is at

and branch "trunk" will compile. But branch "wgpu22", which is mostly changes to dependencies to upgrade "wgpu", "egui", etc. gets the error above. The code with the compile error is the same in both branches.

It seems to be the upgrade to "egui" which caused the problem.
But, in both versions, Context (the type of ctx) is

pub struct Context(Arc(RwLock<ContextImpl>);

which ought to be trouble-free.

Ideas?

The key thing here is 'static. Type inference is trying to equate this temporary lifetime with 'static somewhere.

It looks like this is happening because egui-wgpu changed the lifetimes in Renderer::render.

Old:

    pub fn render<'rp>(
        &'rp self,
        render_pass: &mut wgpu::RenderPass<'rp>,
        paint_jobs: &'rp [epaint::ClippedPrimitive],
        screen_descriptor: &ScreenDescriptor,
    ) {

New:

    pub fn render(
        &self,
        render_pass: &mut wgpu::RenderPass<'static>,
        paint_jobs: &[epaint::ClippedPrimitive],
        screen_descriptor: &ScreenDescriptor,
    ) {

And they added documentation:

Note that the lifetime of render_pass is 'static which requires a call to [wgpu::RenderPass::forget_lifetime]. This allows users to pass resources that live outside of the callback resources to the render pass. The render pass internally keeps all referenced resources alive as long as necessary. The only consequence of forget_lifetime is that any operation on the parent encoder will cause a runtime error instead of a compile time error.

Source link because docs.rs failed to build.

The function they talk about: RenderPass in wgpu - Rust

Which means the fix will be finding a way to gain ownership of RenderPass, and then calling forget_lifetime.

2 Likes

Ah, so that's what was changed.

Now I know what to look at. It's all a side effect of this pull request

and this discussion:

1 Like

Right. Inference from the code down at .render below the lines in the error message implied a lifetime constraint at take_rpass. The error message doesn't show where the inference comes from. That's whats's wrong. And it will be a major effort to fix.

The rend3 crate has its own homebrew object system, built with closures and enums. The RenderGraphEncoderOrPass enum can contain either an encoder or a render pass struct. The WGPU people changed the lifetime of RenderPass but not Encoder, the two enum options.
That breaks code handling both types via the enum. All the code which does that needs to change.

The change also seems to break the SAFETY assumptions for the unsafe code around rend3-hp/rend3/src/graph/graph.rs at trunk · John-Nagle/rend3-hp · GitHub which depends on the lifetime of RenderPass. There may be other collateral damage.

The original developer of rend3 abandoned the project around when WGPU changed that. He now recommends using Vulkan instead of WGPU.

(I am, unfortunately, several years into a project that uses Rend3/WGPU.)