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

Anonymous Structs

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

fn describe(point: struct { x: i32, y: i32 }) i32 {
    return point.x + point.y;
}

test "anonymous struct literal" {
    // The type is inferred from context, so the name need not be written.
    try expect(describe(.{ .x = 1, .y = 2 }) == 3);
}

test "tuples are anonymous structs with numeric fields" {
    const tuple = .{ @as(u8, 1), true, @as(f32, 2.5) };
    try expect(tuple.len == 3);
    try expect(tuple[0] == 1);
    try expect(tuple[1] == true);
}

test "tuples hold mixed types" {
    const pair = .{ @as(u32, 7), "seven" };
    try expect(pair[0] == 7);
    try expect(std.mem.eql(u8, pair[1], "seven"));
}

test "this is how format arguments work" {
    // `.{ a, b }` in a print call is just a tuple; the format string is
    // checked against it at compile time.
    var buf: [32]u8 = undefined;
    const text = try std.fmt.bufPrint(&buf, "{s}={d}", .{ "x", 42 });
    try expect(std.mem.eql(u8, text, "x=42"));
}

.{ ... } is a struct literal whose type comes from context. When the target type is known (a parameter, a field, a return type), the name is redundant:

describe(.{ .x = 1, .y = 2 });

Tuples

Leave out the field names and you get a tuple: a struct whose fields are numbered. Tuples have .len, support indexing, and may mix types freely:

const tuple = .{ @as(u8, 1), true, @as(f32, 2.5) };
tuple[0];   // 1
tuple.len;  // 3

Index with a comptime-known constant: tuple[i] for a runtime i cannot work, because each element has its own type. Walk them with inline for.

This is what format arguments are

std.fmt.bufPrint(&buf, "{s}={d}", .{ "x", 42 });

That second argument is just a tuple. There is no varargs mechanism in Zig: print takes one value that happens to be a struct, walks it with inline for, and checks it against the format string at compile time. A mismatched {d} is a compile error, not a runtime surprise.