Inheritance and rust

Hello all,

I am coming from an OOP background and I am trying to find how to do things the "rust" way.

I want to create a toy program managing multiple "windows".

The windows would have some attributes like the dimension, the visibiliy, and some common associated method to resize, hide, the window. However, I want them to have at least a different render() method to display different information.

So in a typical OOP way, I would have created a GenericWindow super class, and then in a derived class like Footer::GenericWindow and Header::GenericWindows reimplement different render().

I have been thinking that I could create a "Windowable" trait but since trait only contains methods and no attribute (like width and height this seems a bit awkward)

So currently, I am more inclined to create a struct Window{}, keep all the generic code in the associated implementation, and then move the specific part in other struct like

struct Window{
   width: u32,
   height: u32
   ...
}

impl Window{
    fn resize(&self) --> Window{
      ....
    }

and then having

struct Footer{
    win: Window
} 

impl Footer{
   fn render(&self)  {
   ...
   }

struct Header{
    win: Window
} 

impl Header{
   fn render(&self)  {
   ...
   }

Is this a good approach ? Is there a better way to separate the different concern ? Is there a more idiomatic approach ?

I am trying to rewire my brain to embrace new concepts, this is both painful and enlightening.

Thanks in advance

You might still want to have a trait for the render method:

trait Windowable {
  fn render(&self);
}

impl Windowable for Footer {
  fn render(&self) {
   ...
 }
}

impl Windowable for Header {
 fn render(&self) {
  ...
 }
}

This way you could have a list of Windowables containing both Headers and Footers and call render on them.

I think the closest way to approach OOP with Rust would be to use default trait implementations; here's an (untested) example to give an idea. But maybe it's more of an "anti-pattern" to do that in Rust so I'm not sure that's the right way to go :smile:

Thanks for your input. It makes sense to have a Renderable trait to enforce that all windows will actually implement a render() method.

Composition over inheritance, like this:

struct Window {
    renderer: Box<Render>,
    width: u32,
    height: u32,
}
impl Window {
    pub fn new(renderer: Box<Render>, width: u32, height: u32) -> Window {
        Window{ renderer: renderer, width: width, height: height }
    }
    pub fn show() {
        renderer.render();
    }
}

trait Render {
    fn render();
}

struct TextEditor;

impl Render for TextEditor {
    fn render() {
        println!("I'm programmering!");
    }
}

pub fn main() {
    let win = Window::new(Box::new(TextEditor), 640,  480);
    win.show();
}
2 Likes

Thanks this is very clean and understandable. For some reason I had a hard time to reconcile trait and composition, which is a natural fit.

For some reason, I don't entirely grasp the above code only works once the trait Render is declared as public.

Yes, composition is better, but what if any struct may have one super property which would have special behavior (just like __proto__ in javascript) and all impls for this super will be available from struct itself. Example:

struct A(i32);

impl A {
  pub fn square(&self) -> i64 {
    self.0 * self.0
  }
}

struct B {
  super: A, // or Box<trait>
}

...

fn main() {
  let val = B { super: A(12) };

  println!("{}", val.square());
}

I think that's what the Deref trait effectively does.

it may be used in such a way, but original documentation for Deref trait sais:

... Deref should only be implemented for smart pointers to avoid confusion

But I am suggesting prototype inheritance just like in javascript

@andreytkachenko64 You may find the delegation of implementation RFC to be an interesting read, it seeks to solve similar problems.