1. 1/4 test "dot product"... OK
    2. 2/4 test "struct namespaced variable"... OK
    3. 3/4 test "field parent pointer"... OK
    4. 4/4 test "linked list"... OK
    5. All 4 tests passed.

    Each struct field may have an expression indicating the default field value. Such expressions are executed at comptime, and allow the field to be omitted in a struct literal expression:

    test.zig

    1. const Foo = struct {
    2. a: i32 = 1234,
    3. b: i32,
    4. };
    5. test "default struct initialization fields" {
    6. const x = Foo{
    7. .b = 5,
    8. };
    9. if (x.a + x.b != 1239) {
    10. @compileError("it's even comptime known!");
    11. }
    12. }
    1. $ zig test test.zig
    2. 1/1 test "default struct initialization fields"... OK
    3. All 1 tests passed.

    An extern struct has in-memory layout guaranteed to match the C ABI for the target.

    This kind of struct should only be used for compatibility with the C ABI. Every other use case should be solved with or normal struct.

    See also:

    Unlike normal structs, packed structs have guaranteed in-memory layout:

    • Fields remain in the order declared.
    • There is no padding between fields.
    • Zig supports arbitrary width and although normally, integers with fewer than 8 bits will still use 1 byte of memory, in packed structs, they use exactly their bit width.
    • bool fields use exactly 1 bit.
    • A packed enum field uses exactly the bit width of its integer tag type.
    • A field uses exactly the bit width of the union field with the largest bit width.
    • Non-ABI-aligned fields are packed into the smallest possible ABI-aligned integers in accordance with the target endianness.

    This means that a packed struct can participate in a @bitCast or a to reinterpret memory. This even works at comptime:

    test.zig

    1. const std = @import("std");
    2. const builtin = std.builtin;
    3. const expect = std.testing.expect;
    4. const Full = packed struct {
    5. number: u16,
    6. };
    7. const Divided = packed struct {
    8. half1: u8,
    9. quarter3: u4,
    10. quarter4: u4,
    11. };
    12. test "@bitCast between packed structs" {
    13. doTheTest();
    14. comptime doTheTest();
    15. }
    16. fn doTheTest() void {
    17. expect(@sizeOf(Full) == 2);
    18. expect(@sizeOf(Divided) == 2);
    19. var full = Full{ .number = 0x1234 };
    20. var divided = @bitCast(Divided, full);
    21. switch (builtin.endian) {
    22. .Big => {
    23. expect(divided.half1 == 0x12);
    24. expect(divided.quarter3 == 0x3);
    25. expect(divided.quarter4 == 0x4);
    26. },
    27. .Little => {
    28. expect(divided.half1 == 0x34);
    29. expect(divided.quarter3 == 0x2);
    30. expect(divided.quarter4 == 0x1);
    31. },
    32. }
    33. }
    1. $ zig test test.zig
    2. 1/1 test "@bitCast between packed structs"... OK
    3. All 1 tests passed.

    Zig allows the address to be taken of a non-byte-aligned field:

    1. const expect = std.testing.expect;
    2. const BitField = packed struct {
    3. a: u3,
    4. b: u3,
    5. c: u2,
    6. var foo = BitField{
    7. .a = 1,
    8. .b = 2,
    9. .c = 3,
    10. };
    11. test "pointer to non-byte-aligned field" {
    12. const ptr = &foo.b;
    13. expect(ptr.* == 2);
    14. }

    However, the pointer to a non-byte-aligned field has special properties and cannot be passed when a normal pointer is expected:

    test.zig

    1. const std = @import("std");
    2. const expect = std.testing.expect;
    3. const BitField = packed struct {
    4. a: u3,
    5. b: u3,
    6. c: u2,
    7. };
    8. var bit_field = BitField{
    9. .a = 1,
    10. .b = 2,
    11. .c = 3,
    12. };
    13. test "pointer to non-bit-aligned field" {
    14. expect(bar(&bit_field.b) == 2);
    15. }
    16. fn bar(x: *const u3) u3 {
    17. return x.*;
    18. }
    1. $ zig test test.zig
    2. ./docgen_tmp/test.zig:17:26: error: expected type '*const u3', found '*align(:3:1) u3'
    3. expect(bar(&bit_field.b) == 2);
    4. ^

    In this case, the function bar cannot be called becuse the pointer to the non-ABI-aligned field mentions the bit offset, but the function expects an ABI-aligned pointer.

    Pointers to non-ABI-aligned fields share the same address as the other fields within their host integer:

    test.zig

    1. const std = @import("std");
    2. const expect = std.testing.expect;
    3. const BitField = packed struct {
    4. a: u3,
    5. b: u3,
    6. c: u2,
    7. };
    8. var bit_field = BitField{
    9. .a = 1,
    10. .b = 2,
    11. .c = 3,
    12. };
    13. test "pointer to non-bit-aligned field" {
    14. expect(@ptrToInt(&bit_field.a) == @ptrToInt(&bit_field.b));
    15. expect(@ptrToInt(&bit_field.a) == @ptrToInt(&bit_field.c));
    16. }
    1. $ zig test test.zig
    2. 1/1 test "pointer to non-bit-aligned field"... OK
    3. All 1 tests passed.

    This can be observed with and byteOffsetOf:

    test.zig

    1. const std = @import("std");
    2. const expect = std.testing.expect;
    3. const BitField = packed struct {
    4. a: u3,
    5. b: u3,
    6. c: u2,
    7. };
    8. comptime {
    9. expect(@bitOffsetOf(BitField, "b") == 3);
    10. expect(@bitOffsetOf(BitField, "c") == 6);
    11. expect(@byteOffsetOf(BitField, "a") == 0);
    12. expect(@byteOffsetOf(BitField, "b") == 0);
    13. expect(@byteOffsetOf(BitField, "c") == 0);
    14. }
    15. }
    1. $ zig test test.zig
    2. 1/1 test "pointer to non-bit-aligned field"... OK
    3. All 1 tests passed.

    Packed structs have 1-byte alignment. However if you have an overaligned pointer to a packed struct, Zig should correctly understand the alignment of fields. However there is :

    test.zig

    1. $ zig test test.zig
    2. ./docgen_tmp/test.zig:8:32: error: expected type '*u32', found '*align(1) u32'
    3. const ptr_to_b: *u32 = &ptr.b;
    4. ^

    It’s also planned to be able to set alignment of struct fields.

    Using packed structs with is problematic, and may be a compile error in the future. For details on this subscribe to this issue. TODO update these docs with a recommendation on how to use packed structs with MMIO (the use case for volatile packed structs) once this issue is resolved. Don’t worry, there will be a good solution for this use case in zig.

    Since all structs are anonymous, Zig infers the type name based on a few rules.

    • If the struct is in the initialization expression of a variable, it gets named after that variable.
    • If the struct is in the return expression, it gets named after the function it is returning from, with the parameter values serialized.
    • Otherwise, the struct gets a name such as (anonymous struct at file.zig:7:38).

    struct_name.zig

    1. const std = @import("std");
    2. pub fn main() void {
    3. const Foo = struct {};
    4. std.debug.print("variable: {}\n", .{@typeName(Foo)});
    5. std.debug.print("anonymous: {}\n", .{@typeName(struct {})});
    6. std.debug.print("function: {}\n", .{@typeName(List(i32))});
    7. }
    8. fn List(comptime T: type) type {
    9. return struct {
    10. x: T,
    11. };
    12. }
    1. $ zig build-exe struct_name.zig
    2. $ ./struct_name
    3. variable: Foo
    4. anonymous: struct:6:52
    5. function: List(i32)

    Zig allows omitting the struct type of a literal. When the result is , the struct literal will directly instantiate the result location, with no copy:

    struct_result.zig

    1. const std = @import("std");
    2. const expect = std.testing.expect;
    3. const Point = struct {x: i32, y: i32};
    4. test "anonymous struct literal" {
    5. var pt: Point = .{
    6. .x = 13,
    7. .y = 67,
    8. };
    9. expect(pt.x == 13);
    10. expect(pt.y == 67);
    11. }
    1. $ zig test struct_result.zig
    2. 1/1 test "anonymous struct literal"... OK
    3. All 1 tests passed.

    The struct type can be inferred. Here the result location does not include a type, and so Zig infers the type:

    struct_anon.zig

    1. const std = @import("std");
    2. const expect = std.testing.expect;
    3. test "fully anonymous struct" {
    4. dump(.{
    5. .int = @as(u32, 1234),
    6. .float = @as(f64, 12.34),
    7. .b = true,
    8. .s = "hi",
    9. });
    10. }
    11. fn dump(args: anytype) void {
    12. expect(args.int == 1234);
    13. expect(args.float == 12.34);
    14. expect(args.b);
    15. expect(args.s[0] == 'h');
    16. expect(args.s[1] == 'i');
    17. }

    See also: