Pre-RFC: Named arguments

But those features don't currently exist, so the language design question at hand is "should we add them".

In any case, my answer to your question: I prefer the builder pattern to any use of optional/varying arguments or varying ways to pass arguments, as well as to default argument values. Leaving aside issues of clarity and orthogonality, I also like the ability to use typestate to statically prevent invalid combinations of arguments, which would otherwise require a much more sophisticated system for handling optional/default/etc arguments. (I've most commonly seen optional/default/etc arguments handled via runtime code that detects which ones you've passed and asserts against invalid combinations; I'd much prefer static checking.)

For the specific case of "mandatory argument that seems ambiguous or unclear", I'd prefer either naming the method differently (e.g. reserve_additional/reserve_more or similar, or insert_at), or using types to prevent incorrect usage (e.g. for flags arguments), or using builders if necessary but not necessarily as a first choice.

There's also rust-analyzer, which is not a substitute for language features, but in the specific case of "see the documentation or parameter names/types while writing code" it seems relevant.

Because it's almost never spelled my_vec.insert(2, 3); it's more commonly spelled my_vec.insert(i, value) or similar, such that it's obvious from context which parameter is which.

6 Likes

If those were in Rust, I would use them occasionally. But for the vast majority of cases where I use builders in the APIs of libraries, I would still use builders even if those features existed. You can see examples in at least the following crates: regex, aho-corasick, regex-automata, csv, globset and probably others that I'm forgetting.

The main reason why I would keep using builders in those cases is because they don't front load the complexity of construction like named+default parameters do. And in practice, builders are given space to breathe in API docs on a way that is atypical for named+default parameters.

My personal favorite example of named+default parameters run amok is the read_csv function from pandas: pandas.read_csv โ€” pandas 2.1.3 documentation

Reminder: I am expressing a matter of taste. I chose to respond because you said you literally couldn't see a single reason why someone would do what I would do. My only purpose here is to give you a reason. My intent is not to argue that you should have the same reasons, opinions, preferences, judgment, taste, aesthetic and communication style as me.

16 Likes

Just as a reminder since I can see the conversation diverging once again, I explicitly left out optional parameters in my original text, especially for issues like this and also because the way I proposed overloading would not work with optional parameters.

2 Likes

Full support for this sentiment as well.

2 Likes

Thanks for the real world examples of much-used builders, and for saying named arguments are at least considerable for you. I think the arguments you make are correct and highlight that named arguments are not supposed to be a feature used in every function of every crate but as a filler for the case ยซ several parameters where some more clarity would be nice while a builder would be heavy ยป.

To be clear, I don't think named+default (or just named) argument should be added to the language. I don't think they carry their weight.

1 Like

I use Intellij's "Parameter Hints". I couldn't live without it: Screen Shot 2022-08-21 at 3.48.26 PM It's a shame that it has to display the names of all the parameters, including the obvious ones. It would be interesting to know what percentage of users use this feature.

1 Like

Thank you for the reminder of that. You're right, your proposal in this thread is only proposing names and overloading.

For the specific proposal in the thread, I continue to think that the approach to overloading results in too much added complexity / non-orthogonality when attempting to name a function's type or a variant of a function.

The portion of the proposal for for naming arguments without overloading (syntax questions aside) seems more reasonable. However, I personally would prefer not to have it, and I don't think it carries enough weight to balance the added complexity.

It's not obvious to me how this function could be improved with the builder pattern. Sure, this function has too many parameters, but wouldn't you have to define just as many "builder methods"?

This is a really good example of a problem!

I don't think it would be particularly hard to add a mechanism to give "hints" to tools like r-a and similar, to tell it "this argument is so obvious for this function that it's not worth showing the name of it", and then by default (as an option people could disable if they prefer) editors/IDEs could hide the names of such parameters.

I gave you a real world alternative that I designed and that I think is a vast improvement over read_csv: ReaderBuilder in csv - Rust

Again, I feel obliged to continue to point this out: I am not stating what I believe to be objective truth. I am stating a conclusion that is derived from my own taste and judgment.

The problem is not the number of parameters. The problem is its presentation and how the API is digested. I am not making a simplistic argument. It's subtle. Notice how, for example, each method gets its own "space to breathe." Each configuration knob (of which there are many) gets its own API example showing how to use it. Compare that with read_csv: all of the parameters and their docs are squashed together.

I have had this same kind of back-and-forth many times over the years. I would really appreciate it if we just left it at what I've said so far now that you have been shown the existence of a reason, even if you don't agree with it. For example, a common next step in this sort of discussion (in my experience) is to try and talk about how the API documentation for named+default parameters could be "improved" to a point that matches what I think is "good" about today's builder docs. That becomes a tedious discussion that I really just don't want to have.

I apologize for all of the caveats here. I'm just really really really trying to avoid getting sucked into a tit-for-tat about this. Because I've been there many times before about this exact topic.

11 Likes

Why? I never claimed to be stating objective truth, nor did I claim that anyone else was.

It's trivially easy to come up with bad reasons for something. The real debate is about how good the reasons are for something, not whether or not reasons exist for it.

It's completely up to you whether you want to continue this discussion.

There's nothing preventing the authors of the pandas.read_csv documentation from including an API example for each parameter. And if you think the parameters don't have enough "space to breathe," then you could always add more blank lines in between them.

1 Like

Given you've decided to drag me back here, I'll just point to you you're own words above.

A person who has only basic knowledge and definitely no experience in any subject should not come a major very bold and definitive statements to experienced people in said subject.

I would not buy a house built by a crew where the apprentice brick layer tells the architects and engineers how to design a house. Would you?

If you barely know Rust, surely you ought to first spend some time to learn what rust and especially idiomatic Rust looks like and understand why that is before coming here with an attitude to tell us off.

While there is a common base for all of programming, there are differences in style and approach between different languages and ecosystems. You can't just barge in here with your preconceived notions from swift or Python and just assume we are here all stupid wrong.

7 Likes

You said this:

it's hard for me to think of reasons to use the builder pattern instead.

I gave you some reasons. And I acknowledged that I wasn't trying to convince you to adopt my perspective. I was merely giving you the existence of a reason that is used in non-trivial situations in popular crates I've published. Why? Because you said you couldn't think of any.

I think you're taking this discussion into unproductive territory with this comment personally.

Right. I've put you on mute. So I won't see any future replies from you.

5 Likes

Yes, but what I really meant was that I couldn't think of any good reasons. I thought that went without saying.

No, I just genuinely think your argument about "space to breathe" is a really really bad argument, and so I decided to inject some humour into my response. Let me elaborate: If you think the parameters need more "space to breathe", then that means the UI of the docs is badly designed; it doesn't mean you need to use a different language construct.

I never said anyone is stupid. Yes, I think the claim that named parameters are a bad idea is wrong. Let's talk about it. I have plenty of experience with languages with and without named parameters, and I think I understand rust well enough to claim that they would be a useful addition to the language.

1 Like

I use C# at $RealJob, which has those features. But I find myself using them less and less over time.

And I don't think it's just me, either. Take the new .NET APIs for Azure, for example: ChangeFeedOptions Class (Microsoft.Azure.Documents.Client) - Azure for .NET Developers | Microsoft Learn.

That's basically a builder-like class, though done with mutable properties instead of methods. It doesn't even have an all-the-things-as-optional-parameters constructor, even though C# also has named parameters, so it could have.

And then that class is passed to the DocumentClient.CreateDocumentChangeFeedQuery Method (Microsoft.Azure.Documents.Client) - Azure for .NET Developers | Microsoft Learn method, which only has two parameters and thus doesn't need named parameters nor optional ones. The two parameters have very different types, so doesn't need names to distinguish them.

And it's overridden, but only to accept both String and Url, which Rust would either not do or just do as one thing using the same kind of pattern as impl AsRef<Path>.

So what I really want is @ekuber's RFC to make it easier to just initialize structs like that in Rust, without needing all the manual work to make methods for all the things. As far as I can tell I'm perfectly fine without optional/named parameters in Rust.

12 Likes

I second this. I use the inlay hint feature and sometimes it just feels too much because every parameter is displayed. It would be really nice to see such a feature in r-a.

At call site, for simple cases (like vec.insert(i, value)), I think that both named argument and the builder pattern are equaly nice to use.

Currently I named argument are easier to write when declaring the function. One way to make the discussion move forward would be to find a way to make it easier/shorter to create a builder to the point that named argument would not have any advatages anymore. And this would probably be for the best because we could use the same tool (named argument) no matter the complexity of API being implemented.

1 Like