#definition #generate #structs #proc-macro #single #multiple #variant

macro structout

A proc-macro for generating structs from a common definition

6 releases (3 breaking)

0.6.0 Oct 26, 2020
0.5.0 Oct 23, 2020
0.3.0 Oct 20, 2020
0.1.2 Oct 16, 2020

#900 in Procedural macros

BSD-3-Clause

25KB
578 lines

structout

Usage

This library allows for generating multiple structs from a single definition through a procedural macro.

generate!(
  attributes
  visibility <...> where ... {
    field: type,
    ...
  } => {
    OutputStruct => [action(arg), ...]
  }
)
  • (optional) attributes is applied to all variants.
  • (optional) visibility is applied to all variants.
  • (optional) <...> are the type arguments (a.k.a generics); they shouldn't get included if they don't get used.
  • (optional) where ... represents the type constraints.
  • { field: type, ... } is the common struct body which will be used for generating new structs.
  • { OutputStruct => [action(arg), ...] } is the output configuration, where each entry maps to one new struct being generated; further:
    • OutputStruct is the name of the struct
    • [action(arg), ...] are the list of actions which will be used to build this specific variant.

Where "actions" can be one of:

  • omit(fields_names) omits the fields from this struct definition.
  • include(fields_names) has precedence over omit. Includes the fields in this struct definition.
  • attr(args) inserts an attribute before the struct definition.
  • as_tuple() outputs the struct as a tuple struct.
  • upsert(fields) will either update or insert the field with the specified type (i.e. replace the field definition if one exists with the same identifier or, otherwise, insert a new one).

Put into practice:

use structout::generate;

generate!(
  {
    foo: u32,
    bar: u64,
    baz: String
  } => {
    WithoutFoo => [omit(foo)],
    WithoutBar => [omit(bar)],
  }
);

The code above should expand to two structs

struct WithoutFoo {
    bar: u64,
    baz: String
}
struct WithoutBar {
    foo: u32,
    baz: String
}

If one were to add two generic arguments, they should be efficiently split between the variants without the need for PhantomData.

generate!(
  <S, C> where S: Sized, C: Copy {
    foo: S,
    bar: G
  } => {
    OnlyBar => [omit(foo)],
    OnlyFoo => [omit(bar)],
  }
);

The above code should expand to

struct OnlyBar<C>
where
    C: Copy,
{
    bar: G,
}
struct OnlyFoo<S>
where
    S: Sized,
{
    foo: S,
}

For examples of usage for the full API, consult the tests module.

Development

Testing

Testing revolves around snapshot testing (insta). It's effectively done by running cargo expand (cargo-expand), getting its output, then reviewing with it with cargo insta review (cargo insta).

Consult the tests module for seeing how it's implemented in practice.

Motivation

This library attends to the need of generating multiple structs for a single definition. Consider the code

struct Human {
  id: u32,
  age: u32,
  username: String,
  name: String,
  surname: String
}

// suppose this is what you would get from an API
struct HumanEditableParts {
  name: String,
  surname: String
}

HumanEditableParts manually repeats some of the fields and those need to be kept in sync.

In Rust, it is said this pattern can be avoided with "struct composition". i.e.

struct HumanEditableParts {
  name: String,
  surname: String1
}

struct Human {
  id: u32,
  age: u32,
  username: String,
  editable_parts: HumanEditableParts
}

However, that is not always feasible, nor always pleasant to do.

Dependencies

~1.5MB
~36K SLoC