Skip to content

Generate multiple structs from a single definition through a procedural macro

License

Notifications You must be signed in to change notification settings

resolritter/structout

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

About

Generate multiple structs from a single definition through a procedural macro

Topics

Resources

License

Stars

Watchers

Forks

Languages