Hello! I am trying to work with softbuffer and faced this wierd lifetime issue when I tried creating separate struct for Buffer and Window:
se std::{num::NonZeroU32, sync::Arc};
use winit::window::Window;
type Surface = softbuffer::Surface<Arc<Window>, Arc<Window>>;
type Buffer<'a> = softbuffer::Buffer<'a, Arc<Window>, Arc<Window>>;
struct Canvas<'a> {
surface: Surface,
window: Window,
buffer: Option<Buffer<'a>>
}
impl<'a> Canvas<'a> {
fn new(surface: Surface, window: Window) -> Self {
Self { surface, window, buffer: None }
}
fn update(&mut self) {
let width = self.window.inner_size().width;
let height = self.window.inner_size().height;
self.surface.resize(NonZeroU32::new(width).expect("Window size must be positive!"), NonZeroU32::new(height).expect("WIndow size must be positive!"));
let buffer: = self.surface.buffer_mut().expect("Failed creating framebuffer");
self.buffer = Some(buffer);
}
}
But compiler says:
[rustc] lifetime may not live long enough assignment requires that `'1` must outlive `'a` [:20] let's call the lifetime of this reference `'1` [:15] lifetime `'a` defined here
I am confused because I can get Buffer and use it in code as long as I don’t drop surface, but I cannot add it to struct which will hold it some time till it will be replaced with next update().
In softbuffer, Surface::buffer_mut() returns a borrow of the Surface. You cannot store a value and a borrow of that value in the same struct — the type system cannot express it, particularly not the requirement that self.surface must remain unmoved and unaltered while self.buffer exists. (I wish Rust did support this pattern, but it doesn’t today.)
There are tools to do it anyway, like ouroboros, and it would be possible to make this work, but if at all possible, you should avoid this code organization instead. Create a Buffer only when you are going to start drawing a frame, and drop it when you finish, and do all this within the scope of one function call.
let buffer = unsafe {
std::mem::transmute::<Buffer<'_>, Buffer<'a>>(buffer)
};
Before creating separate struct I just used render loop where I got buffer on each iteration. So I wanted the same behavior here, but add custom drawing function (eg. draw_pixel). Since AFAIK I can get buffer only once I need to store it inside struct. But why can’t I do this? For me the logic here is the same as just getting buffer to local variable and presenting + dropping it on iteration end.
No, this is unsound. If the Canvas is moved, then so is the Surface, and so any borrows of it or its fields that Buffer may contain are invalidated — and may cause dereference of dangling pointers, which is undefined behavior. (ouroboros works around this by heap-allocating the data which is referred to, so it never moves.)
You don’t need self-reference to do this — instead, make a struct that is just as temporary as buffer.
No, it will still invalidate the borrows, even though that would prevent dangling pointers in the strictest sense. (The reasons why are rather esoteric and under debate; it might change in the future, but it hasn’t changed yet.)
I strongly recommend that you take the approach with two structs. It will save you a lot of trouble.
Rust calls this self-referential struct, and the borrow checking model doesn’t allow this. The borrow checker can’t verify safety of any structs having a shape like that
Additionally, semantics of the always-strictly-exclusive &mut technically mean such struct can’t exist with a usable surface field, because the same buffer data would be accessible via two different fields at the same time, which is a violation of the exclusivity. Forcing this through with unsafe could result in miscompilation caused by incorrect aliasing information