Thread overview
September 06
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 )
September 06
Looks quite nice.

A nice consequence of the type/storage class being required, is that we'd be able to support struct unpacking for fields as well with my proposed member-of-operator.

``(:field name) = s;``

I can probably assist in a first iteration of the DIP document after I've done member-of-operator which is up next. If that would be helpful.

Regardless, thanks for doing the thread write up, it seems fairly sane and expandable!
September 06
On 9/6/24 00:08, Timon Gehr wrote:
> These are my ideas for how to support unpacking any tuple-like type into multiple components.
> 
> ...

Jacob asked on Discord whether in a `foreach` statement, key and value can both be unpacked, and whether in a foreach statement, `(const x, y)` is also allowed. Both of those work. In particular, this compiles and runs with my `unpacking` branch of DMD:

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

void main(){
    auto aa=[t(1, 2): t(3, 4), t(5, 6): t(7, 8)];
    foreach((const k0, k1), (v0, ref v1); aa){
        k1 = k0;
        v1 = v0;
        v0 = 22;
    }
    assert(aa == [t(1, 2): t(3, 3), t(5, 6): t(7, 7)]);
    foreach((const x, y); [t(1, 2), t(2, 3)]){}
}
```
September 06
On Thursday, 5 September 2024 at 22:08:58 UTC, Timon Gehr wrote:
> These are my ideas for how to support unpacking any tuple-like type into multiple components.
>
> [...]

This is an excellent improvement to language practical usability.
A big "thank you" to Timon, and a big +1 for going forward with it from my side.

/Paolo


September 16
On Thursday, 5 September 2024 at 22:08:58 UTC, Timon Gehr wrote:
> These are my ideas for how to support unpacking any tuple-like type into multiple components.

Looks good. Now that "ref" is allowed in variable declarations, any reason not to allow it in unpacking ones?


September 23
On 9/16/24 10:49, Max Samukha wrote:
> On Thursday, 5 September 2024 at 22:08:58 UTC, Timon Gehr wrote:
>> These are my ideas for how to support unpacking any tuple-like type into multiple components.
> 
> Looks good. Now that "ref" is allowed in variable declarations, any reason not to allow it in unpacking ones?
> 

I think it will just work or not work the same way as in non-unpacking accesses to tuple indices.