Pre-RFC: Named arguments

:wave: Hi, I’m the author of the Swift Regrets articles mentioned above, and one of the early Swift language developers (though no longer). For me, the two articles that got called out above were about the implementation of named arguments more than the feature; I think most of us still think the feature is a good one. That said, the point made above about named arguments being designed into the language and stdlib from the beginning is really important (and it was massively disruptive when we changed the guidelines to what they are today, a few years in).

I miss named arguments in Rust, not just for new_foo and new_bar but for any argument where the type information is not strong enough to clarify the purpose, often &str, Duration, or None. You can newtype these, sure, but if the only reason to newtype is clarity at the call site? Then it’s not pulling its weight.

But this “I miss…” is a snap-my-fingers kind of wish. Adding named arguments to present-day Rust is something that should be approached with caution, and the rules won’t come out exactly like Swift’s (which for better or worse has type-based overloading as well). The migration path for both the stdlib and existing crates out there is a difficult problem. So I appreciate both the thoroughness of the proposal and the thoughtful critiques in the thread.

I do recommend people check out the Swift API Design Guidelines to see what argument names look like in practice in a language that’s had them from the start.

  • On overloading: I agree that the correct user model for labeled arguments is as separate “full names”, not overloading on the “base name”, even though the compiler will have to handle them kind of like overloads for error recovery and partially-written code. The fact that Swift lets you refer to a function by its base name only is not conducive to library evolution, and I wish we hadn’t allowed that. (We never managed to come up with a “full name” syntax for a zero-argument function, though.) I think this means that if labeled arguments are added to Rust, the base name would only refer to the function with no labels, and any full names with labels would have to always be referred to with the labels included. (This implies no overloading on arity without also having labels.)

  • A lot of the troubles we had were in preserving labels in function types. Don’t go down that route.

  • On optionality: Swift differs from Python and C# in that the argument labels are required at the call site if present. Why? There are some technical reasons, but mostly it came down to wanting the same call to look the same everywhere, giving library authors control of how their API is used. It also makes evolution much more straightforward; you don’t need to worry about collisions with other functions in your library. And because people get types wrong much more than they get labels wrong, the labels are important for diagnostics when multiple functions share a base name. Separating the label from the local parameter name also allows them to be different, which is important because good labels are not always nouns.

  • On reordering: This goes a lot with the previous one, but mostly only at the abstract level of “the library author should design their API with intention”. I agree that many times it’s not important, and that it’s somewhat inconsistent with how Rust does structs, and I can’t put my finger on a more concrete reason to disallow reordering besides “it’s simpler for the compiler”.

  • On name: name: this is a Rust feature that I miss in Swift, so if you can figure out how to make a plain name work here I would love it. One way to do so is to disallow unlabeled arguments after labeled arguments, and disallow reordering; that way, you know that if you have another unlabeled argument, it’s actually labeled with the same name.

28 Likes