struct
Shell
1/4 test "dot product"... OK
2/4 test "struct namespaced variable"... OK
3/4 test "field parent pointer"... OK
4/4 test "linked list"... OK
All 4 tests passed.
Each struct field may have an expression indicating the default field value. Such expressions are executed at , and allow the field to be omitted in a struct literal expression:
default_field_values.zig
const Foo = struct {
a: i32 = 1234,
b: i32,
};
test "default struct initialization fields" {
const x = Foo{
.b = 5,
};
if (x.a + x.b != 1239) {
@compileError("it's even comptime known!");
}
}
Shell
$ zig test default_field_values.zig
1/1 test "default struct initialization fields"... OK
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 packed struct or normal .
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 Integers 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.- An field uses exactly the bit width of its integer tag type.
- A packed union 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 or a @ptrCast to reinterpret memory. This even works at :
packed_structs.zig
const std = @import("std");
const native_endian = @import("builtin").target.cpu.arch.endian();
const expect = std.testing.expect;
const Full = packed struct {
number: u16,
};
const Divided = packed struct {
half1: u8,
quarter3: u4,
quarter4: u4,
};
test "@bitCast between packed structs" {
try doTheTest();
comptime try doTheTest();
}
fn doTheTest() !void {
try expect(@sizeOf(Full) == 2);
try expect(@sizeOf(Divided) == 2);
var full = Full{ .number = 0x1234 };
var divided = @bitCast(Divided, full);
switch (native_endian) {
.Big => {
try expect(divided.half1 == 0x12);
try expect(divided.quarter3 == 0x3);
try expect(divided.quarter4 == 0x4);
},
.Little => {
try expect(divided.half1 == 0x34);
try expect(divided.quarter3 == 0x2);
try expect(divided.quarter4 == 0x1);
},
}
}
Shell
$ zig test packed_structs.zig
1/1 test "@bitCast between packed structs"... OK
All 1 tests passed.
Zig allows the address to be taken of a non-byte-aligned field:
pointer_to_non-byte_aligned_field.zig
const expect = std.testing.expect;
const BitField = packed struct {
a: u3,
b: u3,
c: u2,
};
var foo = BitField{
.a = 1,
.b = 2,
.c = 3,
};
test "pointer to non-byte-aligned field" {
const ptr = &foo.b;
try expect(ptr.* == 2);
}
Shell
$ zig test pointer_to_non-byte_aligned_field.zig
1/1 test "pointer to non-byte-aligned field"... OK
All 1 tests passed.
However, the pointer to a non-byte-aligned field has special properties and cannot be passed when a normal pointer is expected:
Shell
$ zig test test.zig
./docgen_tmp/test.zig:17:30: error: expected type '*const u3', found '*align(:3:1) u3'
try expect(bar(&bit_field.b) == 2);
^
In this case, the function bar
cannot be called because 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:
pointer_to_non-bit_aligned_field.zig
const std = @import("std");
const expect = std.testing.expect;
const BitField = packed struct {
a: u3,
b: u3,
c: u2,
};
var bit_field = BitField{
.a = 1,
.b = 2,
.c = 3,
};
test "pointer to non-bit-aligned field" {
try expect(@ptrToInt(&bit_field.a) == @ptrToInt(&bit_field.b));
try expect(@ptrToInt(&bit_field.a) == @ptrToInt(&bit_field.c));
}
Shell
$ zig test pointer_to_non-bit_aligned_field.zig
1/1 test "pointer to non-bit-aligned field"... OK
All 1 tests passed.
This can be observed with @bitOffsetOf and :
test_bitOffsetOf_offsetOf.zig
const std = @import("std");
const expect = std.testing.expect;
const BitField = packed struct {
a: u3,
b: u3,
c: u2,
};
test "pointer to non-bit-aligned field" {
comptime {
try expect(@bitOffsetOf(BitField, "a") == 0);
try expect(@bitOffsetOf(BitField, "b") == 3);
try expect(@bitOffsetOf(BitField, "c") == 6);
try expect(@offsetOf(BitField, "a") == 0);
try expect(@offsetOf(BitField, "b") == 0);
try expect(@offsetOf(BitField, "c") == 0);
}
}
Shell
$ zig test test_bitOffsetOf_offsetOf.zig
1/1 test "pointer to non-bit-aligned field"... OK
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 a bug:
test.zig
const S = packed struct {
a: u32,
b: u32,
};
test "overaligned pointer to packed struct" {
const ptr: *align(4) S = &foo;
const ptr_to_b: *u32 = &ptr.b;
_ = ptr_to_b;
Shell
$ zig test test.zig
./docgen_tmp/test.zig:8:32: error: expected type '*u32', found '*align(1) u32'
const ptr_to_b: *u32 = &ptr.b;
^
When this bug is fixed, the above test in the documentation will unexpectedly pass, which will cause the test suite to fail, notifying the bug fixer to update these docs.
It’s also possible to set alignment of struct fields:
test_aligned_struct_fields.zig
Shell
$ zig test test_aligned_struct_fields.zig
1/1 test "aligned struct fields"... OK
All 1 tests passed.
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
const std = @import("std");
pub fn main() void {
const Foo = struct {};
std.debug.print("variable: {s}\n", .{@typeName(Foo)});
std.debug.print("anonymous: {s}\n", .{@typeName(struct {})});
std.debug.print("function: {s}\n", .{@typeName(List(i32))});
}
fn List(comptime T: type) type {
return struct {
x: T,
};
}
Shell
$ zig build-exe struct_name.zig
$ ./struct_name
variable: Foo
anonymous: struct:6:53
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
const std = @import("std");
const expect = std.testing.expect;
const Point = struct {x: i32, y: i32};
test "anonymous struct literal" {
var pt: Point = .{
.x = 13,
.y = 67,
};
try expect(pt.x == 13);
try expect(pt.y == 67);
}
Shell
$ zig test struct_result.zig
1/1 test "anonymous struct literal"... OK
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
const std = @import("std");
const expect = std.testing.expect;
test "fully anonymous struct" {
try dump(.{
.int = @as(u32, 1234),
.float = @as(f64, 12.34),
.b = true,
.s = "hi",
});
}
fn dump(args: anytype) !void {
try expect(args.int == 1234);
try expect(args.float == 12.34);
try expect(args.b);
try expect(args.s[0] == 'h');
try expect(args.s[1] == 'i');
}
Shell
$ zig test struct_anon.zig
1/1 test "fully anonymous struct"... OK
All 1 tests passed.
Anonymous structs can be created without specifying field names, and are referred to as “tuples”.
The fields are implicitly named using numbers starting from 0. Because their names are integers, the @"0"
syntax must be used to access them. Names inside @""
are always recognised as identifiers.
Like arrays, tuples have a .len field, can be indexed and work with the ++ and ** operators. They can also be iterated over with inline for.
tuple.zig
Shell
$ zig test tuple.zig
1/1 test "tuple"... OK
All 1 tests passed.
See also: