struct

structs.zig

  1. // Declare a struct.
  2. // Zig gives no guarantees about the order of fields and the size of
  3. // the struct but the fields are guaranteed to be ABI-aligned.
  4. const Point = struct {
  5. x: f32,
  6. y: f32,
  7. };
  8. // Maybe we want to pass it to OpenGL so we want to be particular about
  9. // how the bytes are arranged.
  10. const Point2 = packed struct {
  11. x: f32,
  12. y: f32,
  13. };
  14. // Declare an instance of a struct.
  15. const p = Point {
  16. .x = 0.12,
  17. .y = 0.34,
  18. };
  19. // Maybe we're not ready to fill out some of the fields.
  20. var p2 = Point {
  21. .x = 0.12,
  22. .y = undefined,
  23. };
  24. // Structs can have methods
  25. // Struct methods are not special, they are only namespaced
  26. // functions that you can call with dot syntax.
  27. const Vec3 = struct {
  28. x: f32,
  29. y: f32,
  30. z: f32,
  31. pub fn init(x: f32, y: f32, z: f32) Vec3 {
  32. return Vec3 {
  33. .x = x,
  34. .y = y,
  35. .z = z,
  36. };
  37. }
  38. pub fn dot(self: Vec3, other: Vec3) f32 {
  39. return self.x * other.x + self.y * other.y + self.z * other.z;
  40. }
  41. };
  42. const assert = @import("std").debug.assert;
  43. test "dot product" {
  44. const v1 = Vec3.init(1.0, 0.0, 0.0);
  45. const v2 = Vec3.init(0.0, 1.0, 0.0);
  46. assert(v1.dot(v2) == 0.0);
  47. // Other than being available to call with dot syntax, struct methods are
  48. // not special. You can reference them as any other declaration inside
  49. // the struct:
  50. assert(Vec3.dot(v1, v2) == 0.0);
  51. }
  52. // Structs can have global declarations.
  53. // Structs can have 0 fields.
  54. const Empty = struct {
  55. pub const PI = 3.14;
  56. };
  57. test "struct namespaced variable" {
  58. assert(Empty.PI == 3.14);
  59. assert(@sizeOf(Empty) == 0);
  60. // you can still instantiate an empty struct
  61. const does_nothing = Empty {};
  62. }
  63. // struct field order is determined by the compiler for optimal performance.
  64. // however, you can still calculate a struct base pointer given a field pointer:
  65. fn setYBasedOnX(x: *f32, y: f32) void {
  66. const point = @fieldParentPtr(Point, "x", x);
  67. point.y = y;
  68. }
  69. test "field parent pointer" {
  70. var point = Point {
  71. .x = 0.1234,
  72. .y = 0.5678,
  73. };
  74. setYBasedOnX(&point.x, 0.9);
  75. assert(point.y == 0.9);
  76. }
  77. // You can return a struct from a function. This is how we do generics
  78. // in Zig:
  79. fn LinkedList(comptime T: type) type {
  80. return struct {
  81. pub const Node = struct {
  82. prev: ?*Node,
  83. next: ?*Node,
  84. data: T,
  85. };
  86. first: ?*Node,
  87. last: ?*Node,
  88. len: usize,
  89. };
  90. }
  91. test "linked list" {
  92. // Functions called at compile-time are memoized. This means you can
  93. // do this:
  94. assert(LinkedList(i32) == LinkedList(i32));
  95. var list = LinkedList(i32) {
  96. .first = null,
  97. .last = null,
  98. .len = 0,
  99. };
  100. assert(list.len == 0);
  101. // Since types are first class values you can instantiate the type
  102. // by assigning it to a variable:
  103. const ListOfInts = LinkedList(i32);
  104. assert(ListOfInts == LinkedList(i32));
  105. var node = ListOfInts.Node {
  106. .prev = null,
  107. .next = null,
  108. .data = 1234,
  109. };
  110. var list2 = LinkedList(i32) {
  111. .first = &node,
  112. .last = &node,
  113. .len = 1,
  114. };
  115. assert(list2.first.?.data == 1234);
  116. }
  1. $ zig test structs.zig
  2. 1/4 test "dot product"...OK
  3. 2/4 test "struct namespaced variable"...OK
  4. 3/4 test "field parent pointer"...OK
  5. 4/4 test "linked list"...OK
  6. All tests passed.

