[Pre-RFC] named arguments


#229

I would be willing to be at least a part of it, but need to familiarize myself with the full process.

If there’s more than one person working on this, I might not be the best lead. Refactoring trans only shows determination, not experience with navigating the change-the-language waters. If someone else doesn’t want to take the lead, however, I certainly will.


#230

I don’t think this is true, I can imagine that for FFI we could offer guarantees about how anon structs are mapped in to the calling convention as part of the ‘sugar’ for how they can be used.

I’m not convinced that anon structs are the answer, but I’m even less convinced that an ad hoc extension to function call syntax is either. I do think it is important that we consider default arguments too - that seems to be a good part of the motivation here, and even if we leave the details for later, I’d like to be sure of a path forward.


#231

If we start with the ad-hok extension to function syntax and only allow it on nonclosures, we can later integrate it with the type system if we so desire. I.e. syntax could be provided to say “Takes an Fn with 2 arguments named a and b”, or what have you.

The problem I have with actually doing that is that you start tying things really tightly to the names of things in user code. Allowing me to call your function with named arguments is one thing, but calling my callback function with named arguments introduces the problem that nothing makes it obvious that these names are important at the point where I defined the function. Consequently, I don’t think that making it anything more than a lint is really such a good idea: too much spooky action at a distance. And, since it’s now not a hard error, it’s really hard to justify putting it in the type system.

Anon structs being special cased for all the calling conventions that aren’t Rust just lost the big advantage of anon structs: they aren’t general anymore. SO to me that’s sufficient to take them off the table completely, unless someone comes up with a good reason why they shouldn’t be. But there is an additional objection. How do you declare an extern function properly if it’s taking only one struct instead of n arguments? It seems to me that you now have to extend it with extra attributes for the FFI case ala the recent repr(transparent) RFC.

But the thing about the simple extension to function call syntax is that it’s really easy to implement and solves many of the common cases: reduce need for the builder pattern, remember what arguments are at the call site of complex functions, and make it possible to leave out arguments that always have sensible defaults (i.e. Vec3D::new(y = 5) or similar). I think it’s essentially just a pass over whichever IR is most appropriate to change let’s call it a complex function call to a vanilla function call.

But I feel like this has probably all been said, should I put aside the time to read this incredibly enormous thread. I suppose I’ll have to, but who knows when–it’s really quite a lot, and I’d probably need to take notes to synthesize it all.


#232

With the new release of IntelliJ IDEA today (2016.3), JetBrains have actually done named arguments for Java, but only via tooling. Like so: https://www.youtube.com/watch?v=ZfYOddEmaRw

While this isn’t a universal solution (it requires the right tooling, as opposed to writing it out in the code), maybe the language server could be made such that tooling like this can be made really easy to do? That seems like a sensible solution to me at least; we don’t clutter up the language, and those that want can hopefully have easy access to the right tools to get the job done.


#233

Just to toy with some proposed ideas for function call improvements. Using anonymous structs we can simulate structural types:

fn foo({a: &str, b: usize}) { /*...*/ }

Generic over anonymous structs, hat tip to @cramertj’s generics over tuples idea, if combined with proposed trait fields you get something serviceable at definition site, and very nice at call site.


trait Fields {
    let a: usize
    let b: &str
}

fn foo<F: Struct + Fields>(fields: ..F) { /*...*/ }

