Pre-RFC: Named arguments

There are multiple crates that make writing a builder as easy as adding an #[attribute].

Builders are not always appropriate I think, especially for functions with one or two arguments, and needing a proc-macro dependency to make easy to write is not something I consider as a valid solution. It’s easy yes, but costly, and the more it’s used the higher the cost. For small projects proc macros are a big part of the compilation time.

6 Likes

While I don't necessarily agree with this, it seems worth noting that the latter half of this isn't an argument for named arguments over builders, it's an argument for something built into the language or standard library; we could add native support for builders just as easily as native support for named arguments.

Yes if builders were easier to build that would be very nice. I still don’t think they would be useful for functions with 2 arguments that are not complex types.

In my original post I said that builders and named arguments are not opposed, I even believe they are complementary, I still think that is the case.

For a function of one argument, I really don't see the value in a named argument. Vec::with(capacity: 10) is not better than Vec::with_capacity(10) -- especially if it's used in something like .map(Vec::with).

And hopefully we can agree that a function taking 9 bools isn't a good thing even if you name all the parameters in the call. (At least, one should arguably have 512 tests for it, and I know I would never actually write that many unit tests for it.)

But types also help here, since like my example above, if you're taking, say, an impl Read and a &ZipOptions, making the caller name those arguments seems minimally helpful at best, since getting them in the wrong order won't compile anyway.

So basically this is a huge hammer, but it's really only helping function with a few-but-not-too-many functions in code that might be suffering from PrimitiveObsession. And I don't think the tradeoff is worth it—all the details of flowing through parameter names everywhere is hugely complicated.

Is it sometimes handy? Sure. I'm certainly guilty in C# of just slapping a bool on something and using code review to encourage putting the name on it in callers. But I bet we can find other ways of doing something similar in Rust that are less impactful.

As one possible sketch (that if you want to explore should probably be a different thread, not this already-long Pre-RFC one), one could imagine us adding syntax for anonymous structs.

Perhaps one could write this in a library

pub fn foo(uri: Uri, options: &struct { track_cookies: bool, timeout: Option<Duration> }) -> Response;

and call it with something like

let response = foo(uri, &.{ track_cookies: true, timeout: None });

because the declaration desugared to

mod pub_in_priv_hack {
    pub struct Options { 
        track_cookies: bool,
        timeout: Option<Duration>,
    }
}
pub fn foo(uri: Uri, options: &pub_in_priv_hack::Options) -> Response;

That would get the same kind of goodness of named parameters -- low syntactic overhead in the declaration and the caller -- without introducing new stuff to the type system.

For example, you'd be able to use struct literal shorthand if you have a local of the same type as the parameter without adding new rules. And you'd be able to make an instance of the options type to be able to pass the same values to multiple calls without needing to repeat all the names every time. And it would be usable as a binary fn(Uri, _) -> Response without needing to add named-parameter-function-pointers as a new thing. Etc.

That sketch has its problems too, of course, but hopefully it works as a demonstration that there at least exist other possible ways of doing something like this that are much more targeted than the kind of pervasive language impact that named parameters would probably have (as this RFC demonstrates).

8 Likes

I used to want named arguments as they can be helpful in other languages. But the past year or so I have been needing them less and less for many cases as my IDE provides a (IMHO) better solution to the "what is the Nth parameter?"

Rust - IntelliJ IDEs Plugin | Marketplace (inlay parameter hints).

When JetBrains introduced this feature it made working on code so much nicer as I could "see" the names of the parameters for ordered parameter calls. And the IDE even optimizes things such as when the variable passed is named the same as the parameter name it doesn't show the inlay hint.

The scenario this doesn't address is optional parameters, but since rust does not support those anyways, and the builder pattern better fits that scenario IMHO. And that is usually where I grab for named parameters in other languages is when I have many optional parameters.

1 Like

Why do people always resort to those tired strawman arguments in the discussions about named & default parameters? That bullshit can be raised as an objection to any feature whatsoever.

