`boilermates` – How to easily declare multiple structs with common fields, common functionality and simple interconvertability (*I can't believe it's not inheritance!*)

Howdy Rustaceans!

I've just released boilermates – a wondrous (read experimental) proc macro that would allow you to "share" fields between structs, easily convert between them (using from and such), and define common functionality based on the fields they have in common.

It's very much 0.1, and has some hygiene issues, but, you can already do some cool stuff.

For example, you want to have structs A, B and AB. All "related", but not quite identical. You can do something like:

#[boilermates("A", "B")]
struct AB {
    // this field should be in all structs
    field_1: usize,

    // this field should be only in A and AB
    #[boilermates(only_in("A", "AB"))]
    field_2: usize,

    // this field should be only in B and AB
    #[boilermates(not_in("A"))]
    field_3: usize,

    // this field should be in all structs
    field_4: usize,

    // this field should be in all structs
    field_5: usize,
}

This will result in the generation of:

struct A {
    field_1: usize,
    field_2: usize,
    field_4: usize,
    field_5: usize,
}

struct B {
    field_1: usize,
    field_3: usize,
    field_4: usize,
    field_5: usize,
}

struct AB {
    field_1: usize,
    field_2: usize,
    field_3: usize,
    field_4: usize,
    field_5: usize,
}

But you also would get the following implementations for conversion, where you need only to supply what's missing in order to convert between the types, or just do a from/into if the target struct contains all the fields of the source struct:

  • impl From<AB> for A
  • impl From<AB> for B
  • A::into_b: fn (field_3: usize) -> B
  • A::into_a_b: fn (field_3: usize) -> AB
  • B::into_a: fn (field_2: usize) -> A
  • B::into_a_b: fn (field_2: usize) -> AB

You can also define common functionality with the generated Has{Field} traits that have getters like so:

trait ABCommon: HasField1 + HasField4 + HasField5 {
    fn sum_common_fields(&self) -> usize {
        self.field_1() + self.field_4() + self.field_5()
    }
}
impl<T: HasField1 + HasField4 + HasField5> ABCommon for T {}

And now all three structs have the sum_common_fields() method. You can of course create more specialized traits that would apply only to a subset of the structs, for example HasField2 is only implemented for A and AB.

Of course you can pass attributes to the generated structs and to the fields as well.

I find this thing to be useful in APIs where the user input struct doesn't contain the ID of the object, and the output value doesn't need to contain some internal information.

Suggestions and pull requests are welcome. I'll of course continue improving it in the meantime.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.