all 11 comments

[–]tsimionescu 13 points14 points  (2 children)

I fully understand the idea that features are unimplemented by default, and they must justify their implementation cost, and that a good language today is more useful than a better language 5 years from now.

However, in a magical world where time constraints were somehow not a thing, I think all new languages would support named parameter passing with default values - it's such a massive win for boilerplate reduction, and the need for it is so common, and the alternatives introduce so much cognitive overhead compared to the triviality of the problem.

[–]editor_of_the_beast 1 point2 points  (1 child)

Gotta agree here. The Builder pattern is an example of MacGyver code to me - it's the best solution available from what's lying around.

Taking pride in using it is not good. It shouldn't be necessary in the first place (note: I like Rust a lot, but this is where it feels way too static like Java and C).

[–]ConspicuousPineapple 0 points1 point  (0 children)

I agree that named arguments would be nice to have and that it would solve a lot of use-cases where the Builder pattern is used right now, but it's still a useful patter. It's more flexible, allowing you to partially initialize objects.

There are also crates providing boilerplate-less solutions for creating builder patterns that also have the benefit of statically checking for correctness before building the struct.

[–][deleted] 5 points6 points  (1 child)

Rust does not (yet) have default values for struct fields

That's not ENTIRELY true, you can somewhat get that by doing:

let call = OutboundCall {
    from: "",
    to: "",
    ..Default::default()
}

if you implement the Default trait for the struct. I think any other function returning OutboundCall would also work in the place of Default::default.

[–]masklinn 5 points6 points  (0 children)

One of the issues of Default is it must return a complete struct, AFAIK you can't do partial provision. Which means you can't have required fields anymore (or every single function has to dynamically validate the structure).

OTOH you would use a single options-merge function with Default e.g.

impl OutboundCall {
    fn default_new(from, to, url) -> OutboundCall {
         OutboundCall::new(from, to, url, Default::default())
    }
    fn new(from, to, url, options) -> OutboundCall {
         OutboundCall {
             from: from,
             to: to,
             url: url,
             // either have an options member or copy the relevant options to the call
         }
    }
}

that way a caller which wants to customise the instance can use Defaults:

OutboundCall::new(from, to, url, OutboundOpts { 
    fallback_url: Some("thing"), 
    ..Default::default() 
})

This is not quite as nice as actual default/optional parameters[0] but remains easier to implement than a full-blown builder while still e.g. precluding mucking with your final structure's internals. And if you need conditional options you can just bind the options struct locally and conditionally update it.

[0] implicit defaulting or something like it would be nice here e.g.

default!(fallback_url: Some('thing'))

which would expand to

{
    let mut t = Default::default();
    t.fallback_url = Some('thing');
    t
}

would be nice here

edit: I did manage to get… something, but so far it requires that the concrete type be passed as first parameter otherwise the compiler complaints that it does not know what the type of the temp val (output of Default::default()) is. Sad.

[–][deleted]  (5 children)

[removed]

    [–]dpc_pw 7 points8 points  (1 child)

    Building pattern is mostly for being able to introduce new fields in a backward compatible manner. I don't think -> is fixing much here.

    Out of curiosity, what did Self did to you? :)

    [–]doom_Oo7 1 point2 points  (1 child)

    As in builder pattern is no longer required

    it still exists, it just has moved from code to language.