New Destructuring Syntax

It looks like @mlugg has done in incredible job at implementing a destructuring syntax into the compiler

Pull Request:

Proposal:

To quote his commit:

This change implements the following syntax into the compiler:

const x: u32, var y, foo.bar = .{ 1, 2, 3 };

A destructure expression may only appear within a block (i.e. not at
comtainer scope). The LHS consists of a sequence of comma-separated var
decls and/or lvalue expressions. The RHS is a normal expression.

A new result location type, destructure, is used, which contains
result pointers for each component of the destructure. This means that
when the RHS is a more complicated expression, peer type resolution is
not used: each result value is individually destructured and written to
the result pointers. RLS is always used for destructure expressions,
meaning every const on the LHS of such an expression creates a true
stack allocation.

Aside from anonymous array literals, Sema is capable of destructuring
the following types:

  • Tuples
  • Arrays
  • Vectors

A destructure may be prefixed with the comptime keyword, in which case
the entire destructure is evaluated at comptime: this means all vars
in the LHS are comptime vars, every lvalue expression is evaluated at
comptime, and the RHS is evaluated at comptime. If every LHS is a
const, this is not allowed: as with single declarations, the user
should instead mark the RHS as comptime.

There are a few subtleties in the grammar changes here. For one thing,
if every LHS is an lvalue expression (rather than a var decl), a
destructure is considered an expression. This makes, for instance,
if (cond) x, y = .{ 1, 2 }; valid Zig code. A destructure is allowed
in almost every context where a standard assignment expression is
permitted. The exception is switch prongs, which cannot be
destructures as the comma is ambiguous with the end of the prong.

A follow-up commit will begin utilizing this syntax in the Zig compiler.

16 Likes

happy to see this implemented, though i’m a bit surprised that it doesn’t seem to work on struct initializers

const std = @import("std");

pub const Vec3f = struct {
    x: f32,
    y: f32,
    z: f32,
};

pub fn getSome3f() struct { f32, f32, f32 } {
    return .{ 3.0, 1.1, 2.6 };
}

pub fn main() !void {
    const coords = Vec3f{ .x, .y, .z = getSome3f() };
    _ = coords;
}

error: expected ‘,’ after initializer

Personally I could never understand what the point of this would be for struct initializers as you can have default initializers on any field and then just do var foo: Foo = .{};, or if you want multiple “constructors” you could just have a dedicated function in Foo’s namespace and call it instead like var foo = Foo.initSome();.

4 Likes

I believe that you said it: “var”. You have to assign things to a “var” and then assign things to a “const”.

I believe the new syntax means you can just assign straight to the "const"s.

I think this also has implications with respect to shadowing.

Hmm I don’t think I understand your argument. The code snippets I brought up will work with const too so const foo: Foo = .{}; and const foo = Foo.initSome() are all OK. The only time where this would fall apart is if it was not possible to evaluate initSome() at compile-time.

1 Like

No actually, scratch my last remark. const foo = Foo.initSome() should still work in local scope even if initSome() would not be comptime. The only time it wouldn’t work is if we were trying to evaluate it in comptime-scope such as as a global-scoped const, etc.

1 Like

Specifically, something like

    // Old syntax
    const retval = blk: {
        break :blk .{89, 92};
    };
    const p0 = retval[0];
    const p1 = retval[1];

    // New syntax
    const p0, const p1 = blk: {
        break :blk .{89, 92};
    };

where there is a lot of extra ceremony when all you really want to do is peel apart multiple associated values.

I do agree, I can’t seem to come up with a case where “var” is getting in the way. :thinking:

I’ve got a bunch of these kinds of coupled assigns in lots of Vulkan code. I’ll go through them at some point and see if I can cough up some of the edge cases.

1 Like

Mhm, I see. I do agree with the usage of destructuring in your example of by-passing the need for tuples, but I still don’t see any point in allowing destructuring into struct fields as mentioned in the original post I was replying to :slight_smile: