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:
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: