Inheritance and rust


#1

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


Trouble implementing common behavior with traits
#2

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:


#3

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


#4

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();
}

#5

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.


#6

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());
}


#7

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


#8

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


#9

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