Rust中如何用闭包为SDL2游戏更新回调传递数据?
Rust SDL2游戏开发:如何传递数据给update回调
问题背景
我是Rust和底层编程新手,正在用SDL2开发简单游戏,尝试给update回调传递数据时遇到类型不匹配错误。项目代码如下:
main.rs
fn main() -> Result<(), String> { let mut window = CWindow::new("Playground", 1080, 720); let rects: Vec<Rect> = ... // 我想传入`update`的数据 window.set_update(|| update(rects)); // 尝试包装update以传递`rects`,问题出在这里 window.set_draw(draw); window.init()?; } fn draw(canvas: &mut Canvas<Window>) -> () { // 绘制逻辑 } fn update(rects: Vec<Rect>) -> () { // 更新逻辑 }
cwindow.rs
pub struct CWindow { title: String, width: u32, height: u32, callbacks: CWindowCallbacks, window: Option<Window>, } pub struct CWindowCallbacks { update: Option<fn() -> ()>, draw: Option<fn(&mut Canvas<Window>) -> ()>, } impl CWindow { pub fn new(title: &str, width: u32, height: u32) -> CWindow { CWindow { title: String::from(title), width: width, height: height, callbacks: CWindowCallbacks { draw: None, update: None, }, window: None, } } pub fn init(&self) -> Result<(), String> { if self.callbacks.draw.is_none() { panic!("Need draw callback") } if self.callbacks.update.is_none() { panic!("Need update callback") } //... // SDL初始化代码 //... 'running: loop { //... // 事件处理 //... self.callbacks.update.unwrap()(); self.callbacks.draw.unwrap()(&mut canvas); } Ok(()) } pub fn set_draw(&mut self, draw: fn(&mut Canvas<Window>) -> ()) { self.callbacks.draw = Some(draw); } pub fn set_update(&mut self, update: fn() -> ()) { self.callbacks.update = Some(update); } }
错误信息
mismatched types expected fn pointer `fn()` found closure `{closure@src\main.rs:15:23: 15:25}`
解决方案
问题根源
fn()是无状态函数指针,无法携带捕获的变量;而你写的|| update(rects)是捕获了rects的闭包,属于有状态的闭包,二者类型不兼容。要解决这个问题,需要修改CWindow的回调存储方式,支持带环境的闭包。
步骤1:修改CWindow的回调类型
将CWindowCallbacks中的update字段改为Option<Box<dyn FnMut()>>,同时调整set_update方法以接受闭包:
修改后的cwindow.rs:
pub struct CWindow { title: String, width: u32, height: u32, callbacks: CWindowCallbacks, window: Option<Window>, } pub struct CWindowCallbacks { // 改用Box<dyn FnMut()>存储带环境的闭包 update: Option<Box<dyn FnMut()>>, draw: Option<fn(&mut Canvas<Window>) -> ()>, } impl CWindow { pub fn new(title: &str, width: u32, height: u32) -> CWindow { CWindow { title: String::from(title), width, height, callbacks: CWindowCallbacks { draw: None, update: None, }, window: None, } } // 注意init方法需要改为&mut self,因为要调用可变闭包 pub fn init(&mut self) -> Result<(), String> { if self.callbacks.draw.is_none() { panic!("Need draw callback") } if self.callbacks.update.is_none() { panic!("Need update callback") } //... SDL初始化代码 ... let mut canvas = /* 你的Canvas初始化逻辑 */; 'running: loop { //... 事件处理 ... // 调用可变闭包,需要as_mut()获取可变引用 self.callbacks.update.as_mut().unwrap()(); self.callbacks.draw.unwrap()(&mut canvas); } Ok(()) } pub fn set_draw(&mut self, draw: fn(&mut Canvas<Window>) -> ()) { self.callbacks.draw = Some(draw); } // 泛型方法,接受任意符合FnMut()的闭包,装箱后存储 pub fn set_update<F>(&mut self, update: F) where F: FnMut() + 'static, { self.callbacks.update = Some(Box::new(update)); } }
步骤2:根据需求传递数据
根据你对rects的使用场景,选择不同的闭包方式:
场景1:传递所有权(一次性使用)
如果update只需要获取rects的所有权,使用move闭包转移所有权:
fn main() -> Result<(), String> { let mut window = CWindow::new("Playground", 1080, 720); let rects: Vec<Rect> = /* 初始化rects */; // move闭包将rects所有权转移到闭包内部 window.set_update(move || update(rects)); window.set_draw(draw); window.init()?; } fn update(rects: Vec<Rect>) -> () { // 使用rects,注意所有权已转移,后续无法在main中访问 }
场景2:多次修改rects
如果需要在每次update调用中修改rects,让update接受可变引用,并捕获&mut rects:
fn main() -> Result<(), String> { let mut window = CWindow::new("Playground", 1080, 720); let mut rects: Vec<Rect> = /* 初始化rects */; // move闭包捕获可变引用,闭包内部可以修改rects window.set_update(move || update(&mut rects)); window.set_draw(draw); window.init()?; } fn update(rects: &mut Vec<Rect>) -> () { // 修改rects,比如添加元素、更新位置等 }
内容的提问来源于stack exchange,提问作者Blink Dagger
相关产品推荐
相关产品推荐