struct Bar { a: usize, b: &'static str, c: Vec<usize> };
impl Fields for Bar { let a = Self.a; let b = Self.b; }

let bar: Bar = /*...*/;
foo({..bar});
foo(..bar);
foo(a: 3, b: "test");

Maybe .. could be used for sugar for the Struct trait in the type position, e.g.

fn foo<F: ..Fields>(fields: ..F) { /*..*/ }

If Rust gets something like any for quick parametric types:

fn foo(fields: any ..Fields) { /*..*/ }

While this is still a little heavy on the definition side it would allow for traits such as Debug to be added more easily. I can’t figure out a good way to integrate default values in with this. Though between const, trait fields and a little sugar something feels possible.


#234

The problem with the struct based solutions is that they add API boilerplate.

Not only defining named arguments is more complicated than necessary, but the user also has to look up the definition for the fields struct to know what arguments the function accepts, instead of them being defined inline.

I’m strongly in favor of inline solutions.


#235

My suggestion is simple: type ascription, when used in function arguments, should always require parentheses. This leaves f(a: b, c: d) free to be used as the named argument syntax.


#236

My suggestion though influenced by swift:

fn suggest(a: i32, b: i32) {  /*..*/  }

This is the same as provided by rust with positional parameters.

fn suggest(_ a: i32, _ b: i32) { /*..*/ }

This is with named parameters with names corresponding to the parameter names. Shorter than using pub.

fn suggest(another a: i32, with b: i32) { /*..*/ }

This one is with named parameters with alternate parameter names for creating builder like syntax.


#238

I’ve read most of the posts here, and I haven’t seen 1 single argument for why Builders aren’t good enough other than “lots of boilerplate somehow”.

What bothers me about that line of thought is that this is Rust we’re talking about, complete with macros. And someone had actually already leveraged that to reduce all that code to a derive. The project is located here:


#239

I think I remember a few from the discussion but here’s one: you can have functions without any structs involved so the builder pattern is not even an option there.


#240

You can indeed. But my counterargument is that once the fn signature is complex enough to warrant the extra annotations, it is also complex enough to warrant refactoring it to a builder*, even if that builder is an empty struct. Is there even any situation in which that wouldn’t work? If there is one I have yet to encounter it, but I’m open to such use cases.

*Especially with the rust-derive-builder crate


#241

It’s not necessarily complex functions, just that an argument would be the default 90%+ of the time.

Let’s take https://github.com/Keats/jsonwebtoken/blob/master/src/lib.rs#L104 as an example. It is not a complex signature but in the basic case validation will be Validation::default() so my ideal signature would be fn decode<T: DeserializeOwned>(token: &str, key: &[u8], validation = &Validation::default()). I’m not going to create an empty struct for my functions that have a default argument, that’s a UX nightmare.

Regardless, this thread is about named argument so an example specific to named argument without default argument is readibility/maintenant. For example, a method on a struct taking a bool as an argument (or 2). I think it’s fairly uncontroversial that being able to write this made up method self.render_page(&page, with_livereload=true) is more readable than self.render_page(&page, true) as there is no need to go to the definition to see what the bool corresponds to. It could use an enum but that’s overkill when it’s a simple bool.

I personally view the builder pattern as an (ugly) workaround the lack of named/default parameters so I’m probably biased.


#242

Ok so there are a few things: For those kinds of defaults to be always emulated libstd in that I just write multiple constructors with descriptive names. I’m not saying that is necessarily always the way to go, it’s just an alternative that has the advantage of existing today.

Second, I think I see what you’re trying to say, but your example is an antipattern in Rust: rather than use a blind boolean, it is both more readable and more ergonomic (at 0 extra runtime cost relative to the boolean) to use an enum with explicit variants. That eliminates that problem in a very maintainable and self-documenting way, and again has the advantage of existing today.

Here’s the thing: I suspect you view Builders as ugly relative to named params because all the code makes it feel like a “heavy” solution. The thing is, in most cases it’s actually possible to just inline the builder methods and thus have no runtime overhead. And as for the source level: it may look somewhat verbose (though not overly so when both the builder method invocations and the named parameter lists are v-aligned i.e. when comparing them in an apples-to-apples fashion), but it has the advantages of being able to provide impls for traits, something named params will never do.

That is why I’m against named params in Rust: they don’t really offer anything new except syntactic sugar which doesn’t even make the code all that shorter compared to builders (ie very limited upside), while they do raise the syntactic as well as semantic complexity of the language (2 very real downsides).


#243

The bool was an example, the readibility still increase if you can name your arguments for any variable type. Sometimes it’s not needed because you’re passing a variable with an explicit name but having the possibility to use it is a big win imo.

And builder pattern will never work for plain functions or methods, the majority of my usecases for kwargs in Python, unless you sacrifice UX. So, at least in my head, the builder pattern is way more limited.

I’m sure this point has already been discussed in the 240 comments though so I’ll stop there.


#244

What do you mean with the builder pattern not working for plain functions? Do you mean its not applicable to them because of the use of a struct?

Because if so then to me logical approach to that is breaking the API and turning the fn into a builder (which again does not take much effort at all, and is not syntactically or semantically bloated compared to named args).

And while I agree that naming things can make clearer (after all, it’s why we do it in the first place rather than using magic values all over the place), we already have something that is better: very strong type checking combined with explicitness of types. The enum example also does this: it explicitly tells you at call site what the name of the enum is, as well as the actual value, assuming of course that you don’t import all enum variants into scope (in which case the self-documentation is lost). Unless the type is poorly named that will be way better documentation (not to mention actually verifiable in terms of correct use: there are a few examples posted by Graydon earlier in this thread that demonstrate potential pitfalls) than a regular name will ever be.


#245

At this point, is type ascription an issue anymore? There’s been no movement on it over the past year, afaict, and there are a lot of issues with it.


#246

Is named argument in the roadmap for Rust or is it discarded?


#247

From the feedback I got in this thread I made the following conclusions. A lot of people can agree that named arguments are desirable, as a standalone feature or as a first step towards default arguments. But no “ultimate design” has emerged and everyone has its own ideas and perspectives about the matter. When an RFC is finally going to be written, I suspect it will get a huge amount of traffic (judging from the size of this thread).

I have not seen any indication from the core team to say it was of the table. On the contrary, some members participated in this discussion and provided some valuable feedback and ideas. But this is not a high priority feature, it is more generally considered as a “nice to have”. I don’t think any of the core members will take the lead on this, they have a ton of other higher priority stuff to take care of. So if this feature is desired, we (the community) should take the lead on this and push it forward. Until someone takes on the burden of moving this through the RFC process, it will most probably stay on hold for the time being.

My personal opinion about this is that we should probably wait a little. A lot of effort is going into fulfilling the goals of the Roadmap and by trying to push this through the RFC process now, I fear that it might end up being postponed or closed because there are more pressing issues demanding the attention of the core team.


#248

I really like named/default arguments for readability and use them often in languages that have them, like Python.

On the readability side, IntelliJ Rust is adding support for showing the names of arguments at the call site. While not a universal solution by any means, I think this is pretty clever and features like this can help the situation.

Here is an image from one of the related PRs. Note, this is pretty early in the development of this feature and will likely get polished more in the future.


#249

I’m a fan of named args and default values for args and I’m keen to see this land in Rust at some point (together with some other improvements to ‘basic’ data structures). However, I don’t think it will happen this year (even to the RFC stage). I agree with @Azerupi that we should wait a little bit. There is a lot happening in Rust, language-wise, at the moment and I don’t think these features will get a lot of support right now (however, in the future I’ll definitely advocate for these things on the lang team and elsewhere).