Hopefully we can agree that a function taking 19 arguments isn't a good thing even if all parameters are strongly typed. How about we limit function to taking a single argument, works great for Haskell.

Hopefully we can agree that computing zeroes of zeta function at compile time isn't a good idea. Shouldn't implement const functions so people don't abuse them.

Hopefully we agree that a builder with 1024 methods isn't a good idea, even if all methods are well-named.

More strawmans. Not every method has the obvious name of its arguments in its name. Are you claiming that you don't see the value of vec.reserve(additional: 5) over vec.reserve(5)?

Do you think that rng.gen_ratio(numerator: striped, denominator: colored) is in no way better than rng.gen_ratio(striped, colored)?

So why don't we name the variables with single letters and types with hashes of their contents? If you mess them up, it won't compile anyway, right? Obviously the proper names improve readability and help against errors which can't be prevented by the type system, and those always exist.

"Won't compile" also doesn't help the least when reviewing a PR on Github, or reading source on docs.rs. The popularity of inline parameter names in IDEs demonstrates that there is value in explicit parameter names regardless of type system shenanigans.

That's a very opinionated statement which you won't be able to prove.

5 Likes

This is itself a strawman, because that's not what I said.

Applying that example to what I did say, I see no value in vec.reserve(additional: 5) over vec.reserve_additional(5). If that extra word is necessary, then in a unary function it can certainly be put in the function name. (Whether that's a good approach for binary-or-more functions is a different discussion -- I would agree that it gets progressively worse for more parameters, for example -- but for one parameter I think it's clearly equivalent.)

Whether reserve+additional or just reserve is better for that method on Vec a clarity-vs-verbosity call for libs-api to make. I can see arguments in both directions.

You'll notice that the part of my message to which this is replying started out with "For a function of one argument". So no, I don't think the same things for functions of two arguments.

I don't think that exactly follows.

For example, C# allows calling anything with named parameters. (As a result, all parameter names are part of the public API, and changing them is a potentially compilation-breaking change.) So people could put parameter names on all their calls, and not need that IDE extension at all -- which, as you say, might be helpful in reviewing code on GitHub or similar. But, empirically, they don't write most of the names, while still liking the IDE extension that shows them in places where they chose not to write them in the code.

So I could take the same fact and come to a different-but-also-reasonable conclusion: that people like that IDE feature as easily-visible documentation, but would rather not actually put those names in their code.

(As a similar kind of thing, that gives me the idea that it might be cool for r-a to show the trait method's documentation when I'm writing an implementation of that trait, even though I'd rather not copy it into my impl.)

I would never claim I could prove it.

Generally the only thing that one can prove in language discussions like this is the "turing tarpit": that whatever is under discussion isn't necessary. But I never claim that something shouldn't happen simply because it's unnecessary, since programming languages are full of unnecessary things. Certainly one could even just not have parameter names as anything but comments even in the implementations of functions.

This proposal undeniably would help some Rust code. It's always just a trade-off between how much code is helped by how much and what the impacts are in other things to get those improvements. And I don't think that's ever possible to prove a correct answer, especially since language design is a horrible mess of path-dependent choices.

10 Likes

Rust-analyzer supports the same thing and has the same functionality of hiding the parameter name if it is obvious. It doesn't help when browsing code online though, unlike real name parameters.

3 Likes

Indeed, parameter names everywhere would also be unhelpful. As usual, it is a matter of style and subjective readability, just like "should I use long iterator chains or break them into separate named pieces", "should I introduce variables or inline the expression" and "should I give this type a shorter alias".

That is an insignificant restriction in practice. Yes, parameter names are subject to the same API stability policy as your function, field and class names. You just treat them the same way. If you say "what if the parameter name is poorly chosen" - well, what do you do if a method name is poorly chosen? You just try to give good names to start with, and occasionally deal with errors by API changes, deprecated methods or living with sub-optimal choices.

If you want to use a different parameter name in the body of the function, you just introduce renamed variables at the start of the function.

4 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.