使用状态机模式时解决 Rust 所有权问题
Getting around Rust ownership problems when using state machine pattern
这个问题是关于在 Rust 中为视频游戏实现状态机时可能出现的特定所有权模式,其中状态可以持有对 "global" 借用上下文的引用,并且状态机拥有它们的状态.我已尝试尽可能多地删除细节,同时仍然激发问题,但这是一个相当大且纠结的问题。
这是状态特征:
pub trait AppState<'a> {
fn update(&mut self, Duration) -> Option<Box<AppState<'a> + 'a>>;
fn enter(&mut self, Box<AppState<'a> + 'a>);
//a number of other methods
}
我正在使用盒装特征对象而不是枚举来实现状态,因为我希望有相当多的状态。在它们的更新方法中状态 return a Some(State)
以便使它们拥有的状态机切换到新状态。我添加了一个生命周期参数,因为没有它,编译器将生成类型为:Box<AppState + 'static>
的框,使框无用,因为状态包含可变状态。
说到状态机,这里是:
pub struct StateMachine<'s> {
current_state: Box<AppState<'s> + 's>,
}
impl<'s> StateMachine<'s> {
pub fn switch_state(&'s mut self, new_state: Box<AppState<'s> + 's>) -> Box<AppState<'s> + 's> {
mem::replace(&mut self.current_state, new_state);
}
}
状态机始终具有有效状态。默认情况下,它以 Box<NullState>
开头,这是一种什么都不做的状态。为简洁起见,我省略了 NullState
。就其本身而言,这似乎编译得很好。
InGame
状态旨在实现一个基本的游戏场景:
type TexCreator = TextureCreator<WindowContext>;
pub struct InGame<'tc> {
app: AppControl,
tex_creator: &'tc TexCreator,
tileset: Tileset<'tc>,
}
impl<'tc> InGame<'tc> {
pub fn new(app: AppControl, tex_creator: &'tc TexCreator) -> InGame<'tc> {
// ... load tileset ...
InGame {
app,
tex_creator,
tileset,
}
}
}
这个游戏依赖于 Rust SDL2。这组特定的绑定要求纹理由 TextureCreator
创建,并且纹理不会比它们的创建者活得更久。纹理需要一个生命周期参数来确保这一点。 Tileset
持有纹理,因此导出此要求。这意味着我无法在状态本身中存储 TextureCreator
(尽管我愿意),因为可变借用的 InGame
可能会将纹理创建者移出。因此,纹理创建者在 main
中拥有,当我们创建主状态时,对它的引用被传递给:
fn main() {
let app_control = // ...
let tex_creator = // ...
let in_game = Box::new(states::InGame::new(app_control, &tex_creator));
let state_machine = states::StateMachine::new();
state_machine.switch_state(in_game);
}
我觉得这个程序应该是有效的,因为我已经确保 tex_creator
比任何可能的状态都长,而且状态机是最不长寿的变量。但是,我收到以下错误:
error[E0597]: `state_machine` does not live long enough
--> src\main.rs:46:1
|
39 | state_machine.switch_state( in_game );
| ------------- borrow occurs here
...
46 | }
| ^ `state_machine` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
这对我来说没有意义,因为state_machine
只是在方法调用时借用的,但是编译器说方法结束时仍然在借用。我希望它能让我追踪错误消息中的借款人是谁——我不明白为什么在方法 return 时没有 return 借用。
基本上,我想要以下内容:
- 状态由特征实现。
- 状态由状态机拥有。
- 状态能够包含对生命周期大于状态机的任意非静态数据的引用。
- 当状态被换出时,旧框仍然有效,以便可以将其移入新状态的构造函数中。这将允许新状态切换回之前的状态,而不需要重新构建它。
- 状态可以通过 return 从 'update' 获取新状态来表示状态更改。旧状态必须能够在自身内部构建这个新状态。
是否可以满足这些限制条件?如果可以,如何满足?
对于这个冗长的问题以及我可能遗漏了一些明显的东西,我深表歉意,因为在上面的实现中做出了许多决定,我不确定我是否理解生命周期的语义。我试图在网上搜索这种模式的例子,但它似乎比我见过的玩具例子要复杂得多,限制也更多。
在 StateMachine::switch_state
中,您 不想 在 &mut self
上使用 's
生命周期; 's
表示状态借用资源的生命周期,不是状态机的生命周期。请注意,通过这样做,self
的类型以 's
结束两次:完整类型是 &'s mut StateMachine<'s>
;您只需要在 StateMachine
上使用 's
,在参考上使用 而不是 。
在可变引用 (&'a mut T
) 中,T
是 invariant,因此 's
也是不变的。这意味着编译器认为状态机的生命周期与其借用的生命周期相同。因此,在调用switch_state
之后,编译器认为状态机最终借用了自己.
简而言之,将&'s mut self
更改为&mut self
:
impl<'s> StateMachine<'s> {
pub fn switch_state(&mut self, new_state: Box<AppState<'s> + 's>) -> Box<AppState<'s> + 's> {
mem::replace(&mut self.current_state, new_state)
}
}
您还需要将 main
中的 state_machine
声明为可变的:
let mut state_machine = states::StateMachine::new();
这个问题是关于在 Rust 中为视频游戏实现状态机时可能出现的特定所有权模式,其中状态可以持有对 "global" 借用上下文的引用,并且状态机拥有它们的状态.我已尝试尽可能多地删除细节,同时仍然激发问题,但这是一个相当大且纠结的问题。
这是状态特征:
pub trait AppState<'a> {
fn update(&mut self, Duration) -> Option<Box<AppState<'a> + 'a>>;
fn enter(&mut self, Box<AppState<'a> + 'a>);
//a number of other methods
}
我正在使用盒装特征对象而不是枚举来实现状态,因为我希望有相当多的状态。在它们的更新方法中状态 return a Some(State)
以便使它们拥有的状态机切换到新状态。我添加了一个生命周期参数,因为没有它,编译器将生成类型为:Box<AppState + 'static>
的框,使框无用,因为状态包含可变状态。
说到状态机,这里是:
pub struct StateMachine<'s> {
current_state: Box<AppState<'s> + 's>,
}
impl<'s> StateMachine<'s> {
pub fn switch_state(&'s mut self, new_state: Box<AppState<'s> + 's>) -> Box<AppState<'s> + 's> {
mem::replace(&mut self.current_state, new_state);
}
}
状态机始终具有有效状态。默认情况下,它以 Box<NullState>
开头,这是一种什么都不做的状态。为简洁起见,我省略了 NullState
。就其本身而言,这似乎编译得很好。
InGame
状态旨在实现一个基本的游戏场景:
type TexCreator = TextureCreator<WindowContext>;
pub struct InGame<'tc> {
app: AppControl,
tex_creator: &'tc TexCreator,
tileset: Tileset<'tc>,
}
impl<'tc> InGame<'tc> {
pub fn new(app: AppControl, tex_creator: &'tc TexCreator) -> InGame<'tc> {
// ... load tileset ...
InGame {
app,
tex_creator,
tileset,
}
}
}
这个游戏依赖于 Rust SDL2。这组特定的绑定要求纹理由 TextureCreator
创建,并且纹理不会比它们的创建者活得更久。纹理需要一个生命周期参数来确保这一点。 Tileset
持有纹理,因此导出此要求。这意味着我无法在状态本身中存储 TextureCreator
(尽管我愿意),因为可变借用的 InGame
可能会将纹理创建者移出。因此,纹理创建者在 main
中拥有,当我们创建主状态时,对它的引用被传递给:
fn main() {
let app_control = // ...
let tex_creator = // ...
let in_game = Box::new(states::InGame::new(app_control, &tex_creator));
let state_machine = states::StateMachine::new();
state_machine.switch_state(in_game);
}
我觉得这个程序应该是有效的,因为我已经确保 tex_creator
比任何可能的状态都长,而且状态机是最不长寿的变量。但是,我收到以下错误:
error[E0597]: `state_machine` does not live long enough
--> src\main.rs:46:1
|
39 | state_machine.switch_state( in_game );
| ------------- borrow occurs here
...
46 | }
| ^ `state_machine` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
这对我来说没有意义,因为state_machine
只是在方法调用时借用的,但是编译器说方法结束时仍然在借用。我希望它能让我追踪错误消息中的借款人是谁——我不明白为什么在方法 return 时没有 return 借用。
基本上,我想要以下内容:
- 状态由特征实现。
- 状态由状态机拥有。
- 状态能够包含对生命周期大于状态机的任意非静态数据的引用。
- 当状态被换出时,旧框仍然有效,以便可以将其移入新状态的构造函数中。这将允许新状态切换回之前的状态,而不需要重新构建它。
- 状态可以通过 return 从 'update' 获取新状态来表示状态更改。旧状态必须能够在自身内部构建这个新状态。
是否可以满足这些限制条件?如果可以,如何满足?
对于这个冗长的问题以及我可能遗漏了一些明显的东西,我深表歉意,因为在上面的实现中做出了许多决定,我不确定我是否理解生命周期的语义。我试图在网上搜索这种模式的例子,但它似乎比我见过的玩具例子要复杂得多,限制也更多。
在 StateMachine::switch_state
中,您 不想 在 &mut self
上使用 's
生命周期; 's
表示状态借用资源的生命周期,不是状态机的生命周期。请注意,通过这样做,self
的类型以 's
结束两次:完整类型是 &'s mut StateMachine<'s>
;您只需要在 StateMachine
上使用 's
,在参考上使用 而不是 。
在可变引用 (&'a mut T
) 中,T
是 invariant,因此 's
也是不变的。这意味着编译器认为状态机的生命周期与其借用的生命周期相同。因此,在调用switch_state
之后,编译器认为状态机最终借用了自己.
简而言之,将&'s mut self
更改为&mut self
:
impl<'s> StateMachine<'s> {
pub fn switch_state(&mut self, new_state: Box<AppState<'s> + 's>) -> Box<AppState<'s> + 's> {
mem::replace(&mut self.current_state, new_state)
}
}
您还需要将 main
中的 state_machine
声明为可变的:
let mut state_machine = states::StateMachine::new();