Как использовать внутренний объект типажа для изменения членов внешнего объекта?

У меня есть небольшое игровое приложение, использующее объект типажа для обработки ключевых событий и изменения состояния приложения:

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)
    }
}

Это невозможно скомпилировать, потому что последняя функция handle_key_events:

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

Я просто хочу использовать объект GameScene для изменения членов TypingApp, как я могу исправить эту ошибку компиляции? Спасибо!

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
0
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Эта ситуация аналогична описанной в этом вопросе и ответ можно применить здесь.

Проблема возникает из-за того, что при последнем вызове функции ссылается на self.cur_scene (параметр &self функции-типа), в то время как к структуре, содержащей этот элемент, доступ осуществляется исключительно через следующий параметр (app: &mut TypingApp). Это означает, что при реализации функции типажа потенциально возможно получить доступ к члену cur_scene напрямую с помощью параметра self и косвенно через параметр app как эксклюзивную ссылку. Конечно, Rust мешает нам это сделать.

Идея обойти эту проблему состоит в том, чтобы использовать Option (даже если мы не считаем этот член необязательным), чтобы сделать невозможным одновременный общий + эксклюзивный доступ, временно удалив этот член из содержащей его структуры во время вызова и вернув его обратно. после.

В структуре TypingApp

pub cur_scene: Option<Box<dyn GameScene>>,

При его инициализации

cur_scene: Some(Box::new(StartupScene {})),

В его реализации

if let Some(scene) = self.cur_scene.take() {
    scene.handle_key_events(self, key_event)?;
    self.cur_scene = Some(scene);
}
Ok(())
Ответ принят как подходящий

Есть много способов обойти это, я обрисую в общих чертах два, которые я бы рассмотрел. Основная проблема, с которой вы сталкиваетесь, заключается в том, что вы пытаетесь одновременно заимствовать всю структуру и одно из ее полей с возможностью изменения, чего Rust не позволяет. Одно простое решение — поместить все состояние, к которому handle_key_events может потребоваться прикоснуться, в его собственную структуру:

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

pub struct TypingApp {
    cur_scene: ...,
    state: AppState,
}

Затем вам нужно изменить определение метода типажа, чтобы он действовал на &mut AppState, а не на все приложение:

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

И измените место вызова:

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

Теперь вы заимствуете два непересекающихся поля, что разрешено.

Другим, более радикальным изменением подхода мог бы стать переход к более декларативному стилю, при котором handle_key_events ничего не изменяет, а генерирует сообщения, которые TypingApp сам обрабатывает:

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
        }
    }
}

Другие вопросы по теме