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.