Skip to content

Proposal: Extend the tuple semantics #12330

@ikskuh

Description

@ikskuh

As discussed on the Discord yesterday:

The Problem

Right now, the definition of packed struct doesn't allow to include arrays. This is due to slicing an array should be a supported operation, but a bit-packed array will not have the properties of being sliceable, as it requires pointers that include bit offset properties.

Proposed solution

This proposal extends the semantics of packed struct as described in #10113 by adding a tiny feature to #4335:

  • Allow tuples to be bit-packed (packed struct {u2,u2,u2,u2}) or extern(extern struct {u8, u16}).
  • Allow bit-packed tuples in packed structs.

Evaluation

Right now, bit-packed tuples are a feature of stage1, whereas stage2 will produce a compile error:

test {
    // const T = packed struct { u2, u2, u2, u2 }
    const T = @Type(.{
        .Struct = .{
            .layout = .Packed,
            .fields = &.{
                .{ .name = "0", .field_type = u2, .default_value = null, .is_comptime = false, .alignment = 0 },
                .{ .name = "1", .field_type = u2, .default_value = null, .is_comptime = false, .alignment = 0 },
                .{ .name = "2", .field_type = u2, .default_value = null, .is_comptime = false, .alignment = 0 },
                .{ .name = "3", .field_type = u2, .default_value = null, .is_comptime = false, .alignment = 0 },
            },
            .decls = &.{},
            .is_tuple = true,
        },
    });

    @compileLog(T, @sizeOf(T), @alignOf(T), @bitSizeOf(T));
    @compileLog(@offsetOf(T, "0"), @bitOffsetOf(T, "0"));
    @compileLog(@offsetOf(T, "1"), @bitOffsetOf(T, "1"));
    @compileLog(@offsetOf(T, "2"), @bitOffsetOf(T, "2"));
    @compileLog(@offsetOf(T, "3"), @bitOffsetOf(T, "3"));

    const J = packed struct {
        a: u3,
        b: T,
        c: u5,
    };

    @compileLog(J, @sizeOf(J), @alignOf(J), @bitSizeOf(J));
    @compileLog(@offsetOf(J, "a"), @bitOffsetOf(J, "a"));
    @compileLog(@offsetOf(J, "b"), @bitOffsetOf(J, "b"));
    @compileLog(@offsetOf(J, "c"), @bitOffsetOf(J, "c"));
}

Compiling this code with stage1 yields the following:

| struct:4:22, 1, 1, 8
| 0, 0
| 0, 2
| 0, 4
| 0, 6
| J, 2, 1, 16
| 0, 0
| 0, 3
| 1, 11

This looks about right and follows the semantics of this propsal, but probably more by accident and less by semantic decisions.

But using stage2 will give us this error:

tup.zig:26:9: error: packed structs cannot contain fields of type 'tuple{u2, u2, u2, u2}'
        b: T,
        ^~~~
tup.zig:26:9: note: only packed structs layout are allowed in packed types

Compile Log Output:
@as(type, tuple{u2, u2, u2, u2}), @as(comptime_int, 4), @as(comptime_int, 1), @as(comptime_int, 8)
@as(comptime_int, 0), @as(comptime_int, 0)
@as(comptime_int, 1), @as(comptime_int, 8)
@as(comptime_int, 2), @as(comptime_int, 16)
@as(comptime_int, 3), @as(comptime_int, 24)

Which looks like the tuple type is properly recognized as a tuple, but the layout is completly ignored and will be set to .Auto.

Allowing a tuple to be put into a packed struct as long as the tuple would have a packed layout would make the language more generalized and allows for some nice uses.

Use Cases

As with other tuples, a packed tuple can be indexed with comptime known indices:

// using the same types as above:
test {
    var t: T = .{ 0, 1, 2, 3 };

    var j: J = .{
        .a = 3,
        .b = t,
        .c = 11,
    };

    j.b[1] = 1;
    j.b[3] = 2;

    std.debug.print("a={} b={{ {}, {}, {}, {} }}, c={}\n", .{
      j.a,
      j.b[0],
      j.b[1],
      j.b[2],
      j.b[3],
      j.c,
    });
}

This allows us to have quasi-arrays in a packed struct by grouping fields into indexed field structs instead of structs with named fields.

Other implications are that users of Zig can now create smaller tuples when it seems appropiate. One example here would be a packed struct { u31, bool } which can be passed around in a register instead of using either memory or two registers, saving us 32 bit of padding per element.

Pros

  • Makes tuples and structs more alike, as a tuple struct is just a struct with a flag and decimal field names. It should be possible to allow packed or extern layout.
  • Allows quasi-arrays in packed structs

Cons

I can't think of any negative downsides of this proposal, as it only slightly increases the syntactical surface, but at the same time removes forbidden properties, so actually less stuff to remember.

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalThis issue suggests modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions