Live data from Hacker News

Builder Pattern in Rust

greyblake.com

61–70 of 93 posts

Re: Builder Pattern in Rust

#61

Currently this is the only thing slightly bothering me when writing Rust code, everything else is an absolute blast and I'm loving every second of it. Hopefully an industry wide best-practice will develop soon on how to deal with the problems outlined in the article. Does anybody know what a Phantom Builder is? The article teases it but doesn't say anything.

In my personal projects I usually define macros. They can handle a varying number of arguments, which helps a lot.

Though sometimes Rust macros feel just a bit too strict for me, the way they are checked outright prevents some uses. At the same time they've been really helpful for letting me define a lot of options with a compact syntax.

Re: Builder Pattern in Rust

#62

Earlier quoted context omitted.

Default only assigns parameters to their default value based on their type (e.g. "0" for u8), it's not an actual implementation of actual default parameters. More details about this and what else is lacking in Rust compared to Kotlin: https://medium.com/@cedricbeust/what-rust-could-learn-from-k...

You can just implement the Default trait yourself and set sane defaults right? At least that’s how I use it, implement the default trait manually to easily return a initialized struct.

Yes, I was referring to when you derive the trait, I should have been more specific.

Also, this doesn't help at all for function invocation, which is still very cumbersome in Rust in the absence of default parameters, named parameters, and overloading.

Re: Builder Pattern in Rust

#63
post #46

Earlier quoted context omitted.

At the risk of being pedantic, I would use separate types there for the id's to avoid this kind of issue, but in general I agree with your point

There's an interesting commonality here: syntactically, `function(Type1(int), Type2(int))` and `function(type1=int, type2=int)` are almost identical. They differ in that types can help guard against errors elsewhere (within or without that function call), but named arguments let you not care about the order. I've always wondered: what if languages had no guaranteed argument order, but let you skip explicit naming if…

It's an interesting thought. Personally I don't mind explicit ordering, since it's just one more way to cross-check correctness. Also I can imagine leaning on the type inference to resolve which argument is which might not be the best for compile times.

Re: Builder Pattern in Rust

#64
post #41

Personally I think if you need a builder, you did something wrong in the first place. If you have a struct with so many defaultable values, you should break it into smaller pieces. Reasonable struct should not need more than 5 properties, unless it's something like ORM mapping of database table.

It's a nice rule of thumb, but it's not always practical. For instance, something like a vulkan compute pipeline has a ton of parameters just due to the complexity of the domain, and breaking it down into sub-objets to a greater extent than is already done would just add artificial hierarchy.

Re: Builder Pattern in Rust

#65
post #4

Earlier quoted context omitted.

I agree. I’ve seen a few libraries (I can’t remember which) instead take the approach of a constructor that takes a single-use struct as an argument. The struct implements Default and all of its members are public. You can then use ..Struct::default() to splice in the default values, and override only the values you want to set. It ends up being an approximation of named optional arguments, albeit with a somewhat mor…

Yeah, I've seen it called the Init Struct pattern: https://xaeroxe.github.io/init-struct-pattern/

That reminds me of a library where I’ve seen it in use: wgpu. For example https://docs.rs/wgpu/0.11.0/wgpu/struct.TextureViewDescripto...

Re: Builder Pattern in Rust

#66
In a relevant sense, common occurrence of a pattern reveals a weakness in the language where it appears, because no one has succeeded in capturing it in a library so it doesn't need to be coded again.

Thus, C programs are shot through with hash table implementations, because a generally usable hash table library is not possible in C. Rust has a good one in the standard library, so Rust programs with a custom hash table are vanishingly rare.

Ideally, when a pattern is recognized, the language gets a new fundamental primitive that enables constructing the library. Sometimes that is too hard, and the pattern becomes itself a new core language feature. After that, sometimes an enabling fundamental feature is finally added later, and the frozen pattern becomes redundant.

Thus, each big built-in language feature identifies a failure (at some past time) to capture it in a library for lack of the building blocks that would have been needed. Those might have since appeared, but the built-in is still more idiomatic than using a library.

Each pattern represents an opportunity to strengthen the language with a new primitive that would enable eliminating need for any more instances of the pattern. But there are always new patterns, so always more such opportunities. And, that's how a language evolves.

Re: Builder Pattern in Rust

#67
post #45

Earlier quoted context omitted.

OT, but how can a value that lives on the heap be const? And how could ownership of a (non-Copy) const be passed somewhere else?

The trick is that an empty string doesn’t actually own any data on the heap. It’s basically a null pointer, and all string methods check for that in some way. This is a typical optimization trick that many languages implement (empty lists and strings are very common). Defining new to be const just makes this optimization a requirement.

But still- at a language semantics level, values can't have multiple owners, and String couldn't implement Copy just for this one case. So how does this reconcile with the borrow-checker?

Or can consts have multiple owners since they're immutable and have a static lifetime? Is this just the first case of a non-Copy being const, so the question has never come up before?

Re: Builder Pattern in Rust

#68
post #46

Earlier quoted context omitted.

At the risk of being pedantic, I would use separate types there for the id's to avoid this kind of issue, but in general I agree with your point

There's an interesting commonality here: syntactically, `function(Type1(int), Type2(int))` and `function(type1=int, type2=int)` are almost identical. They differ in that types can help guard against errors elsewhere (within or without that function call), but named arguments let you not care about the order. I've always wondered: what if languages had no guaranteed argument order, but let you skip explicit naming if…

I'm not aware if this exact idea, but dependency injection frameworks have a similar concept where the parameter order doesn't matter and the framework figures out the right order when it calls functions based on the types.

It's probably really complicated at the language level if it also has to interact with overloading, subtyping, and generics.

Re: Builder Pattern in Rust

#69
post #46

Earlier quoted context omitted.

Named parameters in C# make refactoring far less error-prone too. Consider a method that accepts two int parameters: “DeleteUser(int tenantId, int userId)” - if a call-site was DeleteUser(1,2) and you swapped the parameter arguments you’d be in trouble. Having “DeleteUser(tenantId: 1, userId: 2)” makes code not only safer, but also far more self-documenting.

At the risk of being pedantic, I would use separate types there for the id's to avoid this kind of issue, but in general I agree with your point

What would you do about a slice function? Without the parameter names you won't even see if the second parameter is length of the slice or its right end.

Re: Builder Pattern in Rust

#70
post #69
post #46

Earlier quoted context omitted.

At the risk of being pedantic, I would use separate types there for the id's to avoid this kind of issue, but in general I agree with your point

What would you do about a slice function? Without the parameter names you won't even see if the second parameter is length of the slice or its right end.

Yeah I agree there are examples where types are not enough to disambiguate arguments.
Post reply on HN