⚡ Zig Guide LiveUnofficial
✓ Zig 0.17.0-dev.1454+5faa79730On an older Zig?

Structs

const std = @import("std");
const expect = std.testing.expect;

const Vec3 = struct {
    x: f32 = 0, // default value
    y: f32 = 0,
    z: f32 = 0,

    pub fn dot(a: Vec3, b: Vec3) f32 {
        return a.x * b.x + a.y * b.y + a.z * b.z;
    }

    // Taking `*Vec3` lets the method mutate the receiver.
    pub fn scale(self: *Vec3, factor: f32) void {
        self.x *= factor;
        self.y *= factor;
        self.z *= factor;
    }
};

test "construct and read" {
    const v = Vec3{ .x = 1, .y = 2, .z = 3 };
    try expect(v.y == 2);
}

test "defaults fill in the rest" {
    const v = Vec3{ .x = 5 };
    try expect(v.y == 0 and v.z == 0);
}

test "methods" {
    const a = Vec3{ .x = 1, .y = 2, .z = 3 };
    const b = Vec3{ .x = 4, .y = 5, .z = 6 };
    try expect(Vec3.dot(a, b) == 32);
    try expect(a.dot(b) == 32); // same call, method syntax
}

test "mutating methods need a mutable receiver" {
    var v = Vec3{ .x = 1, .y = 1, .z = 1 };
    v.scale(3);
    try expect(v.x == 3);
}

test "field order is not guaranteed" {
    // Zig may reorder fields for packing unless you say otherwise with
    // `extern struct` (C ABI) or `packed struct` (bit-level layout).
    try expect(@sizeOf(Vec3) == 12);
}

Defaults

Fields may declare a default, so a literal only mentions what differs:

const v = Vec3{ .x = 5 };   // y and z default to 0

A field with no default must be supplied; there is no zero-initialisation by fiat.

Methods and the receiver

A method taking self: Vec3 gets a copy; one taking self: *Vec3 can mutate the original. Calling a *Vec3 method requires a mutable value: v must be a var, and Zig takes the address for you.

Choose the receiver deliberately: Vec3 for small value types you want copied, *const Vec3 for large ones you only read, *Vec3 to mutate.

Layout is not guaranteed

A plain struct may have its fields reordered and padded however the compiler sees fit. When the layout matters, say so:

DeclarationLayout
structunspecified; optimiser’s choice
extern structC ABI compatible
packed structbit-level, well-defined, backed by an integer

Reaching for extern or packed “just in case” costs you the packing the optimiser would otherwise do, so use them only at real boundaries.