7 releases (4 breaking)

Uses old Rust 2015

0.5.1 Nov 27, 2016
0.5.0 Nov 25, 2016
0.4.0 Oct 24, 2016
0.3.0 Oct 19, 2016
0.1.1 Oct 9, 2016

#2613 in Rust patterns

Download history 1/week @ 2023-10-19 11/week @ 2023-10-26 4/week @ 2023-11-02 2/week @ 2023-11-09 5/week @ 2023-11-16 8/week @ 2023-11-23 33/week @ 2023-11-30 1/week @ 2023-12-07 9/week @ 2023-12-14 32/week @ 2023-12-21 11/week @ 2023-12-28 4/week @ 2024-01-04 6/week @ 2024-01-11 12/week @ 2024-01-18 17/week @ 2024-01-25 17/week @ 2024-02-01

52 downloads per month
Used in entropy-rs

MIT license

74KB
1.5K SLoC

Rust 1.5K SLoC // 0.1% comments Shell 189 SLoC // 0.1% comments

Builder Macro

Crates.io Build Status Coverage Status

This crate contains a builder! macro to declare a struct and a corresponding builder.

The macro is inspired from jadpole/builder-macro, and is designed to remove duplication of field declaration, as well as generating appropriate setter methods.

Documentation

Documentation and usage examples can be found at azriel.im/builder_macro

License

The MIT License


lib.rs:

This crate contains two macros to declare a struct and a corresponding builder.

  • data_struct!: The builder returns a Result<StructName, &'static str>
  • object_struct!: The builder returns the declared StructName

The macro is inspired from jadpole/builder-macro, and is designed to remove duplication of field declaration, as well as generating appropriate setter methods.

Background

For usage, please skip ahead to the Usage section.

There are two kinds of structs that this crate aims to support:

  • Data structs: Parameter values are only known at runtime, and failure to build should be handled by the application.
  • Object structs: Parameter values are largely known at compile time, and failure to build means the application no longer works, and should panic.

For data structs, returning a Result allows the caller to handle the failure gracefully. For object structs, any panic!s should be caught by the developer before release. By removing the intermediary Result, the developer also no longer needs to call unwrap(), which makes the code that much more concise.

Usage

Specify the dependency in your crate's Cargo.toml:

[dependencies]
builder_macro = "0.5.0"

Include the macro inside your crate's lib.rs or main.rs.

#[macro_use]
extern crate builder_macro;
#

Examples

Disclaimer: The examples use the data_struct! macro. They are equally valid for the object_struct! macro, the difference being the return type is the struct itself and not a Result.

Non-consuming Builder

The simplest usage of the builder macro to generate a non-consuming builder is:

#
data_struct!(ItemBuilder -> Item {
    required_field: i32,
    defaulted_field: &'static str = "abc",
});

let item = ItemBuilder::new(123).build().unwrap();
let another = ItemBuilder::new(456).defaulted_field("def").build().unwrap();

assert_eq!(123, item.required_field);
assert_eq!("abc", item.defaulted_field);
assert_eq!(456, another.required_field);
assert_eq!("def", another.defaulted_field);

The generated code functions as follows:

#
struct Item {
    required_field: i32,
    defaulted_field: &'static str,
}

/// Auto-generated builder
struct ItemBuilder {
    required_field: Option<i32>,
    defaulted_field: Option<&'static str>,
}

impl ItemBuilder {
    /// Construct the builder
    pub fn new(required_field: i32) -> ItemBuilder {
        ItemBuilder { required_field: Some(required_field), defaulted_field: Some("abc"), }
    }

    /// Build the struct
    pub fn build(&self) -> Result<Item, &'static str> {
        let required_field = self.required_field.clone().ok_or(
            concat!("Must pass argument for field: '", stringify!(required_field), "'"))?;
        let defaulted_field = self.defaulted_field.clone().ok_or(
            concat!("Must pass argument for field: '", stringify!(defaulted_field), "'"))?;

