Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CustomDefault #3

Closed
killercup opened this issue Aug 6, 2016 · 7 comments
Closed

CustomDefault #3

killercup opened this issue Aug 6, 2016 · 7 comments

Comments

@killercup
Copy link
Collaborator

The builder pattern allows us to set optional values on a struct—but how do we determine the default values for those fields? Rust's default answer (hehe) is of course std::default with its #[derive(Default)] and custom-written impl Default.

At first, we can

  • let the user add #[derive(Default)] so they can do MyStruct::default().setter(42),
  • or impl MyStruct { pub fn new(…) {…} } so they can do MyStruct::new(…).setter(42).

It would be really nice to also offer a way to derive/generate a Default impl with custom values, e.g.:

custom_derive! {
    #[derive(CustomDefault)]
    struct Channel {
        id: Uuid,
        token: Authentication,
        #[default(42i32)] // ← custom attribute
        special_info: i32,
    }
}

(How) should we do this? (I don't think it's necessary to have this early on, and it should probably be a separate crate.)

@colin-kiegel already suggestion this in DanielKeep/rust-custom-derive#18.

@colin-kiegel
Copy link
Owner

Since derive(Builder) aims to improve convenience, we should be looking for a simple way for the user to specify 'custom defaults'.

I think there are three approaches

  1. incorporate customizing defaults of optional fields into derive(Builder)
  2. require a Default implementation for optional fields (not necessarily the struct)
  3. require a Default implementation for the struct (not necessarily its items)

Option 1:

  • +: We can avoid a Default-implementation of required fields.
  • +: Looks convenient to use to me.
  • -: But this increases our problem scope significantly.
    => We may postpone it to a version 2.x, or something like that - at least to get started.

Option 2:

  • +: We can avoid a Default-implementation of required fields.
  • -: Definitely not convenient to use, right? (imagine a struct with multiple fields of the same type, but different defaults - new-type pattern + much extra work? - ugh!).

Option 3:

  • +: Convenient for the user
  • +: Convenient for us to begin with (smaller problem scope).
  • -: User can circumvent required fields.

By the principle of exclusion, I'd say lets start with option 3, i.e. let derive(Builder) require a Default-implementation for the same struct it acts on (for at least versions 0.x - 1.x)

This artificial restriction gives us some legroom (=flexibility):

  • Our perspective: Specifying defaults will not be part of our problem-scope
  • User perspective: The user can implement defaults (a) manually, (b) use derive(Default) or (c) in the future possibly use something like derive(CustomDefault)

@killercup killercup mentioned this issue Aug 6, 2016
@colin-kiegel
Copy link
Owner

As @killercup suggested the CustomDefault-crate could also implement a constructor with some required parameters if not all fields are defaulted. The method name could be user-defined and default to new(...). :-)

@colin-kiegel
Copy link
Owner

Just found rust-derivative - it comes with custom default functionality.🎉 Licensed as MIT+Apache.

https://mcarton.github.io/rust-derivative/Default.html#setting-the-value-of-a-field

#[derive(Debug, Derivative)]
#[derivative(Default)]
struct Foo {
    foo: u8,
    #[derivative(Default(value="42"))]
    bar: u8,
}

println!("{:?}", Foo::default()); // Foo { foo: 0, bar: 42 }

Looks very good. I would have preferred #[CustomDerive] instead of #[derive(Derivative)] #[derivative(Default)] though.

@colin-kiegel
Copy link
Owner

I am preparing a PR for the next days.

Proposed API / Documentation

You can define default values for each field via annotation by #[builder(default="...")],
where ... stands for any Rust expression and must be string-escaped, e.g.

  • #[builder(default="42")]
  • #[builder(default="\"foo\"")]
  • #[builder(default=r#"format!("Hello {}!", name)"#)]
#[derive(Builder, PartialEq)]
struct Lorem {
    #[builder(default=r#"format!("Hello {}!", "World")"#)]
    pub ipsum: String,
}

fn main() {
    // If we don't set the field `ipsum`,
    let x = LoremBuilder::default().build().unwrap();

    // .. the custom default will be used for `ipsum`:
    assert_eq!(x, Lorem {
        ipsum: String::from("Hello World!"),
    });
}

Note: The expression will be evaluated with each call to build.

Discussion

  • A syntax like #[builder(default(...))] would not require escaping, but introduce
    significant parsing constraints. You would not be able to write write arbitrary expressions.
  • This can also be overriden. E.g. #[builder(default="42u32")] can be defined for the whole struct.
    It is then possible to override this field by field. Unset via #[builder(default="")].
  • A custom default will imply the setter to be 'optional' as in optional setters #36. This way 'optional' setters are nothing more than #[builder(default="Default::default()")] - ok, this looks weird. ^^

@TedDriggs
Copy link
Collaborator

If you haven't seen it, serde does a really elegant job handling this (docs).

Using #[serde(default)] will look for a derived or explicit implementation of std::default::Default, while #[serde(default="path")] will allow per-field overrides.

@colin-kiegel
Copy link
Owner

@TedDriggs thx for the hint. I was already thinking about using #[builder(default)] as shorthand to use Default::default(). Aligning with serde would be another argument to do this. :-)

colin-kiegel added a commit that referenced this issue Mar 19, 2017
e.g. `#[builder(default="\"Hello World!\"")]`
colin-kiegel added a commit that referenced this issue Mar 20, 2017
e.g. `#[builder(default="\"Hello World!\"")]`
or just `#[builder(default)]` without value to delegate to `Default`
colin-kiegel added a commit that referenced this issue Mar 20, 2017
e.g. `#[builder(default="\"Hello World!\"")]`
or just `#[builder(default)]` without value to delegate to `Default`
colin-kiegel added a commit that referenced this issue Mar 23, 2017
implement custom defaults #3 and optional setters #36
@colin-kiegel
Copy link
Owner

colin-kiegel commented Mar 25, 2017

published with v0.4.0

fyi @shssoichiro @JuanPotato

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants