Search code examples
rust

How to use inner trait object to modify members of external object?


I have a small game app using trait object to handle key events and modify app state:

pub trait GameScene {
    fn handle_key_events(&self, app: &mut TypingApp, key_event: KeyEvent) -> Result<()>;
}

pub struct StartupScene {}

impl GameScene for StartupScene {
    fn handle_key_events(&self, app: &mut TypingApp, key_event: KeyEvent) -> Result<()> {
        todo!()
    }
}

pub struct TypingApp {
    /// lots of other members here
    /// ......
    /// modify above members according to the user's input
    pub cur_scene: Box<dyn GameScene>
}

impl Default for TypingApp {
    fn default() -> Self {
        Self {
            cur_scene: Box::new(StartupScene{})
        }
    }
}

impl TypingApp {
    pub fn handle_key_events(&mut self, key_event: KeyEvent) -> Result<()> {
        self.cur_scene.handle_key_events(self, key_event)
    }
}

This cannot be compiled because the last handle_key_events function:

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/app.rs:53:9
   |
53 |         self.cur_scene.handle_key_events(self, key_event)
   |         --------------^-----------------^^^^^^^^^^^^^^^^^
   |         |              |
   |         |              immutable borrow later used by call
   |         mutable borrow occurs here
   |         immutable borrow occurs here

I just want to use GameScene object to modify members of TypingApp, how can I fix this compile error? Thanks!


Solution

  • There's many ways to get around this, I'll outline the two that I'd consider. The principal problem you're running into is that you're trying to borrow the whole struct and one of its fields mutably at the same time, which Rust doesn't allow. One simple fix is to put all the state that handle_key_events might need to touch into its own struct:

    pub struct AppState {
        precision: f64,
        error_rate: f64,
        // ...
    }
    
    pub struct TypingApp {
        cur_scene: ...,
        state: AppState,
    }
    

    Then you need to change the trait method definition to act on &mut AppState instead of the whole app:

    pub trait GameScene {
        fn handle_key_events(&self, app_state: &mut AppState, key_event: KeyEvent) -> Result<()>;
    }
    

    And alter the call site:

    impl TypingApp {
        pub fn handle_key_events(&mut self, key_event: KeyEvent) -> Result<()> {
            self.cur_scene.handle_key_events(&mut self.state, key_event)
        }
    }
    

    Now you're borrowing two disjoint fields which is allowed.

    Another, more radical change in approach would be to move to a more declarative style where handle_key_events doesn't modify anything but generates messages that TypingApp itself handles:

    enum Message {
        WrongInput,
        CorrectInput,
        // etc...
    }
    
    pub trait GameScene {
        fn handle_key_events(&self, app: &TypingApp, key_event: KeyEvent) -> Result<Message>;
    }
    
    impl TypingApp {
        pub fn handle_key_events(&mut self, key_event: KeyEvent) -> Result<()> {
            match self.cur_scene.handle_key_events(&self, key_event)? {
                Message::WrongInput => self.record_wrong_input(), // or whatever
                // handle other messages
            }
        }
    }