6 releases (3 stable)

1.2.0 Sep 15, 2024
1.1.0 Aug 26, 2024
1.0.1-beta.1 Jul 14, 2024
1.0.1-beta.0 Apr 7, 2024
0.2.0 Dec 11, 2023

#241 in Rust patterns

Download history 4/week @ 2024-07-20 8/week @ 2024-07-27 1/week @ 2024-08-03 183/week @ 2024-08-24 16/week @ 2024-08-31 185/week @ 2024-09-14 37/week @ 2024-09-21 22/week @ 2024-09-28 2/week @ 2024-10-05 33/week @ 2024-10-12 383/week @ 2024-10-19 395/week @ 2024-10-26 1004/week @ 2024-11-02

1,815 downloads per month
Used in translators

MIT/Apache

340KB
338 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

Features

For any feature, you can find blueprints in ./tests directory 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();

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

#[macro_use] extern crate macon;

#[derive(Builder)]
#[builder(Into=!)]     // Disable for all fields
struct IntoSettings {
  #[builder(Into=!)]   // Disable for specific field
  no_into: String,
  #[builder(Into)]     // Enable (only when disabled at struct level) for specific field
  with_into: String,
}

let built = IntoSettings::builder()
  .no_into(String::from("no value conversion"))
  .with_into("value conversion")
  .build();

assert_eq!(String::from("no value conversion"), built.no_into);
assert_eq!(String::from("value conversion"), built.with_into);

This feature is required to use with dyn trait:

#[macro_use] extern crate macon;

#[derive(Builder)]
struct DynTrait {
  #[builder(Into=!)]
  function: Box<dyn Fn(usize) -> usize>,
}

DynTrait::builder()
  .function(Box::new(|x| x + 1))
  .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