rustborrow-checkerwgpu-rswinit

Managing lifetime(s) in Rust: How do I ensure that variable(s) live 'long enough'?


I am attempting to write a rendering engine library in Rust, using wgpu and winit, and have been following along with the 'Learn WGPU' tutorial. Unfortunately, some unsafe code (create_surface) in the wgpu crate has proven quite frustrating to work around!

My Window structure is derived from the State structure (see here) defined in the 'Learn WGPU' tutorial:

// This is pseudo-code.

struct Window<'a> {
    surface: wgpu::Surface<'a>,
    window: &'a winit::window::Window,

    // Other stuff...
}

The lifetime is needed as:

"...the surface contains unsafe references to the window's resources."

This is where my issue begun.

Now, I intend to have a 'multi-window compatible' library, and as such, manage windows using a HashMap, like so:

// This is pseudo-code.

struct App<'a> {
    windows: HashMap<winit::window::WindowId, crate::window::Window<'a>>,

    // Other stuff...
}

Now, originally, I had a function (in App) called create_window that would both create the winit::window::Window and the crate::window::Window (in one function), before storing it in the windows HashMap, like so:

// This is pseudo-code.

fn create_window(&mut self, title: &str, width: u32, height: u32) {
    // Create `winit` window...
    let window = ?;

    let surface = instance.create_surface(window).unwrap();

    self.windows.insert(window.id(), crate::window::Window::new(surface, &window));
}

This implementation (obviously) does not work, as window is dropped at the end of the function, and therefore, the crate::window::Window's reference to it becomes invalid ('temporary value dropped while borrowed').

OK, so I move the creation of the winit::window::Window outside of the create_window function, to ensure that it 'lives long enough', like so:

// This is pseudo-code.

fn add_window(&mut self, window: &'a winit::window::Window) {
    let surface = instance.create_surface(window).unwrap();

    self.windows.insert(window.id(), crate::window::Window::new(surface, &window));
}

I then try to test the add_window function using an example:

// This is pseudo-code.
// This is an example for my library ('examples/window-example/main.rs')...

fn main() {
    let mut app = App::new(ControlFlow::Poll).expect("Failed to create app.");

    // Create `winit` window...
    let window = ?;

    app.add_window(&window).expect("Failed to create window.");

    app.run().expect("Failed to run app.");
}

The problem is, I still get the same error ('window does not live long enough')! But (to my knowledge), window should live as long as the program itself, as it is only dropped at the end of main, and therefore should live long enough to be used by crate::window::Window's (or rather, wgpu's) surface?

I have been trying to work around this issue for quite a while now, but to no avail. I am still learning, so any additional explanation(s) would be greatly appreciated!

Thank you!


Solution

  • Given that you want to manage multiple windows (and, really, even if you didn’t), the practical solution to this borrowing problem is to entirely avoid it. create_surface() does not actually need a reference to the window, and using a reference is problematic in the ways you have found, so don't. You can use a different kind of pointer; in particular, an Arc.

    struct Window {
        surface: wgpu::Surface<'static>,
        window: Arc<winit::window::Window>,
    }
    
    fn create_window(&mut self, title: &str, width: u32, height: u32) {
        // Create `winit` window owned by an `Arc`
        let window = Arc::new(
            event_loop.create_window(Window::default_attributes()).unwrap()
        );
    
        let surface = instance.create_surface(window.clone()).unwrap();
    
        self.windows.insert(window.id(), crate::window::Window::new(surface, window));
    }
    

    The lifetime 'static in the Surface type represents, in this case, the fact that it now does not borrow anything, but instead has shared ownership of the window.