Default Field Values

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 tests passed.

extern struct

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 struct.

See also:

packed struct

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.
  • A packed enum 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 @bitCast or a @ptrCast to reinterpret memory. This even works at comptime:

test.zig

  1. const std = @import("std");
  2. const builtin = @import("builtin");
  3. const assert = std.debug.assert;
  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. assert(@sizeOf(Full) == 2);
  18. assert(@sizeOf(Divided) == 2);
  19. var full = Full{ .number = 0x1234 };
  20. var divided = @bitCast(Divided, full);
  21. switch (builtin.endian) {
  22. .Big => {
  23. assert(divided.half1 == 0x12);
  24. assert(divided.quarter3 == 0x3);
  25. assert(divided.quarter4 == 0x4);
  26. },
  27. .Little => {
  28. assert(divided.half1 == 0x34);
  29. assert(divided.quarter3 == 0x2);
  30. assert(divided.quarter4 == 0x1);
  31. },
  32. }
  33. }
  1. $ zig test test.zig
  2. 1/1 test "@bitCast between packed structs"...OK
  3. All tests passed.

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

test.zig

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

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 assert = std.debug.assert;
  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. assert(bar(&bit_field.b) == 2);
  15. }
  16. fn bar(x: *const u3) u3 {
  17. return x.*;
  18. }
  1. $ zig test test.zig
  2. /home/andy/dev/zig/docgen_tmp/test.zig:17:26: error: expected type '*const u3', found '*align(:3:1) u3'
  3. assert(bar(&bit_field.b) == 2);
  4. ^
  5. /home/andy/dev/zig/docgen_tmp/test.zig:17:15: note: referenced here
  6. assert(bar(&bit_field.b) == 2);
  7. ^

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 assert = std.debug.assert;
  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. assert(@ptrToInt(&bit_field.a) == @ptrToInt(&bit_field.b));
  15. assert(@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 tests passed.

This can be observed with @bitOffsetOf and byteOffsetOf:

test.zig

  1. const std = @import("std");
  2. const assert = std.debug.assert;
  3. const BitField = packed struct {
  4. a: u3,
  5. b: u3,
  6. c: u2,
  7. };
  8. test "pointer to non-bit-aligned field" {
  9. comptime {
  10. assert(@bitOffsetOf(BitField, "a") == 0);
  11. assert(@bitOffsetOf(BitField, "b") == 3);
  12. assert(@bitOffsetOf(BitField, "c") == 6);
  13. assert(@byteOffsetOf(BitField, "a") == 0);
  14. assert(@byteOffsetOf(BitField, "b") == 0);
  15. assert(@byteOffsetOf(BitField, "c") == 0);
  16. }
  17. }
  1. $ zig test test.zig
  2. 1/1 test "pointer to non-bit-aligned field"...OK
  3. All 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

  1. const S = packed struct {
  2. a: u32,
  3. b: u32,
  4. };
  5. test "overaligned pointer to packed struct" {
  6. var foo: S align(4) = undefined;
  7. const ptr: *align(4) S = &foo;
  8. const ptr_to_b: *u32 = &ptr.b;
  9. }
  1. $ zig test test.zig
  2. /home/andy/dev/zig/docgen_tmp/test.zig:8:32: error: expected type '*u32', found '*align(1) u32'
  3. const ptr_to_b: *u32 = &ptr.b;
  4. ^
  5. /home/andy/dev/zig/docgen_tmp/test.zig:8:5: note: referenced here
  6. const ptr_to_b: *u32 = &ptr.b;
  7. ^

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 planned to be able to set alignment of struct fields.

Using packed structs with volatile 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.

struct Naming

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.warn("variable: {}\n", @typeName(Foo));
  5. std.debug.warn("anonymous: {}\n", @typeName(struct {}));
  6. std.debug.warn("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:49
  5. function: List(i32)

See also: