You are on the right track: if you want common implementation then you have to stick with traits.
Let's look at you code:
- Right off the bat, you can't have
SceneBase
as the name for both a trait and a struct.
- methods on traits that modify itself or return a property have to have
&mut self
or &self
respectively.
- Your
SceneBase::new()
returns Self
. That's not compatible with using trait objects i.e. Box<SceneBase>
(see this for more info). There are two ways to deal with it - move the new
method to the impl
and lose access to it via trait interface or make it a generic object.
- Your method
fn new(scene: SceneBase) -> Self
also wants to construct new as if it was a trait object.
With that in mind, let's go over your initial examples line by line. struct SceneBase
is renamed to struct SceneBasic
, to avoid conflict with the trait name, same was done for SceneContainer
, etc.
SceneBase
becomes:
trait SceneBase {
fn new() -> Self;
fn is_active(&self) -> bool;
fn is_ready(&self) -> bool;
fn start(&mut self);
fn update(&mut self);
fn stop(&mut self);
}
The names in Rust are snake_case
, not pascalCase
. You also need to tell what methods modify or don't modify the struct.
Next up, we implement SceneBase
for struct SceneBasic
, because our traits are useless unless something implements them.
impl SceneBase for SceneBasic {
fn new() -> SceneBasic {
SceneBasic {
active: false,
is_ready: false,
}
}
fn is_active(&self) -> bool {
self.active
}
fn is_ready(&self) -> bool{
self.is_ready
}
fn start(&mut self){
self.active = true;
}
fn update(&mut self) {
// Some implementation
}
fn stop(&mut self) {
self.active = false;
}
}
Next up, let's rewrite SceneContainer
so it no longer uses trait objects
#[derive(Debug)]
struct BasicSceneContainer<T: SceneBase> {
container: T, // or Box<T> if that's what's really needed
}
Using generic <T: SceneBase>
means that for every type that implements SceneBase
there will be new kind of BasicSceneContainer
created (for more detail see What is monomorphisation with context to C++?).
Finally, with all this given, we can rewrite SceneContainer
:
trait SceneContainer<T: SceneBase> {
fn new(scene: T) -> Self;
fn update_children(&mut self);
fn pop(&mut self);
}
impl<T: SceneBase> SceneContainer<T> for BasicSceneContainer<T> {
fn new(scene: T) -> BasicSceneContainer<T> {
BasicSceneContainer {
container: scene
}
}
fn update_children(&mut self) {
self.container.update();
}
fn pop(&mut self) {
// pop the container
}
}
Let's say we want to "extend" SceneBasic
with SceneAdvanced
, how would we do it in Rust? We'd probably just use the delegate pattern using composition:
struct SceneAdvanced {
delegate: SceneBasic,
}
impl SceneBase for SceneAdvanced {
fn new() -> SceneAdvanced {
SceneAdvanced {
delegate: SceneBasic {
active: false,
is_ready: false,
}
}
}
fn is_active(&self) -> bool {
self.delegate.active
}
//etc.
}
See playground for full code.