🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Generic Data Structures

A generic data structure is a container that works for many value types from one definition. One stack definition can hold integers in one program and text in another.

One stack, integers

Stack(i32) builds a stack type for integers, and pushes followed by pops show its order.

fn Stack(comptime T: type) type {
    return struct { items: [8]T, len: usize };
}
fn push(comptime T: type, stack: *Stack(T), value: T) void {
    stack.items[stack.len] = value;
    stack.len += 1;
}
fn pop(comptime T: type, stack: *Stack(T)) T {
    stack.len -= 1;
    return stack.items[stack.len];
}
const std = @import("std");
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    var s: Stack(i32) = .{ .items = undefined, .len = 0 };
    push(i32, &s, 10);
    push(i32, &s, 20);
    push(i32, &s, 30);
    const top = pop(i32, &s);
    try out.print("len={d}\n", .{s.len});
    try out.print("top={d}\n", .{top});
    try out.print("next={d}\n", .{pop(i32, &s)});
}

Same stack, text

Stack([]const u8) builds the same stack shape for text slices, with no change to the container definition.

fn Stack(comptime T: type) type {
    return struct { items: [8]T, len: usize };
}
fn push(comptime T: type, stack: *Stack(T), value: T) void {
    stack.items[stack.len] = value;
    stack.len += 1;
}
fn pop(comptime T: type, stack: *Stack(T)) T {
    stack.len -= 1;
    return stack.items[stack.len];
}
const std = @import("std");
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    var s: Stack([]const u8) = .{ .items = undefined, .len = 0 };
    push([]const u8, &s, "red");
    push([]const u8, &s, "blue");
    const first = pop([]const u8, &s);
    const second = pop([]const u8, &s);
    try out.print("first={s}\n", .{first});
    try out.print("second={s}\n", .{second});
    try out.print("len={d}\n", .{s.len});
}

A generic box

A generic box holds one value of any chosen type, so the same definition serves a number and a flag.

fn Box(comptime T: type) type {
    return struct { value: T };
}
const std = @import("std");
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const n = Box(i32){ .value = 42 };
    const f = Box(bool){ .value = true };
    try out.print("number={d}\n", .{n.value});
    try out.print("flag={}\n", .{f.value});
}

Example

fn Stack(comptime T: type) type {
    return struct { items: [8]T, len: usize };
}
fn push(comptime T: type, stack: *Stack(T), value: T) void {
    stack.items[stack.len] = value;
    stack.len += 1;
}
fn pop(comptime T: type, stack: *Stack(T)) T {
    stack.len -= 1;
    return stack.items[stack.len];
}
const std = @import("std");
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    var s: Stack(i32) = .{ .items = undefined, .len = 0 };
    push(i32, &s, 4);
    push(i32, &s, 8);
    push(i32, &s, 12);
    try out.print("len={d}\n", .{s.len});
    const first = pop(i32, &s);
    try out.print("popped={d}\n", .{first});
    const second = pop(i32, &s);
    try out.print("popped={d}\n", .{second});
    try out.print("left={d}\n", .{s.len});
    try out.print("bottom={d}\n", .{s.items[0]});
}

Key points

  • Generic container: a generic container is a struct definition that takes a type as a parameter.
  • Comptime type parameter: a comptime type parameter is written as comptime T: type and is fixed when the container is used.
  • Type function: a type function is a function that returns a type instead of a value.
  • Stack: a stack is a container that returns values in the reverse order they were added.
  • Capacity: capacity is the largest number of values a fixed container can hold.
  • Length: length is the number of values currently stored in a container.

The example builds a stack for integers from one generic definition. It adds three values and removes the last two first. One value stays behind, so the length is one and the bottom value is still four.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

What does Stack(i32) produce?