3 releases (1 stable)

1.0.1-beta.0 Apr 7, 2024
1.0.0 Feb 2, 2024
0.3.0 Jan 30, 2024
0.2.0 Dec 11, 2023

#317 in Rust patterns

Download history 6/week @ 2024-01-26 7/week @ 2024-02-02 9/week @ 2024-02-16 35/week @ 2024-02-23 25/week @ 2024-03-01 3/week @ 2024-03-08 2/week @ 2024-03-15 2/week @ 2024-03-29 50/week @ 2024-04-05 9/week @ 2024-04-12

61 downloads per month

MIT/Apache

335KB
249 lines

Another builder macro-based generator with its own idioms.

"Maçon" is French translation for "builder"

Usage

#[macro_use] extern crate macon;

#[derive(Builder)]
struct MyType {
  integer: i32,
  string: String,
  optional: Option<String>,
}

let _mytype: MyType = MyType::builder()
    .integer(42)
    .string("foobar")
    .build();
  • adds a builder struct (<TargetStruct>Builder)
  • build struct implements Default
  • adds a builder() function to target struct to initialize a new builder
  • each target struct field can be set with function of same name and parameter of same type
  • use build() function to create new target struct instance
  • any unset field will make build() call not compile (default)
  • setter argument is generic over Into
  • Option fields are not mandatory. And setters use wrapped type.

Settings

Settings are set using #[builder()] attribute.

struct

field

Configuration

Some additional configuration can be passed by adding a macon-config.yaml at crate root (also supports extesions .yml and .json).

Content example:

version: "1" # Ignored

default_types:
  defaults: true
  includes:
  - my_crate::a_module::Foobar
  excludes:
  - usize

option_types:
  defaults: true
  includes:
  - my_crate::a_module::MyOption
  excludes:
  - core::option::Option

Syntax description (all keys are optionals):

  • version
    Define configuration syntax version. Currently ignored.

  • default_types
    Configure automatic Default types.

  • default_types.defaults
    Include (or not) default values for default_types. Supported values: true (default) or false.

  • default_types.excludes
    List of pathes (e.g. std::string::String) to exclude (even if included). Default to empty.

  • default_types.includes
    List of pathes (e.g. my_crate::a_module::Foobar) to include. Default to empty.

  • option_types
    Configure automatic Default types.

  • option_types.defaults
    Include (or not) default values for default_types. Supported values: true (default) or false.

  • option_types.excludes
    List of pathes (e.g. std::string::String) to exclude (even if included). Default to empty.

  • option_types.includes
    List of pathes (e.g. my_crate::a_module::MyOption) to include. Default to empty.

Features

For any feature, you can find blueprints in [./tests directory)tests showing code generated by macro.

Typestate pattern (default)

Blueprints:

By default, builder rely on typestate pattern. It means state is encoded in type (using generics). Applicable functions are implemented (callable) only when state (type) matches:

  • Build function build() when all properties has been set
  • Each property setter function as long as property haven't been set

Optionally, you can set it explictly:

#[macro_use] extern crate macon;

#[derive(Builder)]
#[builder(mode=Typestate)]
struct MyType {
  integer: i32,
  string: String,
}

Panic on build()

Blueprints:

By default, builder rely on typestate pattern to avoid misconfiguration by adding compilation constraint. You can switch to a builder that just panic when misconfigured:

#[macro_use] extern crate macon;

use std::path::PathBuf;

#[derive(Builder)]
#[builder(mode=Panic)]
struct MyType {
  integer: i32,
  path: PathBuf,
}

let _mytype: MyType = MyType::builder()
    .integer(42)
    .build();

Result on build()

Blueprints:

By default, builder rely on typestate pattern to avoid misconfiguration by adding compilation constraint. You can switch to a builder that returns a Result:

#[macro_use] extern crate macon;

use std::path::PathBuf;

#[derive(Builder)]
#[builder(mode=Result)]
struct MyType {
  integer: i32,
  string: String,
}

