Skip to content

Struct Declarations

A struct is a type made from named fields.

A struct is a type made from named fields.

const Point = struct {
    x: i32,
    y: i32,
};

This declares a type named Point. It has two fields, x and y. Both fields have type i32.

A value of this type is written with a struct literal:

const p = Point{
    .x = 10,
    .y = 20,
};

The field names are part of the literal. This makes the code clear when a struct has many fields.

A complete program:

const std = @import("std");

const Point = struct {
    x: i32,
    y: i32,
};

pub fn main() void {
    const p = Point{
        .x = 10,
        .y = 20,
    };

    std.debug.print("x = {d}, y = {d}\n", .{ p.x, p.y });
}

The output is:

x = 10, y = 20

Fields are selected with the dot operator:

p.x
p.y

The dot is used both when constructing a value and when reading a field. In a literal, .x = 10 means “set the field named x.” In an expression, p.x means “read the field x from p.”

A struct value may be mutable.

const std = @import("std");

const Point = struct {
    x: i32,
    y: i32,
};

pub fn main() void {
    var p = Point{
        .x = 10,
        .y = 20,
    };

    p.x = 30;

    std.debug.print("x = {d}, y = {d}\n", .{ p.x, p.y });
}

The output is:

x = 30, y = 20

The variable p is declared with var, so its fields may be changed. If p were declared with const, the assignment to p.x would be rejected.

A struct can contain fields of different types.

const User = struct {
    id: u64,
    name: []const u8,
    active: bool,
};

Here name is a slice of constant bytes. In small programs, this is the usual type for a string value.

const user = User{
    .id = 1,
    .name = "alice",
    .active = true,
};

The type of "alice" fits []const u8.

Struct declarations may be nested inside other declarations.

const std = @import("std");

pub fn main() void {
    const Pair = struct {
        left: i32,
        right: i32,
    };

    const pair = Pair{
        .left = 3,
        .right = 4,
    };

    std.debug.print("{d}, {d}\n", .{ pair.left, pair.right });
}

A struct type can also be anonymous. This is useful for small temporary values.

const value = .{
    .name = "zig",
    .year = 2026,
};

Here Zig infers an anonymous struct type. The value has fields named name and year.

Anonymous structs are often used as arguments to formatting functions:

std.debug.print("{s} {d}\n", .{ "Zig", 2026 });

The expression .{ "Zig", 2026 } is also an anonymous struct, but with numbered fields instead of named fields. This form is called a tuple.

A struct may have default field values.

const Config = struct {
    verbose: bool = false,
    retries: u8 = 3,
};

Then a literal may omit those fields:

const config = Config{};

This is the same as:

const config = Config{
    .verbose = false,
    .retries = 3,
};

A literal may override only the fields it needs:

const config = Config{
    .verbose = true,
};

Now verbose is true and retries is still 3.

Structs may contain declarations as well as fields.

const Point = struct {
    x: i32,
    y: i32,

    const zero = Point{
        .x = 0,
        .y = 0,
    };
};

The declaration zero belongs to the namespace of Point.

It is accessed with:

Point.zero

A struct is both a layout of data and a namespace. This is important in Zig. Functions associated with a type are just declarations inside the struct. There is no separate class system.

For now, use a struct when several values should move together under one name.

const Rectangle = struct {
    width: u32,
    height: u32,
};

This is clearer than passing two unrelated integers through the program.

Exercises.

7-1. Define a struct named Book with fields title, pages, and published.

7-2. Create a Book value and print its fields.

7-3. Define a struct named Counter with one field, value: u32. Make a mutable counter and increment it.

7-4. Define a Config struct with default values. Create one value using all defaults, and another value overriding one field.