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 |
#2571 in Rust patterns
Used in entropy-rs
74KB
1.5K
SLoC
Builder Macro
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 aResult<StructName, &'static str>
object_struct!
: The builder returns the declaredStructName
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);