Demystifying Structs in Zig: Composition, Defaults, and Packed Layouts

Zig’s struct type is more than just a container for fields. It supports powerful patterns like compile-time defaults, field introspection, and precise memory layouts. In this tutorial, we’ll explore how to define and use structs effectively in Zig. Step 1: Defining a Basic Struct At its core, a struct groups related data together: const User = struct { id: u32, name: []const u8, }; const alice = User{ .id = 1, .name = "Alice" }; Fields are accessed using dot notation: std.debug.print("User ID: {}, Name: {s}\n", .{alice.id, alice.name}); Step 2: Adding Default Values You can give fields default values using =, which allows partial struct initialization: const User = struct { id: u32 = 0, name: []const u8 = "Guest", }; const bob = User{}; // uses defaults This makes your structs more flexible without boilerplate constructors. Step 3: Using Structs for Composition Zig encourages composition over inheritance. You can nest structs cleanly: const Address = struct { city: []const u8, country: []const u8, }; const Profile = struct { user: User, location: Address, }; Then use it like: const profile = Profile{ .user = .{ .id = 2, .name = "Bob" }, .location = .{ .city = "Berlin", .country = "Germany" }, }; Step 4: Declaring Packed Structs For low-level or binary work, packed ensures tightly controlled memory layouts: const Flags = packed struct { is_admin: bool, is_active: bool, reserved: u6, // 6-bit padding }; This is essential for interfacing with C or hardware. Step 5: Anonymous Structs and Field Access You can also define anonymous structs or use inline fields: const point = struct { x: i32, y: i32, }{ .x = 10, .y = 20 }; You can introspect fields at compile time using @typeInfo. ✅ Pros and ❌ Cons of Zig Structs ✅ Pros:

May 9, 2025 - 07:46
 0
Demystifying Structs in Zig: Composition, Defaults, and Packed Layouts

Zig’s struct type is more than just a container for fields. It supports powerful patterns like compile-time defaults, field introspection, and precise memory layouts. In this tutorial, we’ll explore how to define and use structs effectively in Zig.

Step 1: Defining a Basic Struct

At its core, a struct groups related data together:

const User = struct {
    id: u32,
    name: []const u8,
};

const alice = User{ .id = 1, .name = "Alice" };

Fields are accessed using dot notation:

std.debug.print("User ID: {}, Name: {s}\n", .{alice.id, alice.name});

Step 2: Adding Default Values

You can give fields default values using =, which allows partial struct initialization:

const User = struct {
    id: u32 = 0,
    name: []const u8 = "Guest",
};

const bob = User{}; // uses defaults

This makes your structs more flexible without boilerplate constructors.

Step 3: Using Structs for Composition

Zig encourages composition over inheritance. You can nest structs cleanly:

const Address = struct {
    city: []const u8,
    country: []const u8,
};

const Profile = struct {
    user: User,
    location: Address,
};

Then use it like:

const profile = Profile{
    .user = .{ .id = 2, .name = "Bob" },
    .location = .{ .city = "Berlin", .country = "Germany" },
};

Step 4: Declaring Packed Structs

For low-level or binary work, packed ensures tightly controlled memory layouts:

const Flags = packed struct {
    is_admin: bool,
    is_active: bool,
    reserved: u6, // 6-bit padding
};

This is essential for interfacing with C or hardware.

Step 5: Anonymous Structs and Field Access

You can also define anonymous structs or use inline fields:

const point = struct {
    x: i32,
    y: i32,
}{ .x = 10, .y = 20 };

You can introspect fields at compile time using @typeInfo.

✅ Pros and ❌ Cons of Zig Structs

✅ Pros: