-2

Continuing from my previous question, my goal is to create a scene which inherits the scene base and from there push new scenes into a container. You can pop scenes off the container to then show a window or transition to a new map or whatever you want to do in that particular scene:

#[derive(Debug)]
struct SceneBase {
    active: bool,
    isReady: bool,
}

trait SceneBase {
    fn new() -> Self;
    fn isActive() -> bool;
    fn isReady() -> bool;
    fn start();
    fn update();
    fn stop();
}

#[derive(Debug)]
struct SceneContainer {
    container: Box<SceneBase>,
}

trait SceneContainer {
    fn new(scene: SceneBase) -> Self;
    fn updateChildren();
    fn pop();
}

I am approaching this in an object-oriented way and I know Rust isn't really OO. How can I re-organize my structures and traits such that I have some sort of base that other scenes inherit from and then can be pushed to a container for "storage" until needed?

Any ideas on how to lose the OO and think more ... functional on this one?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
TheWebs
  • 12,470
  • 30
  • 107
  • 211
  • *and I know Rust isn't really OO* — citation required. If you think that "OO" means "has inheritance", there's a lot of programmers that would like to have a word with you. – Shepmaster Jan 17 '18 at 14:29
  • The question doesn't even attempt to show what the desired "inheritance" *is*. All that's been presented is code that doesn't even compile. – Shepmaster Jan 17 '18 at 14:38

3 Answers3

2

The question is quite generic but as a general answer: if you want to replace an inheritance mechanism, do some composition:

struct Thing {
    stuff: i32,
    extension: Box<ThingExtension>,
}

impl Thing {
    fn new(stuff: i32, extension: Box<ThingExtension>) -> Self {
        Thing { stuff, extension }
    }
}

trait ThingExtension {
    fn do_something(&self, stuff: i32);
}

This snippet is self-understandable: the user will implement the ThingExtension trait to build a real Thing.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Boiethios
  • 38,438
  • 19
  • 134
  • 183
1

Boiethios is right in that you should use composition. I wanted to give you a more specific example related to what you are trying to accomplish. In Rust, inheritance doesn't exist so we use composition to accomplish the task of having different structures that need the same functionality.

It starts with a trait, in your case that looks something like this

trait Scene {
    fn is_active(&self) -> bool;
    fn is_ready(&self) -> bool;
    fn start(&mut self);
    fn update(&mut self);
    fn stop(&mut self);
}

This scene trait can now be used to create a SceneBase struct

struct SceneBase {
    active: bool,
    ready: bool,
}

impl SceneBase {
    fn new() -> SceneBase {
        SceneBase {
            active: false,
            ready: false,
        }
    }
}

impl Scene for SceneBase {
    fn is_active(&self) -> bool {
        self.active
    }

    fn is_ready(&self) -> bool {
        self.ready
    }

    fn start(&mut self) {
        self.active = true;
    }

    fn update(&mut self) {
        println!("scene update.");
        self.ready = true;
    }

    fn stop(&mut self) {
        self.ready = false;
        self.active = false;
    }
}

We have declared the trait Scene and implemented it for SceneBase. We use composition to add the same functionality to the MySceneOne and MySceneTwo structures:

struct MySceneOne {
    scene: Box<Scene>,
}

struct MySceneTwo {
    scene: Box<Scene>,
}

The other part of this is using the functionality that we provided. We create another trait that retrieves the Scene for us and implement it for our scenes:

trait GetScene {
    fn get_scene(&mut self) -> &mut Scene;
}

impl GetScene for MySceneOne {
    fn get_scene(&mut self) -> &mut Scene {
        &mut *self.scene
    }
}

Now we can add our scenes generically in a collection. Here I use a Vec but you could create your own that provides the container functionality you are looking for.

let mut v: Vec<Box<GetScene>> = Vec::new();
let my_scene = MySceneOne {
    scene: Box::new(SceneBase::new())
};
let my_other_scene = MySceneTwo {
    scene: Box::new(SceneBase::new())
};

v.push(Box::new(my_scene));
v.push(Box::new(my_other_scene));

Next we want to use our scenes so we make a function that takes a GetScene and then does something with the scene in this case the update.

fn update_scene(b: &mut Box<GetScene>) {
    let scene = b.get_scene();
    scene.update();
}

After we have our function we can now call it for the scenes we have in our Vec.

for scene in v.iter_mut() {
    update_scene(scene);
}

For a complete example I created this playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Daniel Wilkins
  • 447
  • 4
  • 7
1

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Daniel Fath
  • 16,453
  • 7
  • 47
  • 82