Named arguments increase readability a lot

I am not trying to argue against having named arguments in rust. I'm not arguing for it, either. I was only trying to point out something similar that is available today that people might find useful (and might not be aware of)

Yes, apologies if it came off like me saying you were arguing one way or the other. My intent was to give my interpretation of what the feature you mentioned implies.

1 Like

I would find it quite annoying to have to remember which call syntax to use for every function call I make. In any case, saying that this should not be possible won't really work, because you can already implement that possibility for free functions with a macro, and why wouldn't you?

In fact, if this were a constraint, I'd vastly prefer just being able to implement methods on anonymous structs.

1 Like

Example in Rust: company.build(house, Options { from: drateState, to: dateStop, afterThat: whitePaint, finally: sell, ..Default::default() }).

Only barely longer, but easier to wrap, easier to make functions to generate the options, easier to add more options without breaking existing callers, ...

9 Likes

This can work but then you either need a gigantic Options struct or almost one per method where you need it, which is equally horrible to me.

5 Likes

Which is precisely the sort of verbosity issue that structural records / anonymous structs is targeting.


So far all we've really done here is spell out in more detail what I meant above by "heavy overlap with other proposals" and "subtle semver implications". For anyone seriously interested in this feature, I'd advise taking a step back, reading the existing proposals and design discussions and really thinking about the interactions with overlapping proposals, possibly producing a more comprehensive proposal that really takes all of this into account.

Maybe there is still a compelling argument to be made that the best set of solutions to all these overlapping problems involves some form of named arguments, but at this point it's clearly not going to happen in a back-and-forth discussion where proponents are largely reacting to the most recent objection. And seriously consider the possibility that, no matter how great a feature this was in other languages, and even if current Rust with named arguments is a net win over just current Rust, maybe named arguments really aren't part of the best set of solutions for Rust when all the other viable alternatives are considered (which is what I currently believe, for more or less the reasons @CAD97 gave).

18 Likes

Could be far less horrible if we had constructor inference:

company.build(house, _ {
    from: drateState,
    to: dateStop,
    afterThat: whitePaint,
    finally: sell,
    ..Default::default()
})

Then, because the second argument of build is CompanyBuildOptions (or whatever it is called), the compiler can infer that the only type that can replace the _ is CompanyBuildOptions.

7 Likes

We would also need things like lifetime elision and impl trait before the ergonomics of structural records matches those of just regular named arguments.

What is the use case for structural records outside of emulating named/optional arguments?

See https://github.com/Centril/rfcs/blob/rfc/structural-records/text/0000-structural-records.md#motivation. I'd say about half of what's there (including the first example) overlaps with named arguments, but the other half applies only to structural records.

1 Like

FWIW, the RFC itself states:

While this is a possible use case, in this RFC, we do not see named function arguments as a major motivation for structural records as they do not cover aspects of named arguments that people sometimes want.

It seems to me that whether or not structural records is a good idea is orthogonal to named/optional arguments. Trying to shoehorn both use cases into a single feature might be counterproductive.

Also, IMHO it is worth factoring in the periodic requests for named/optional args here and elsewhere, and the lack of those for structural records.

While it's not the motivating use case, there is overlap. And while you say it's orthogonal, I very much think the existence of structural records would have strong influence on the design of named and optional arguments, just like anything else that reduces friction for using structs as on off function argument bundles (so, not only structural records, but inline docs, struct type elison and an improved story around defaults, perhaps even const generics with string types as opposed to just numbers).

I prefer structural record feature to land first. You may already know that many accepted RFCs are still waiting to be implemented.

Structural record can also be applied in returned position.

I'm actually not at all in favor of required named arguments. In some functions this makes sense, bot having to name the param when calling a function adds to the overhead when learning an API.

Take a simple function like get_mut this is common across the ecosystem. But everyone who implements it may use a different parameter name.

So when I call get_mut I will have to check the docs to find out what they've called it.

createFileAtPath(p, withMode: m) is implemented by calling something like createFileAtPath_withMode(p, m) — just a regular function with two required arguments.

You can have multiple of these, and that's not even function overloading. These are just different function names.

So the compiler just creates the different functions depending on the usage of createFileAtPath in the code?

2 Likes

There are no optional arguments in ObjC/Swift. Author writes two (or more) functions, and only exactly these combinations are allowed, nothing else. It's 100% syntax-only feature, 0% magic.

fn createFileAtPath(path: p) {
}

fn createFileAtPath_withMode(path: p, _: m) {
}

These are separate functions, with two different names, separate function bodies, and separate function pointers.

There's just a syntax sugar that moves fragments of function's name from inside parens to outside parens. It changes calls written like createFileAtPath(p, withMode:m) to invocation like createFileAtPath_withMode(p,m).

2 Likes

As far as I know the structural records RFC hasn't been accepted yet.

We do have both tuples and functions with multiple arguments. But nobody considers supporting both a wart. It is a similar case with named/optional arguments and structural records.

Rust treats arguments differently in many ways: (a) lifetime elision, (b) reborrowing, (c) impl traits, and I am sure others (I am no means a compiler expert). At least (b) and possibly (a) should be different between arguments and structs. That is why IMO it is a mistake to conflate the two.

2 Likes

Have to admit that it's pretty ingenious though. Most people abhor overly long function names, while they love named arguments.

(The flip side of the medal is the possibility/convention to have two different names (external/local) for the same argument...)

1 Like

This is misinformed at best.

get_mut would (typically) not require an argument name. (Of course, you can still write a bad API just like you can call the method get_mut_but_cad97s_special.)

A "required named arguments" feature doesn't take away the ability to use positional arguments. Named arguments are instead a second kind of argument.

Consider str::replace(&self, impl Pattern, &str). You would still always call it as s.replace("find", "replace"). If a new function were defined with the signature (using ad hoc syntax) str::replace(&self, impl Pattern, with: &str), that separate function would be called with s.replace("find", with="replace"). The Swift function is named s.replaceOccurences(of="find", with="replace").

Any modern kwarg feature isn't just going to be you're allowed/required to rename the name the function definition site binds the value to. Instead, it's a separate name that's a deliberate part of the API.

And I'd argue we already have API surface that's a clunky workaround for the lack of named arguments in constructor functions. Which of these is clearer: HashMap::with_capacity_and_hasher(rand(), default()) or HashMap::with(capacity=rand(), hasher=default())? (The real answer is to use HashMap::with_capacity (HashMap::with(capacity:)?), ofc.)


I still stand by my opinion that even the best Swift-inspired kwargs feature Rust could possibly have would still feel horribly second-class due to the fact that the stdlib doesn't use it. We could incrementally convert it to use them where it would be most beneficial (HashMap::with(capacity:hasher:)), but it'd be a huge, untenable amount of change for the stable stdlib to deprecate most all functionality for the kwarg form.

So getting ~70% of the benefit of kwargs by making the Options Struct pattern easier to use via lightweight structural records and/or inferred type struct literals seems like the better approach to me.


One final note: the API in the post above would better be createFile(at: path, with: mode)... probably. Dot enums make the naming a bit less obvious, as you might write createFile(at: path, mode: .ReadWrite). Kwargs don't suddenly make good API design trivial, of course.

3 Likes

FWIW, this is a big part of my feeling here. I'd love to see more features that make structs better that I think would be useful in general, and see how far we get with those.

3 Likes