Pre-RFC: Named arguments

FWIW, I'm also currently uncertain about named arguments. They seem like they're only really for this weird middle ground, since if something takes 1 argument it doesn't need to be named -- I'm totally fine with new and with_capacity instead of overloading new() and new(capacity: -- and if something takes 4 named parameters I bet it'll want 5 named parameters soon, and thus it should be passing a real type instead.

So yes, named parameters are good for functions which are used more than once and have, say, 2-4 parameters and have multiple parameters of the same type and it's not something (like [T]::swap) where the order of them is irrelevant.

Thus the insert example isn't great to me. Its type is &mut Vec<T>, usize, T, so the at is just noise in every case except T == usize. I don't need named arguments for .insert(0.0, 1), since it doesn't compile. And if I'm reading code, it's usually more like .insert(i, x), so it's not surprising which is which. (Basically, this is me agreeing with yigal that named arguments are super-important in Python, but much less so in Rust. Note that, thanks to us not having integer coercion, even .insert(0_u8, 0) is an error, unlike in many other languages where a u8 can coerce to usize.)

The example of "oh, but you could use with as the argument name for a builder" is incredibly unpersuasive to me. You could name every single parameter ever "with". We have types in rust, and the fact that you're passing a ConnectionOptions is the important thing here. Forcing people to type with: in front of it is entirely noise.

My strongest feeling is that I want to see ekuber's default field values RFC first, to explore a kind of "builder pattern lite" to see how that impacts things before going all the way to something as pervasive as named arguments. (Notably, that RFC won't propose changing name resolution, trait matching, etc the way this one does.)

That RFC would, for example, address the "and also can't handle non-optional arguments" objection in #12 above.

:+1: to this. Struct initializers using : is my biggest complaint with Rust syntax -- It would be so much nicer if = was for values and : was for types, rather than the confusing mix we have today.

One previous idea was https://github.com/rust-lang/rfcs/pull/2443, in case that sparks something.

I'm not sure this is a meaningful distinction, since I could just say "it's not overloading, it's using the parameter types as part of the function name". And indeed, that's how overloading is actually implemented in things like C++.

But more importantly, if I can import two different functions with use yourcrate::foo;, then I'd say they're "overloaded", regardless of the exact details. Because that means their name is just foo, not foo(a:) and foo(b:).

There might be something to tease apart here.

If you're looking to "iterate quickly", then hopefully that's not breaking changes to the crate's public API. This is my biggest gripe with C#'s named parameters, actually, that all parameter names (on exposed things) are always a semver guarantee. And thus I'm glad to see the proposal here doesn't instantly make ever parameter name part of semver.

But for things that aren't part of the public API, I'm far more sympathetic to "ok, this function is kinda terrible, but whatever, it's just a module-private helper I call three times inside this file to reuse some logic".

That opens up some potential freedom, the same way as non_exhaustive does. We could, for example, provide a slightly-ugly syntax in the caller that would use the existing names, but that's only allowed for calling things defined in the same crate. So if you have a crate-local helper, sure you can call self.Handle(request, @@loadCookies = true, @@followRedirects = false); if you want. (Placeholder syntax, of course.) But you probably shouldn't make a function like that part of the public API. It's basically the same kind of idea as "sure, maybe you use a 4-tuple inside a function to store some stuff, but think carefully before you put a 4-tuple in a public API".

That would remove a bunch of the complications here, like signature changes, overloading, updating Fn traits, etc. But I can see some extra compilations there too, like maybe "crate" is the wrong pivot here -- something from a derive might be "in" your crate but that doesn't necessarily mean you should be able to use its parameter names, so maybe it's more like "same hygiene context" or something.

Hmm, that makes me wonder about hygiene for argument names. Can a macro make an argument name that can be used only from inside the macro, but not outside of it?

13 Likes