Since dmd 2.103, named arguments for struct literals and regular functions, including overloads, have been implemented per DIP 1030. Making it work with template functions turned out to be a bit more difficult than expected, so had to be pushed back to a later release. I know people don't like half-baked / unfinished features, so I didn't want to announce it yet by adding it to the changelog. I considered introducing a -preview=namedArguments
switch, but then that switch would quickly linger in a deprecated state, and dub packages may need to conditionally specify that switch to support both dmd 2.103 and newer releases. That's why I thought it'd would be simpler to silently let it sit in the compiler, but in retrospect, it ended up causing confusion (example: issue 24241), so I won't do this again if there's a next time.
Progress
You can see the state of the named arguments implementation on its projects page. I've been meaning to finish at least named function arguments (as opposed to named template arguments) before the end of 2023, but fell short unfortunately.
Templates got me stuck for a while because of a circular dependency between parameter types (which can be tuples) and argument assignments:
- The function to resolve named arguments needs a function signature.
- The function signature is created by deducing template arguments.
- Template arguments are deduced by (named) function arguments
The good news is: I found a solution that I'm satisfied with, and have a working Pull Request to merge Soon™.
However, while implementing all of this, I did encounter various ambiguities / edge cases which weren't covered by DIP 1030's text that could use your input.
Empty tuple value
alias AliasSeq(T...) = T;
int f(int x, int y) { return 0; }
int v = f(y: AliasSeq!(), 1, 2);
Currently, the named argument y with an empty tuple will collapse into nothing, and (1, 2)
will be assigned to (x, y)
.
- Should this be an error?
- Should this assign
1
toy
?
Overloading by name
With named arguments, you can disambiguate an overload with identical types by name:
string f(T)(T x) { return "x"; }
string f(T)(T y) { return "y"; }
static assert(f(x: 0) == "x");
static assert(f(y: 0) == "y");
However, both template functions will end up with exactly the same types. DIP 1030 specifies parameter names aren't part of the mangling, resulting in clashing symbols at run time:
void main()
{
writeln(f(x: 1)); // x
writeln(f(y: 1)); // also x
}
Should the compiler, after finding a matching overload, retry all other overloads without named arguments to prevent this? Or should it instantiate it the x
variant because it saw it first, and then refuse to instantiate y
because the mangle has been seen before?
Tuple parameters
You currently can't assign a tuple parameter by name:
alias AliasSeq(T...) = T;
int f(AliasSeq!(int, int) x) { return 0; }
// This will expand to:
// int f(int __param_0, int __param_1) { return 0; }
// So this fails:
int v = f(x: 1, 2);
I can change it so it expands to
int f(int x, int __param_1)
But consider that a type tuple can already have names when it came from a parameter list:
int f(int x, int y) { return 0; }
static if (is(typeof(f) T == __parameters)) {}
pragma(msg, T); // (int x, int y)
int g(T) {return 0;}
static assert(g(x: 3, y: 5) == 0); // Currently works
int h(T z) {return 0;}
static assert(h(z: 3, 5) == 0); // Fails, should this work?
Is the first parameter named x
, z
, both?
Note: making the declaration of h()
an error would be a breaking change.
Forwarding?
(This did not come up in the implementation, but was pointed out by Timon Gehr on Discord.)
Is there a way to forward named arguments? Consider:
import std.stdio;
int f(int x, int y);
auto logAndCall(alias f, T...)(T args)
{
writeln(args);
return f(args);
}
logAndCall!f(y: 1, x: 0);
Are the names propagated to the T args
parameter? If so, that wouldn't be hygienic:
Imagine an argument named writeln
- it would hijack the function call!
Perhaps we could allow access to names some other way, like args.x
. But still, if we had another parameter (T args, string file)
then the called function could not have a parameter named file
.
Named value sequence?
So if we can't implicitly give a T...
names, can we explicitly? We already saw a __parameters
type tuple can have names, this could be expanded to value sequences:
logAndCall!f(args: AliasSeq!(y: 1, x: 0));
This syntax is ambiguous with named template parameters however: According to DIP 1030, this should try to set template parameters y
and x
of the AliasSeq
template. Is there a way to make forwarding named arguments work?