Struct inheritance/embedding best practice


#1

I was looking at one of my project which has more than 50 classes all of which inherit from a common base class.

struct Base{
    id: i64,
    created_by: String,
    created_time: DateTime<UTC>,
    updated_by: String,
    updated_time: DateTime<UTC>,
    version, i64
}

struct Role { //Some magic here

}

All classes have this as base class. I was wondering how this can be done. I have read few blogs which suggest the enum approach, trait approach. but none of this see natural. What are the other options? Is there something like Go’s embedding planned or possible?

Note, I am not planning to rewrite the project, just curious to see how it will look like

Thanks,
Murali


#2

That looks very much like a database-heavy project. I’m guessing the goal is/was to have all those fields in all SQL tables?

I wouldn’t know a solution from the top of my head, but two things that come to mind would be:

  • Generate the classes by macro:
db_struct! {
  // Macro implementation magically inserts common fields,
  // can even do defaults
  my_field1: foo,
  my_field2: bar
}

This probably is simplest for ORMs (Diesel!) to interpret (I hope)

  • Second: embed the Base-struct as a field in all structs. If you make sure that there is a Default impl for Base, the user wouldn’t even have to init it thanks to the default-init syntax as explained here on StackOverflow (that was surprisingly hard to find, do we have a documentation problem here?)

  • Mix and match the above: make a macro that inserts the fields and generates the default impl. (Or use #[derive(default)] )

Just some stabs in the dark to get the discussion started :slight_smile:


#3

I would suggest embed+trait if you want to “inherit” also the interface and have the possibility to use the objects like trait objects.


#4

@juleskers Thank you. I like the macro approach. Macros and compiler plugin are so powerful, i am excited to see how frameworks are going to use/abuse them. I also found this RFC which proposes to have fields in trait. that might solve some of the issues.
I did not know about Default trait, like you said there is no mention of it in docs I believe. Thanks again for this.


#5

While there are probably some good use cases for having fields in traits, there are certainly ways to to get around of this too using getters and setters.

You can define a trait that looks something like this:

trait BaseMeta {
  /* field access */
  fn get_id(&self) -> i64;
  fn get_version(&self) -> i64;
  fn set_id(&mut self);
  fn set_version(&mut self);

  /* methods */
  fn common_base_methods(&self);
}

Then either in the macro db_struct! you can implement these things, or you can write a custom derive which could look like:

#[derive(BaseMeta)]
struct DbEntry {
  base: Base,
  field1: Foo,
  field2: bar,
}

which would implement the getters and setters for your DbEntry while placing all your meta data in your self.base field. Then you could even implement something of an “Abstract Base Class” for extending the functionality with things that satisfy BaseMeta, with something like:

trait BaseExt: BaseMeta {
   fn expiration_date(&self) -> DateTime<UTC> {
     self.get_updated_time() + Duration::days(1)
   }
}

This would give you an easy way to add these methods you want to your struct, keeping a default implementation, which you could override if desired.


#6

You’re welcome! :slight_smile:

It will be in the appendix of The Book, 2nd edition, “21.3 C: derivable traits”.
The “update syntax” as it’s called is mentioned in The Book, 1st edition, in the structure section, 3.12, but doesn’t make the jump to combining it with Default.
I’ve opened an issue to discuss this here

In my experience with java, getters/setters are becoming almost an anti-pattern, at least in the java-bean style that is most common. They promote/imply mutable state everywhere. This gives up a big advantage that rust provides with its immutable-by-default strategy.
Of course, you can’t call the setters on an immutable binding, you need &mut Self, so the problem isn’t as bad as it gets in java.