macon

Crates.iomacon
lib.rsmacon
version1.2.0
sourcesrc
created_at2023-12-11 22:09:34.124458
updated_at2024-09-15 13:33:31.415239
descriptionAnother builder macro-based generator with its own idioms.
homepagehttps://github.com/loganmzz/macon-rs
repositoryhttps://github.com/loganmzz/macon-rs
max_upload_size
id1065606
size548,967
Logan Mzz (loganmzz)

documentation

README

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();
Commit count: 24

cargo fmt