What is the right way of achieving Inheritance in rust

I have below C++ code where A is a base class and B & C are derived class of A.

// cpp code :
class A
{
public:
    int a;
    int b;
    virtual void setup(){
    //class A definition
    }

}

class B:public A
{
	void setup(){
	//class B definition
	}

}

class C:public A
{
	void setup(){
	//class C definition
	}
}

Below is what i think rust approach will be

// rust approach :
struct A {
 a:i32,b:i32,
}

struct B {
  var:A,
}

struct C{
  var:A,
}


trait common{
   fn setup();
}

impl common for A{
  fn setup(){
  //A implementation
  }
}

impl common for A{
  fn setup(){
  //B implementation
  }
}

impl common for c{
  fn setup(){
  //C implementation
  }
}

Is this a correct approach ? or is there any standard approach followed by rustaceans?

Rust does not have inheritance, and there are multiple alternatives which are appropriate for different circumstances. It is better to ask what technique should be used instead of inheritance in some specific situation where you would use inheritance in C++.

5 Likes

There is no right way to do inheritance in Rust.
That said, you can write code that does pretty much the same thing.

enum A {
      B(B),
      C(C),
}

struct B { ... }
struct C { ... }

impl A {
     fn setup(&self) {
            match self {
                  A::B(b) => { .... },
                  A::C(c) => { .... },
             }
      }
}
1 Like

Rust is not an object-oriented language. Inheritance doesn't have a standard, 1-to-1 replacement, so you shouldn't try to find one and apply it blindly. In order to achieve data abstraction and polymorphism, Rust offers a variety of features. Which one you should use depends on the problem you are solving:

  • enum is the most direct approach: it represents a choice among a finite, closed set of types known ahead, at compile time. You can use enums when you know you will only have a handful of choices composed of very concrete types. Enums have the advantage that they are easy to create, use, can be extended to be generic if later needed, and the concrete, closed set of types means that things like serialization/deserialization will work trivially. They have a small overhead because any time you want to extract a concrete value from an enum, you'll have to match on it.
  • Generics (aka parametric polymorphism) and traits are useful when you want to abstract over any set of types satisfying a given interface. You probably want to use them if you had used a "pure virtual" class in an OO language or templates in C++. They have moderate compile-time cost, but they are monomorphized, so they don't involve runtime overhead – just like C++ templates.
  • Trait objects (colloquially referred to as dyn Trait, a kind of existential type) are useful when you need dynamic binding (similar to virtual methods in C++). They allow for high flexibility as they hide concrete types behind a vtable, but as a consequence, they have some runtime overhead. However, they don't allow calling generic methods (for obvious technical reasons).
  • impl Trait (another kind of existential type) is useful for example when you want to return a statically-typed value without the costs of indirection (as imposed by dyn Trait) but you want to hide its identity from the consumer. You would probably choose this option when you had otherwise returned a base class.

Without knowing the specifics of what you are exactly trying to do, it's not possible to sensibly recommend any of these approaches over another.

19 Likes

Hi all,

As suggested in this post i have chosen enum to achieve inheritance like behavior in rust and my code looks like this

enum Input{
  Component_1{},
  Component_2{},
  Component_3{},
}

trait common {
  fn setup();
}

Here Component_1,Component_2,Component_3 all are struct like datatypes and serves as an input to different components in my code.(since input is different for different components I have 3 different structs here).I have combined all these type under a common enum-type Input .
All these structs (Component_1,Component_2 etc) has some behaviour in common (setup()) and for that reason i have created a trait common. But the requirement is such that all these Component_1,Component_2 etc should impl setup() in different ways .something like below

impl common for Input::Component_1 {

}

Is it possible by any chance or do you think my approach is wrong here?

I don't understand what is "common" in that if you need to write separate functions at the end of the day anyway. What does that setup function do?

in C++ context - it is a virtual function declared inside base class . and it will have different implementation in each derived class.

Then you need to match on the enum in order to find out which variant it actually contains at runtime. @RedDocMD's post shows you just that.

Wow, it would never have occurred to me that one could implement methods on an enum. Perhaps I'm daft. Is this mentioned anywhere in the Rust book or somewhere?

https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html

Did you never use any of the methods of Option or Result? Like, the .unwrap() method for example?

1 Like

You can implement methods on pretty much any type. The question is not "why?", it's "why not?". Anyway, there are already plenty of enum types with methods in the standard library, surely you used at least Option or Result.