Search code examples
rustwgpu-rswinit

Cannot create wgpu surface due to lifetime constraint


I am updating a project to the newest WGPU version which adds a lifetime parameter to wgpu::Surface<'window> that is causing me problems with the trait winit exposes for the window initialization winit::application::ApplicationHandler.

A smaller version of the problem is as follows:

use wgpu::{Dx12Compiler, Gles3MinorVersion, Instance, InstanceDescriptor, InstanceFlags, Surface};
use winit::{
    application::ApplicationHandler,
    event_loop::EventLoop,
    window::{Window, WindowAttributes},
};

struct Application<'a> {
    event_loop: EventLoop<()>,
    window: Option<Window>,
    surface: Option<Surface<'a>>,
}

impl<'a> ApplicationHandler for Application<'a> {
    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
        if self.window.is_none() {
            self.window = Some(
                event_loop
                    .create_window(WindowAttributes::default())
                    .unwrap(),
            );
            let instance_descriptor = InstanceDescriptor {
                backends: wgpu::Backends::VULKAN,
                flags: InstanceFlags::default(),
                dx12_shader_compiler: Dx12Compiler::Fxc,
                gles_minor_version: Gles3MinorVersion::Automatic,
            };
            let instance = Instance::new(instance_descriptor);
            self.surface = Some(
                instance
                    .create_surface(self.window.as_ref().unwrap())
                    .unwrap(),
            );
        }
    }

    fn window_event(
        &mut self,
        event_loop: &winit::event_loop::ActiveEventLoop,
        window_id: winit::window::WindowId,
        event: winit::event::WindowEvent,
    ) {
    }
}

The above code yields lifetime may not live long enough referencing self.surface = Some(...) noting that the implicit lifetime of &mut self is not guaranteed to live as long as 'a

I cannot augment the resumed trait method with lifetime bounds like resumed<'b: 'a>(&'b mut self, ...) so I cannot see a way to reconcile the lifetimes.

I've also tried structuring the types as such

struct Inner<'a> {
    window: Window,
    surface: Option<Surface<'a>>
}

struct Outer<'a> {
    event_loop: EventLoop<()>,
    inner: Option<Inner<'a>>,
}

which lets me rationalize that the lifetime of the window is guaranteed to be as long as the lifetime of the surface, but this obviously doesn't change the compiler error, and adds a problem of circular reference.

So my question is: Given the signature of ApplicationHandler::resumed and the lifetime requirements how do we create a wgpu surface?

The full error message

error: lifetime may not live long enough
  --> src\main.rs:68:13
   |
53 | impl<'a> ApplicationHandler for Application<'a> {
   |      -- lifetime `'a` defined here
54 |     fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
   |                - let's call the lifetime of this reference `'1`
...
68 |             self.surface = Some(
   |             ^^^^^^^^^^^^ assignment requires that `'1` must outlive `'a`

Solution

  • You have correctly observed you have a sort of circular reference ("self-referencing struct"). The fix is to make it non-circular:

             ┌────→ Surface ──┐
    Application               ├─→ Window
             └────────────────┘ 
    

    You do this by using Arc to create shared ownership of the window.

    struct Application {
        window: Option<Arc<Window>>,
        surface: Option<Surface<'static>>,
    }
    
    impl ApplicationHandler for Application {
        fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
            if self.window.is_none() {
                let window = Arc::new(
                    event_loop
                        .create_window(WindowAttributes::default())
                        .unwrap(),
                );
                self.window = Some(window.clone()); // clone to share
                let instance = Instance::new(...);
                self.surface = Some(
                    instance
                        .create_surface(window) // do not use as_ref
                        .unwrap(),
                );
            }
        }
    

    When you pass an Arc<Window> instead of an &'a Window to create_surface(), the returned Surface does not have any lifetime restriction and can be stored as Surface<'static>.

    Note also that, while it's not part of the question you asked, you must not try to store the EventLoop in your Application struct. It's consumed when you start it and you can't hold onto it any more.