Unpacking syntax

Timon Gehr timon.gehr at gmx.ch
Thu Sep 5 22:08:58 UTC 2024


These are my ideas for how to support unpacking any tuple-like type into 
multiple components.


Synopsis:

```d
import std.typecons: t=tuple, T=Tuple;

void main(){
     // unpack declarations
     auto (a, (b, c)) = t(1, t(2, "3"));
     assert(t(a, b, c) == t(1, 2, "3"));

     import std.stdio, std.string, std.conv;
     auto (u, v) = readln().strip.split.to!(T!(int,int));

     // can unpack in foreach
     foreach(i, (x, y); [t(1, 2), t(3, 4)]) {
         assert(x==2*i+1 && y==2*i+2);
     }

     // works with storage classes
     auto arr = [t(1, 2), t(3, 4)];
     foreach((ref x, y); arr) {
         x = 2*y;
     }
     foreach(const (x, y); arr) {
         static assert(is(typeof(x) == const(int)));
         static assert(is(typeof(y) == const(int)));
         assert(x == 2*y);
     }

     // works with opApply
     static struct Iota2d{
         int start,end;
         int opApply(scope int delegate(T!(int,int)) dg){
             foreach(i; start .. end) {
                 foreach(j; start ..end) {
                     if(auto r = dg(t(i,j)))
                         return r;
                 }
             }
             return 0;
         }
     }
     bool[4][4] visited;
     foreach((x, y); Iota2d(0,4)){
         visited[x][y] = true;
     }
     import std.algorithm;
     assert(visited[].all!((ref x)=>x[].all));

     // works with ranges
     import std.range;
     foreach(i, (j, k); enumerate(arr)) {
         writeln(i," ",j," ",k); // "0 4 2\n1 8 4\n"
     }

     // can unpack in lambda parameter list
     [t(1, 2), t(2, 3)].map!( ((a, b)) => a+b ).each!writeln; // "3\n5\n"

     // works with storage classes
     arr.each!( ((ref x, y)){ x = 3*y; });
     assert(arr.all!( (const (x, y)) => x == 3*y));
}
```

The code above works with my implementation, which can be found at:
https://github.com/tgehr/dmd/tree/unpacking


An unpacking declaration works if the right-hand side is an expression 
sequence or has `alias this` to an expression sequence. The number of 
elements has to match exactly.

Each variable without explicitly declared type that is unpacked to needs 
to have at least one storage class.

```d
auto (a, b) = t(1, 2); // ok
(auto a, auto b) = t(1, 2); // ok

(a, auto b) = t(1, 2); // error
(auto a, b) = t(1, 2); // error
```

This is less confusing and would allow this syntax to be used for mixed 
variable declaration and reassignment in the future.


Types can be declared explicitly, or inferred, independently for each 
variable:
```d
(int a, (string b, auto c)) = t(1, t("2", 3.0f));
```

Note that it is _not_ possible to declare a type for the whole unpacking 
explicitly:

```d
Tuple!(int, int) (a, b) = t(1, 2); // error
```

Storage classes can be applied to all unpacked variables independently.

```d
(auto a, const b, immutable c) = t(1, 2, 3);
```

Unpacking works with all variants of the `foreach` statement. (See 
synopsis for some examples, `foreach((x, y); a .. b)` can work too if 
`a` and `b` happen to be tuple-like types.) Here, storage classes are 
not required, consistent with how `foreach` works without unpacking.


Unpacking works in the parameter list of a function literal:

```d
int function(Tuple!(int, int)) f = ((x, y)) => x + y;

auto summands = t(1, 2);
writeln(f(summands))); // "3\n"
```

Unpacking does not work in the parameter list of a function that is not 
a literal. The reason for this is that there is no canonical type for 
the corresponding parameter:

```d
auto foo((int a, int b), int c){} // error
```

This restriction can be lifted at some point if we add built-in tuple types.


In `foreach` statements and in function literal parameters, the `ref` 
storage class can be applied to individual variables within an 
unpacking. They will cause the entire structure to be passed by `ref`, 
but non-`ref` unpacked variables will be initialized by value. (See 
synopsis for some examples.)

The `lazy` storage class is not supported for unpacked parameters, as 
that does not seem to make sense.



Limitations:

- The `auto ref` storage class is not currently supported on unpacking 
declarations.

- Applying the `out` storage class to individual variables within an 
unpacking is not currently supported.


I think this is a decent minimum viable product in terms of unpacking.



Future work:
- Move semantics for unpacking. (Currently it will do too many copies.)

- Do we want some way to define a manual unpacking without `alias this`?

- Do we want to be able to directly unpack static arrays and array 
slices of the correct length?

- `auto ref` support.

- `out` parameters in unpackings, e.g. `((in x, out y)){ y=x; }`

- Do we want a way to partially unpack? E.g., `auto (x, y, ...) = t(1, 
2, 3, 4);`
- Wildcards. E.g., `auto (_, x) = t;`

- General tuple syntax. (WIP at: 
https://github.com/tgehr/dmd/tree/tuple-syntax )


More information about the dip.ideas mailing list