📣 `#[derive(Builder)]` with brand new features

#[derive(Builder)] automatically implements the builder pattern for arbitrary structs.

We started #[derive(Builder)] back in August 2016 with the tools available at that time (i.e. macros by example). It was able to derive some setters for a given struct, but within some hard constraints. Luckily that has changed now. :slight_smile:

:tada: Welcome macros 1.1

With the stabilisation of custom derive in rust 1.15 we are now able to overhaul the derive_builder crate.

The major change is, that we now implement the traditional builder pattern instead of a simplified version. A simple #[derive(Builder)] will now generate a FooBuilder for your struct Foo with all setter-methods and a build method. Previously everything happened on Foo without a dedicated builder struct.

New features include

  • customize builder name, e.g. #[builder(name="MyBuilder")]
  • private setters, e.g. #[builder(private)]
  • prefixes, e.g. #[builder(setter(prefix="with"))]
  • different setter pattern, e.g. #[builder(pattern="immutable")]
  • field specific overrides
  • additional debug info via env_logger, e.g. RUST_LOG=derive_builder=trace cargo test

:scissors: A quick example

Just copy+paste this to give it a try. :slight_smile:

#[macro_use]
extern crate derive_builder;

#[derive(Default, Builder, Debug)]
struct Channel {
    token: i32,
    // opt into setter type conversion
    #[builder(setter(into))]
    special_info: i32,
    // .. a whole bunch of other fields ..
}

fn main() {
    // builder pattern, go, go, go!...
    let ch = ChannelBuilder::default()
        .token(19124)
        .special_info(42u8)
        .build()
        .unwrap();
    println!("{:?}", ch);
}

Note that we did not write any definition or implementation of ChannelBuilder. Instead the derive_builder crate acts on #[derive(Builder)] and generates the necessary code at compile time.

show generated code

This is the generated boilerplate code you didn't need to write. :slight_smile:

#[derive(Clone, Default)]
struct ChannelBuilder {
    token: Option<i32>,
    special_info: Option<i32>,
}

#[allow(dead_code)]
impl ChannelBuilder {
    pub fn token(&mut self, value: i32) -> &mut Self {
        let mut new = self;
        new.token = Some(value);
        new
    }
    pub fn special_info<VALUE: Into<i32>>(&mut self, value: VALUE) -> &mut Self {
        let mut new = self;
        new.special_info = Some(value.into());
        new
    }
    fn build(&self) -> Result<Channel, String> {
        Ok(Channel {
            token: Clone::clone(self.token
                .as_ref()
                .ok_or(
                       "token must be initialized")?),
            special_info: Clone::clone(self.special_info
                .as_ref()
                .ok_or("special_info must be initialized")?),
        })
    }
}

:checkered_flag: Get Started

It's as easy as 1+1:

  1. Add derive_builder to your Cargo.toml either manually or with cargo-edit:
  • cargo add derive_builder
  1. Annotate your struct with #[derive(Builder)]

:book: Further Reading

The documentation covers detailed explanation of all features and tips for troubleshooting. You'll also find a discussion of different builder patterns.

:mega: Feedback

We would like to hear your thoughts and ideas about this new crate. Does it help solve your problem? What do you miss?

:clap: Credits

Special thanks to @killercup and @jer for their feedback and contributions. :slight_smile:

35 Likes

There is a new release :fireworks:

v0.4.0 - 2017-03-25

Added

  • skip setters, e.g. #[builder(setter(skip))]
  • default values, e.g. #[builder(default="42")] or just #[builder(default)]

Changed

  • deprecated syntax #[builder(setter_prefix="with")], please use #[builder(setter(prefix="with"))] instead
  • setter conversions are now off by default (here is why), you can opt-into via #[builder(setter(into))]
  • logging is behind a feature flag to improve build times. To activate it, please add features = ["logging"] to the dependency in Cargo.toml. Then you can use it like: RUST_LOG=derive_builder=trace cargo test.

Fixed

  • use full path for result #39
  • support #[deny(missing_docs)] #37
  • support #![no_std] via #[builder(no_std)] #41

Many thanks to @killercup for taking the time for all the reviews! :wink:

5 Likes

One month and four releases later... :checkered_flag:

0.4.5 - 2017-04-25

Added

  • try_setters, e.g. #[builder(try_setter)]. These setters are exposed
    alongside the normal field setters and allow callers to pass in values which
    have fallible conversions to the needed type through TryInto. This
    attribute can only be used on nightly when #![feature(try_from)] is
    declared in the consuming crate's root; this will change when Rust issue
    #33417 is resolved.
  • customize setter names via #[builder(setter(name="..."))]
  • customize build_fn name via #[builder(build_fn(name="..."))]
  • suppress build method generation via #[builder(build_fn(skip))]
  • derive additional traits via #[builder(derive(Trait1, Trait2, ...))]
  • set field visibility separate from setter visibility via
    #[builder(field(private))] at the field or struct level

Fixed

  • support generic references in structs #55
  • support #![no_std] #63
  • setter(skip) honors struct-inherited and explicit defaults #68

Deprecated

  • #[builder(default)] and #[builder(default="...")] at the struct level will
    change their behaviour in 0.5.0 and construct a default value for the struct,
    instead of all fields individually. To opt into the new behaviour and squelch
    this deprecation warning you can add the struct_default feature flag.
  • builder fields will no longer be public by default in 0.5.0; relying on this
    will now emit a deprecation warning. Fields can be explicitly made public at
    the struct or field level using the new #[builder(field(public))]
    attribute. To squelch this warning and opt-into the new behaviour, use the
    private_fields crate feature or explicitly set field visibility at the
    struct level.

Many thanks to @TedDriggs for implementing most of this! :vulcan: :wink:

4 Likes

And another feature. :sunny:

0.4.6 - 2017-04-26

Added


Thx again to @TedDriggs and @Faern for implementation and discussion about this.

1 Like