let myTypeResult: Result<MyType,String> = MyType::builder()
    .integer(42)
    .build();

assert_eq!(
  Err(String::from("Field path is missing")),
  myTypeResult.map(|_| ())
);

Tuple

Blueprints:

Tuples are struct with unamed fields. Then set<ordinal>() is used as setter:

#[macro_use] extern crate macon;

#[derive(Builder)]
struct MyTuple(
  i32,
  Option<String>,
  String,
);

let _mytuple: MyTuple = MyTuple::builder()
    .set0(42)
    .set2(String::from("foobar"))
    .build();

Only for Typestate mode, you can use set(), none(), keep() and default() calls to assign values in order:

#[macro_use] extern crate macon;

#[derive(Builder)]
struct MyTuple(
  i32,
  Option<String>,
  String,
);
let _mytuple: MyTuple = MyTuple::builder()
    .set(42)
    .none()
    .set(String::from("foobar"))
    .build();

Into argument

Blueprints:

Setter function argument is generic over Into to ease conversion (especially for &str):

#[macro_use] extern crate macon;

#[derive(Builder)]
struct MyTuple(
  String,
);
let _mytuple: MyTuple = MyTuple::builder()
    .set("foobar")
    .build();

Implement Into

Blueprints:

Builders implement Into for target type (and reverse From also). Except for Result mode which uses TryInto / TryFrom.

#[macro_use] extern crate macon;

#[derive(Builder)]
struct MyStruct {
  value: String,
};
let _mytuple: MyStruct = MyStruct::builder()
    .value("foobar")
    .into();

Option fields

Blueprints:

As their name suggests, Option fields are facultative: you can build instance without setting them explicitly.

Setter argument are still generic over Into but for wrapped type. No need to wrap into an Option:

#[macro_use] extern crate macon;

#[derive(Builder)]
struct WithOptional {
  mandatory: String,
  optional: Option<String>,
}

let built = WithOptional::builder()
  .optional("optional value")
  .mandatory("some value")
  .build();

assert_eq!(Some(String::from("optional value")), built.optional);

You can set them explicitly to None with <field>_none() or none() for ordered setter:

#[macro_use] extern crate macon;

#[derive(Builder)]
pub struct WithOptional {
  mandatory: String,
  optional: Option<String>,
}

let built = WithOptional::builder()
  .optional_none()
  .mandatory("some value")
  .build();

assert_eq!(None, built.optional);

Note: In order to detect optional fields, field type name must match:

  • core::option::Option
  • ::core::option::Option
  • std::option::Option
  • ::std::option::Option
  • Option

You can disable Option support by using #[builder(Option=!)] at struct or field level:

#[macro_use] extern crate macon;

#[derive(Builder)]
#[builder(Option=!)]
struct DisableOptionStruct {
  optional: Option<String>,
}

let built = DisableOptionStruct::builder()
  .optional(Some(String::from("mandatory value")))
  .build();

assert_eq!(Some(String::from("mandatory value")), built.optional);

If you use an alias, use #[builder(Option=<WrappedType>)] at field level to enable Option support:

#[macro_use] extern crate macon;

type OptString = Option<String>;
#[derive(Builder)]
struct AliasedOptionStruct {
  #[builder(Option=String)]
  optional: OptString,
}

let built = AliasedOptionStruct::builder()
  .optional("aliased value")
  .build();

assert_eq!(Some(String::from("aliased value")), built.optional);

Default struct

Blueprints:

If struct derives Default, all fields are then optional and values are kept from default instance:

Note: In order to detect Default derive, Builder derive attribute must be placed before other derive attributes.

#[macro_use] extern crate macon;

#[derive(Builder,)]
#[derive(PartialEq,Debug,)]
#[builder(Default,)]
struct DeriveDefaultStruct {
  integer: usize,
  string: String,
  optional: Option<String>,
}

impl Default for DeriveDefaultStruct {
    fn default() -> Self {
        DeriveDefaultStruct {
            integer: 42,
            string: String::from("plop!"),
            optional: Some(String::from("some")),
        }
    }
}

