Winit事件循环中WGPU状态引发的App结构体自引用生命周期问题求助
Winit事件循环中WGPU状态引发的App结构体自引用生命周期问题求助
兄弟,我之前踩过完全一样的坑!你遇到的本质是Rust的自引用结构体生命周期冲突:你的App同时持有Window和依赖Window的State,而State里的Surface<'w>绑定了Window的生命周期,编译器没办法保证Window的生命周期能覆盖App的整个生命周期——毕竟你是在resumed函数里用临时引用创建State,然后想把它存到App的字段里,编译器会担心这个临时引用失效后,Surface会指向已经被销毁的Window,所以直接报错了。
给你几个可行的解决方案,按推荐程度排序:
方案一:用Arc<Window>让State持有Window的所有权(最推荐)
这个方法最符合Rust的所有权模型,而且代码改动很小,几乎没有额外开销:
- 先修改
State结构体,去掉生命周期参数,新增一个Arc<Window>字段来持有窗口的所有权:
struct State { window: Arc<Window>, surface: wgpu::Surface, device: wgpu::Device, queue: wgpu::Queue, config: wgpu::SurfaceConfiguration, size: winit::dpi::PhysicalSize<u32>, }
(如果你的wgpu版本还保留Surface的生命周期参数,把它改成wgpu::Surface<'static>就行,因为Arc能保证Window不会被提前销毁,相当于引用是永久有效的)
- 调整
State::new的参数,接受Arc<Window>:
async fn new(window: Arc<Window>) -> State { let size = window.inner_size(); // 后面的instance、surface创建逻辑完全不变,只是用&window来生成surface let surface = instance.create_surface(&window).unwrap(); // ... 其他代码都不用改 Self { window, // 把Arc存到State里 surface, device, queue, config, size, } }
- 简化
App结构体,去掉生命周期参数,只存State即可:
#[derive(Default)] struct App { state: Option<State>, }
- 最后在
resumed函数里创建Arc<Window>并初始化State:
fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window = Arc::new(event_loop.create_window(Window::default_attributes()).unwrap()); let state = pollster::block_on(State::new(window.clone())); self.state = Some(state); }
这样State自己持有Window的Arc,编译器能完全确保Surface引用的Window不会被销毁,生命周期问题直接解决。
方案二:用自引用结构体库(比如ouroboros)
如果不想用Arc,可以用专门处理自引用的库ouroboros,它会帮你封装unsafe逻辑,确保自引用的安全性:
- 先在
Cargo.toml里添加依赖:
ouroboros = "0.15"
- 定义自引用的
AppState结构体:
use ouroboros::self_referencing; #[self_referencing] struct AppState { window: Window, #[borrows(window)] #[covariant] state: State<'this>, }
- 修改
App结构体,存Option<AppState>:
#[derive(Default)] struct App { state: Option<AppState>, }
- 在
resumed里初始化自引用结构体:
fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window = event_loop.create_window(Window::default_attributes()).unwrap(); let app_state = AppStateBuilder { window, state_builder: |window| pollster::block_on(State::new(window)), }.build(); self.state = Some(app_state); }
- 访问
State的时候需要用库提供的方法:
// 比如在RedrawRequested里 self.state.as_mut().unwrap().borrow_state().render();
方案三:用unsafe绕过检查(极度不推荐)
如果你非常确定自己的逻辑不会出现悬垂引用,可以用unsafe强制转换生命周期,但这会完全绕过编译器的安全检查,一旦逻辑出错就会导致未定义行为,只有万不得已才用:
fn resumed(&mut self, event_loop: &ActiveEventLoop) { self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap()); let state = pollster::block_on(State::new(&self.window.as_ref().unwrap())); // 强制把临时生命周期转成App的生命周期 let static_state = unsafe { std::mem::transmute::<State<'_>, State<'w>>(state) }; self.state = Some(static_state); }
我自己当时用的是方案一,改动小还安全,完全没遇到后续问题,你可以试试!
备注:内容来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

