Ada has named arguments ( https://en.wikibooks.org/wiki/Ada_Programming/Subprograms#Named_parameters )
If ten years from now you want to use Rust to write a control system for a large aeroplane, having a less bug-prone language helps. Taking a look at Ada could help.
But isnāt it usually obvious for compiler what type a given token has?
I.e. compiler should distinguish between x: i32
and x: 10
because in the first case the i32
is a type name,
and in the second case 10
is a value, so itās not ambiguous, isnāt it?
I would argue that features that add complexity have a bigger cost in languages like Rust than in Python or Ruby because Rust is a systems language, which people use to write software that is used longer and is modified less than Python or Ruby. People spend larger portion of the time thinking and designing and smaller portion of the time typing comparing to Python. Code is read and understood more often in system/infrastructure software than in script or a high-level application. Complexity is a bigger concern in these cases. Thus Rust should add fewer features than Python or Ruby do given features with roughly the same cognitive cost and other benefits.
Right, so you should favour having language features that improve the reading of the code, even if they cost a little elsewhere. Named arguments is a feature that improves the reading of the code.
No. More syntax means more energy spent in trying to determine which specific syntax is used here.
Take a look at a line of code that uses named arguments, how much energy do you need to see if your arguments are named or not?
send_onions(amount: n1, size: s1)
I think you are overstating your point...
Iām not sure, but I think he means that the more ways you have at your disposal to design your API, the more time you will spend deciding on the design.
I think this is true, but as he pointed out, Rust code is supposedly going to be read more often than it is going to be written. For that reason I think that features that add a little more explicitness when it is desired (without forcing it all over the place) are worth the little overhead they bring when designing the API. I can understand that it is not everyoneās opinion though.
I also understand the argument about multiple small features that would end up adding more complexity than the sum of their individual complexity. But this is true for all alternatives proposed thus far (except doing nothing), especially the ones that would require some syntax sugar.
interestingly, I'm against this feature completely and I come from a C++ & Java background
I think this is a question of design trade-offs:
-
This is indeed a very popular feature in scripting languages which fits with their model of rapid development and less effort on initial design. It also fits scripting languages because they use dynamic typing and therefore named arguments in such a context have more benefits and less visible costs.
-
C# is an outlier in such discussions because it suffers from acute featureritis - It has basically every possible feature under the sun. I currently work at a C# shop and have to fight with several very stupid anti-features of C# that people claim "makes writing code easier/quicker". For example, static classes make code near impossible to test and regions remove the need to better organize the code (Hey, Let's just put everything in one huge file with tens of thousands LOC and just use regions because we are lazy to use proper logical (namespaces) & physical (file system) organization (end of after work day rant))
-
In a statically typed lower-lever language such as Rust it is clear that the benefits are fewer and the costs (however marginal people claim) are more noticeable.
Given that Rust has a rich type system that already allows to distinguish parameters the added value becomes marginal at best. and the cost of "there's more than one way to do this" becomes much more noticeable, at least for me.
Honestly, in the vast majority of cases, based on my personal experience & background, I'd consider named arguments a code-smell in the design where the developer didn't consider providing stronger typing for the arguments and/or just has too many parameters which should have been better organized.
Ada programmers seem to like this feature.
i32
is also a valid expression if you had a variable with that name, so yes it is ambiguous. (Any ident
token is valid as both an expression and a type)
I think this is an important distinction that should be taken into account whenever someone familiar with dynamically typed languages are determining the benefit/cost of named arguments to a statically typed language.
If I were going to do that, named arguments would help way less than using explicit types for everything and using Rust's type system to it's full potential.
Combining the dynamic/static typing perspective with the perspective of writing a control system, the trade-offs become more obvious. If you're going to be writing safety critical software, arguing against using newtypes or the builder pattern on the basis of how much boilerplate is required is almost pointless, since the cost of doing that (and getting it right) is well worth the benefit. It's also not obvious that named parameters would be any better at making the code 'safer'. The scenarios in which the effort isn't worth it is with 'throwaway' code and that's not Rust's 'niche' anyways.
The primary use case for named parameters in Rust is when two parameters have the same type. If each parameter is of different type, it provides only subjective benefit that can be changed by using improved argument names. And I have yet to see an example where a function with two parameters of the same type wouldn't be improved by having those parameters have different type or some other refactoring that results in parameters with distinct types.
Arguably an example of the Blub paradox.
(Another relevant old Lisp argument: design patterns are missing language features, as applied to the builder pattern. That however is only relevant to a subset of calls - those with enough arguments that the builder pattern is currently justified - and arguably the language feature(s) in question already exist, in the form of keyed struct literals and functional record update. I honestly don't understand why people write horribly verbose builders, instead of taking a struct intended to be passed as a literal ending with , ..Default::default()
, which isn't much longer at the call site. Maybe a bit of sugar would help.)
Because builders are extensible in semver compatible upgrades. Technically, the syntax Ty { field: val, .. Ty::default() }
will work even if new fields are added, but it still requires making the fields public, which means that syntax isn't specifically mandatory.
Yeah, thatās one issue. I think the way FRU is desugared was a mistake: given
struct S { f: i32, g: i32 }
then
S { f: 0, ..old }
means
S { f: 0, g: old.g }
which forbids having inaccessible fields among the ones not specified, but it could have meant
(let mut tmp = old; tmp.f = 0; tmp)
which wouldnāt, and would work the same in most cases - but not all. I wonder if thereās a backwards-compatible way to change it. (I proposed something several months ago, but in retrospect it was overcomplicated.)
In practice, though, thereās not much downside to pub _dont_use_this: ()
ā¦
Somewhat agreed. We do something similar for the dual of this problem with std::io::ErrorKind
. I use the same trick in a few of my crates too. However, std
has the advantage of declaring the __Nonexhaustive
variant as unstable, which means new variants are guaranteed not to be breaking changes.
I havenāt read every proposal here (there are alot) but I donāt see this one so I thought I would throw it out there.
To me (and a lot of other people) the biggest benefit of named arguments is default arguments. It would be great if this could be easily accomplishd in a struct. āBut that would require you to import the structāā¦ maybe not.
What if you could write annonymous structs like so:
fn foo(a: u8, b: u8, defaults: {
bar: u8,
baz: u8,
bam: u64,
zing: f64,
}) {
// do foo
}
and then the user could call it with
foo(1, 2, {bar: 10, bam: 12, ...}) // `...` means "rest are default"
// OR
foo(1, 2, {...}) // use only defaults
This way you never have to actually define the ādefaultsā struct ā it is defined implicitly. Also note you could use a defined struct for defaults
and the user could call it the same way (without needing to import it).
This is also a way of implementing ānamed-onlyā arguments, which I prefer for the cases where you want default arguments.
Edit1: this would have the advantage (or disadvantage) of allowing you to define and initialize struts without naming them ā which may be good or bad. It could get you a python ādict-likeā experience that is still type-checked.
Edit2: I would think this approach would use rustās type inference ability, which allows you to say let x = foo()
where foo()
returns type Foo
without needing to import type Foo
.
Edit3: to have truly default arguments, we would have to resolve how to have default args for structs ā but that is a giant todo anyway, and could use simple syntax.
Iāve come to the conclusion that the best way to convince people to agree to named arguments is to first discuss anonymous structs as a feature. There are already anonymous tuple structsā¦ which are tuples, so it would make sense if anonymous regular structs were in the language. Otherwise itās a surprising omission.
This would probably placate the people who want named arguments since you could easily pass anonymous structs into functions.
@iopq and declare them as well! You are right, it is a surprising omissionā¦ is there an RFC for annonymous structs? I canāt find one.
If additionally struct fields get default values, then perhaps most issues with named arguments are handled without too much burden on other parts of the language.
Then you could write:
fn foo(opts: { x: i32, y: i32 = 3 }) { ...}
foo({ x: 1 })