Named Arguments Status Update

Dennis dkorpel at gmail.com
Fri Jan 5 09:48:53 UTC 2024


Since dmd 2.103, named arguments for struct literals and regular 
functions, including overloads, have been implemented per [DIP 
1030](dlang.org/dips/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](https://issues.dlang.org/show_bug.cgi?id=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](https://github.com/orgs/dlang/projects/19). 
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](https://github.com/dlang/dmd/pull/15040) 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

```D
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` to `y`?

## Overloading by name

With named arguments, you can disambiguate an overload with 
identical types by name:
```D
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:

```D
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:
```D
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
```D
int f(int x, int __param_1)
```

But consider that a type tuple can already have names when it 
came from a parameter list:

```D
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:

```D
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:

```D
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?



More information about the Digitalmars-d mailing list