У меня есть небольшое игровое приложение, использующее объект типажа для обработки ключевых событий и изменения состояния приложения:
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
, как я могу исправить эту ошибку компиляции? Спасибо!
Эта ситуация аналогична описанной в этом вопросе и ответ можно применить здесь.
Проблема возникает из-за того, что при последнем вызове функции ссылается на 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
}
}
}