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!
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.