        Ok(Item { required_field: required_field, defaulted_field: defaulted_field })
    }

    #[allow(dead_code)]
    /// Auto-generated setter
    pub fn defaulted_field(&mut self, defaulted_field: &'static str) -> &mut Self {
        self.defaulted_field = Some(defaulted_field);
        self
    }
}

To generate public structs and builders, see visbility.

Consuming Builder

When the generated struct should own trait objects, they cannot be cloned, and so the builder must transfer ownership to the constructed instance.

To generate a consuming builder, instead of using ->, use => between the builder name and the target struct name.

#
trait Magic {
    fn abracadabra(&mut self) -> i32;
}
struct Dust {
    value: i32,
}
impl Magic for Dust {
    fn abracadabra(&mut self) -> i32 {
        self.value
    }
}

// Note: we use => instead of -> for the consuming variant of the builder
data_struct!(MyStructBuilder => MyStruct {
    field_trait: Box<Magic> = Box::new(Dust { value: 1 }),
    field_vec: Vec<Box<Magic>> = vec![Box::new(Dust { value: 2 })],
});

let mut my_struct = MyStructBuilder::new().build().unwrap();

assert_eq!(my_struct.field_trait.abracadabra(), 1);
assert_eq!(my_struct.field_vec[0].abracadabra(), 2);

Visibility

Generate a builder and struct with module private visibility:

#
data_struct!(MyStructBuilder -> MyStruct {
    field_i32: i32 = 123,
    field_str: &'static str = "abc",
});

let my_struct = MyStructBuilder::new()
    .field_i32(456)
    .build()
    .unwrap();
assert_eq!(my_struct.field_i32, 456);
assert_eq!(my_struct.field_str, "abc"); // uses default

Generate a builder and struct with public visibility:

#
mod inner {
    data_struct!(pub MyStructBuilder -> MyStruct {
        pub field_i32: i32 = 123,
        field_str: &'static str = "abc",
    });
}

let my_struct = inner::MyStructBuilder::new()
    .field_i32(456)
    .build()
    .unwrap();
assert_eq!(my_struct.field_i32, 456);

// The next line will fail compilation if uncommented as field_str is private
// assert_eq!(my_struct.field_str, "abc");

Assertions

You may specify assertions after field declarations inside an assertions: { ... } block.

If an assertion fails, the build() method will return an Err(...).

#
data_struct! {
    pub BuilderName -> StructName {
        #[allow(dead_code)]
        a_private_field: &'static str,
        /// a_field is an i32 which must be between 0 and 100 inclusive
        pub a_field: i32 = 50,
    }, assertions: {
        assert!(a_field >= 0);
        assert!(a_field <= 100);
        // Yes you can assert on private fields
        assert!(!a_private_field.is_empty());
    }
}

let result_1 = BuilderName::new("non-empty string").build();
let result_2 = BuilderName::new("").build();

assert!(result_1.is_ok());
assert_eq!(result_2.err(),
           Some("assertion failed: 'assert!(! a_private_field . is_empty (  ))'"));

Full Usage Format

The full macro usage format is:

#
// We declare the builder insider a module simply to demonstrate scope
mod inner {
    data_struct! {
        /// StructName is an example struct.
        /// These docs are copied over to the generated struct.
        pub BuilderName -> StructName {
            // meta attributes are copied over to the struct's fields
            #[allow(dead_code)]
            a_private_field: &'static str,

            /// a_field is an i32 which must be between 0 and 100 inclusive
            pub a_field: i32 = 50,
        }, assertions: {
            assert!(a_field >= 0);
            assert!(a_field <= 100);
            // Yes you can assert on private fields
            assert!(!a_private_field.is_empty());
        }
    }
}

let my_struct = inner::BuilderName::new("a_private_field must be non-empty")
    .build()
    .unwrap();

assert_eq!(50, my_struct.a_field);

No runtime deps