builder_macro 0.5.1

A macro to generate structs and a corresponding builder

Crate builder_macro [] [src]

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);

Macros

data_struct

Macro to declare a struct and a corresponding builder that returns a Result<T, &'static str>. See the module documentation for more.

declare_structs

Declares the type struct and its corresponding builder struct.

object_struct

Macro to declare a struct and a corresponding builder that returns a Result<T, &'static str>. See the module documentation for more.