let built = DeriveDefaultStruct::builder()
  .build();

assert_eq!(
  DeriveDefaultStruct {
    integer: 42,
    string: String::from("plop!"),
    optional: Some(String::from("some")),
  },
  built,
);

In case Default derive detection is undesired, you can disable it with #[builder(Default=!)].

On the other hand, if have your own Default implementation, you can add #[builder(Default)] to enable support.

#[macro_use] extern crate macon;

#[derive(Builder,)]
#[derive(PartialEq,Debug,)]
#[builder(Default,)]
struct CustomDefaultStruct {
  integer: usize,
  string: String,
  optional: Option<String>,
}

impl Default for CustomDefaultStruct {
    fn default() -> Self {
        CustomDefaultStruct {
            integer: 42,
            string: String::from("plop!"),
            optional: Some(String::from("some")),
        }
    }
}

let built = CustomDefaultStruct::builder()
  .build();

assert_eq!(
  CustomDefaultStruct {
    integer: 42,
    string: String::from("plop!"),
    optional: Some(String::from("some")),
  },
  built,
);

You can keep default value (from default built instance) explicitly with <field>_keep() or keep() for ordered setter:

let built = CustomDefaultStruct::builder()
  .integer_keep()
  .string("overriden")
  .optional_none()
  .build();

assert_eq!(
  CustomDefaultStruct {
    integer: 42,
    string: String::from("overriden"),
    optional: None,
  },
  built,
);

Default fields

Blueprints:

If field implements Default, it is then optional and value is:

  1. kept from default instance if struct derives Default,
  2. or, initialized with default value.
#[macro_use] extern crate macon;

#[derive(Builder)]
#[derive(Debug,PartialEq,)]
struct WithDefaultFields {
  integer: usize,
  string: String,
  optional: Option<String>,
}

let built = WithDefaultFields::builder()
  .build();

assert_eq!(
  WithDefaultFields {
    integer: 0,
    string: String::from(""),
    optional: None,
  },
  built,
);

You can set them explicitly to default with <field>_default() or default() for ordered setter (e.g. override default instance value):

#[macro_use] extern crate macon;

#[derive(Builder)]
#[derive(Debug,PartialEq,)]
struct WithDefaultFields {
  integer: usize,
  string: String,
  optional: Option<String>,
}

let built = WithDefaultFields::builder()
  .integer_default()
  .string_default()
  .optional_default()
  .build();

assert_eq!(
  WithDefaultFields {
    integer: 0,
    string: String::from(""),
    optional: None,
  },
  built,
);

In order to detect default fields, field type name must match (leading :: and module path are optionals):

  • bool
  • char
  • f32
  • f64
  • i8
  • i16
  • i32
  • i64
  • i128
  • isize
  • str
  • u8
  • u16
  • u32
  • u64
  • u128
  • usize
  • std::string::String
  • core::option::Option
  • std::option::Option
  • std::vec::Vec
  • alloc::vec::Vec
  • std::collections::HashMap
  • std::collections::hash_map::HashMap
  • std::collections::HashSet
  • std::collections::hash_set::HashSet

If you use an alias or unsupported type, use #[builder(Default)] at field level to enable Default support:

#[macro_use] extern crate macon;

#[derive(Builder)]
#[derive(Debug,PartialEq,)]
struct ExplicitDefaultOnField {
  #[builder(Default)]
  boxed: Box<usize>,
}

let built = ExplicitDefaultOnField::builder()
    .build();

assert_eq!(
  ExplicitDefaultOnField {
    boxed: Box::from(0),
  },
  built,
);

You can disable Default support by using #[builder(Default=!)] at field level:

// Don't compile
#[macro_use] extern crate macon;

#[derive(Builder)]
struct DisableDefaultOnField {
  #[builder(Default=!)]
  integer: usize,
}

DisableDefaultOnField::builder()
  .integer_default()
  .build();

Dependencies

~2–3MB
~61K SLoC