首页 > 解决方案 > 将可变实例方法传递给函数

问题描述

我通过为自己创建一个玩具 SDL2 库来自学 Rust。

我在 Go 中创建了类似的东西,并试图移植我的代码。到目前为止,这是我无法克服的问题。我希望我的库对程序状态的函数进行回调,以便我可以将键盘事件从我的库代码发送到我的客户端代码。

目的是让来自 SDL 键盘事件泵的 keydown 事件触发状态对象上的 on_keydown 函数。如果我删除 State 结构并只使用静态函数,那么它就可以工作。当然,这会阻止我根据键盘操作更改程序的状态。

我正在尝试尽可能少地使用外部板条箱。

图书馆的相关部分。

pub enum GameCommand {
    Quit,
    Continue,
}

pub struct Window {
    keydown_event: fn(Event) -> GameCommand,
}

impl Window {
    pub fn set_keydown_event(&mut self, f: fn(e: Event) -> GameCommand) {
        self.keydown_event = f;
    }

    pub fn run(&mut self) -> Result<(), String> {
        let mut event_pump = self.game.context.event_pump()?;

        'running: loop {
            // Handle events
            for event in event_pump.poll_iter() {
                let mut gc = GameCommand::Continue;
                match event {
                    Event::Quit { .. } => break 'running,
                    Event::KeyDown { repeat: false, .. } => {
                        gc = (self.keydown_event)(event);
                    }
                    _ => {}
                }
                if let GameCommand::Quit = gc  {
                    break 'running
                }
            }
        }
        Ok(())
    }
}

现在是客户端bin的相关部分。

struct State {
    bgcolor: Color,
}

impl State {
    fn on_keydown(&mut self, event: Event) -> GameCommand {
        
        match event {
            Event::KeyDown { keycode: Some(Keycode::R), .. } => {
                self.bgcolor.r += 1;
                GameCommand::Continue
            },
            Event::KeyDown { keycode: Some(Keycode::G), .. } => {
                self.bgcolor.g += 1;
                GameCommand::Continue
            },
            Event::KeyDown { keycode: Some(Keycode::B), .. } => {
                self.bgcolor.b += 1;
                GameCommand::Continue
            },
            Event::KeyDown { keycode: Some(Keycode::Escape), ..} => {
                GameCommand::Quit
            },
            _ => GameCommand::Continue,
        }
    }
}

现在主要功能。

fn main()  -> Result<(), String> {
    let mut state = State {
        bgcolor: Color::RGB(0, 0, 0),
    };

    let mut window = Window::new();
    window.set_keydown_event(state.on_keydown);

    Ok(())
}

有很多代码被跳过以保持简短。我用这段代码得到的错误是。

{
    "code": "E0615",
    "message": "attempted to take value of method `on_keydown` on type `State`\n\nmethod, not a field\n\nhelp: use parentheses to call the method: `(_)`",
    
}

如果我window.set_keydown_event(state.on_keydown);收到此错误。

{
    "code": "E0308",
    "message": "mismatched types\n\nexpected fn pointer, found enum `sdlgame::GameCommand`\n\nnote: expected fn pointer `fn(sdl2::event::Event) -> sdlgame::GameCommand`\n               found enum `sdlgame::GameCommand`",
}

我认为问题在于函数签名的差异。在它期望的 set_keydown_event 函数中。

fn(Event) -> GameCommand

这就是为什么一个与结构无关的普通函数起作用的原因。对于改变状态的实例方法,它需要签名。

fn on_keydown(&mut self, event: Event) -> GameCommand

最初,我试图实现这是一种单线程方式,因为我试图让事情变得简单,以便我推理出来。多线程将在稍后出现。

这在 Rust 中是否可行,实现此结果的正确方法是什么?

提前致谢。

标签: rust

解决方案


基本上,您需要使用函数特征以及显式闭包,以便将调用绑定到变量。因此,您将更Window改为使用函数特征:

// F is now the function type
pub struct Window<F: FnMut(Event) -> GameCommand> {
    keydown_event: F,
}

然后您将更改您的 impl 以支持该功能特征:

// put generic in impl
impl<F: FnMut(Event) -> GameCommand> Window<F> {
    // take F as the parameter type now
    pub fn set_keydown_event(&mut self, f: F) {
        self.keydown_event = f;
    }

    pub fn run(&mut self) -> Result<(), String> {
        // this function should stay the same
    }
}

然后,您将向它传递一个显式闭包

fn main() -> Result<(), String> {
    let mut state = State {
        bgcolor: Color::RGB(0, 0, 0),
    };

    let mut window = Window::new();
    // binds the on_keydown function to the state variable
    window.set_keydown_event(|x| state.on_keydown(x));

    Ok(())
}

推荐阅读