struct
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 comptime, and allow the field to be omitted in a struct literal expression:
test.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!");
}
}
$ zig test test.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 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
const std = @import("std");
const builtin = std.builtin;
const assert = std.debug.assert;
const Full = packed struct {
number: u16,
};
const Divided = packed struct {
half1: u8,
quarter3: u4,
quarter4: u4,
};
test "@bitCast between packed structs" {
doTheTest();
comptime doTheTest();
}
fn doTheTest() void {
assert(@sizeOf(Full) == 2);
assert(@sizeOf(Divided) == 2);
var full = Full{ .number = 0x1234 };
var divided = @bitCast(Divided, full);
switch (builtin.endian) {
.Big => {
assert(divided.half1 == 0x12);
assert(divided.quarter3 == 0x3);
assert(divided.quarter4 == 0x4);
},
.Little => {
assert(divided.half1 == 0x34);
assert(divided.quarter3 == 0x2);
assert(divided.quarter4 == 0x1);
},
}
}
$ zig test test.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:
const assert = std.debug.assert;
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;
assert(ptr.* == 2);
}
However, the pointer to a non-byte-aligned field has special properties and cannot be passed when a normal pointer is expected:
test.zig
const std = @import("std");
const assert = std.debug.assert;
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" {
assert(bar(&bit_field.b) == 2);
}
fn bar(x: *const u3) u3 {
return x.*;
}
$ zig test test.zig
./docgen_tmp/test.zig:17:26: error: expected type '*const u3', found '*align(:3:1) u3'
assert(bar(&bit_field.b) == 2);
^
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
const std = @import("std");
const assert = std.debug.assert;
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" {
assert(@ptrToInt(&bit_field.a) == @ptrToInt(&bit_field.b));
assert(@ptrToInt(&bit_field.a) == @ptrToInt(&bit_field.c));
}
$ zig test test.zig
1/1 test "pointer to non-bit-aligned field"...OK
All 1 tests passed.
This can be observed with and byteOffsetOf:
test.zig
const std = @import("std");
const assert = std.debug.assert;
const BitField = packed struct {
a: u3,
b: u3,
c: u2,
};
comptime {
assert(@bitOffsetOf(BitField, "b") == 3);
assert(@bitOffsetOf(BitField, "c") == 6);
assert(@byteOffsetOf(BitField, "a") == 0);
assert(@byteOffsetOf(BitField, "b") == 0);
assert(@byteOffsetOf(BitField, "c") == 0);
}
}
$ zig test test.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 :
test.zig
$ 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;
^
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
const std = @import("std");
pub fn main() void {
const Foo = struct {};
std.debug.warn("variable: {}\n", .{@typeName(Foo)});
std.debug.warn("anonymous: {}\n", .{@typeName(struct {})});
std.debug.warn("function: {}\n", .{@typeName(List(i32))});
}
fn List(comptime T: type) type {
return struct {
x: T,
};
}
$ zig build-exe struct_name.zig
$ ./struct_name
variable: Foo
anonymous: struct:6:51
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 assert = std.debug.assert;
const Point = struct {x: i32, y: i32};
test "anonymous struct literal" {
var pt: Point = .{
.x = 13,
.y = 67,
};
assert(pt.x == 13);
assert(pt.y == 67);
}
$ 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 assert = std.debug.assert;
test "fully anonymous struct" {
dump(.{
.int = @as(u32, 1234),
.float = @as(f64, 12.34),
.b = true,
.s = "hi",
});
}
fn dump(args: var) void {
assert(args.int == 1234);
assert(args.float == 12.34);
assert(args.b);
assert(args.s[0] == 'h');
assert(args.s[1] == 'i');
}
See also: