Skip to content

Tour of `std`

std is Zig’s standard library.

Tour of std

std is Zig’s standard library.

A standard library is the set of useful code that comes with the language. You do not need to download it from a package manager. When you install Zig, you already have std.

Zig’s official documentation describes the standard library as a collection of commonly used algorithms, data structures, and definitions for building programs and libraries. You can also view the local standard library docs by running zig std.

The most common way to use it is:

const std = @import("std");

This line means: load the standard library and call it std in this file.

After that, you can use things inside it:

const std = @import("std");

pub fn main() void {
    std.debug.print("Hello from std!\n", .{});
}

Here, std.debug.print is a printing function from the standard library.

std Is Organized into Modules

The standard library is not one giant list of functions. It is organized into modules.

A module is a namespace. It groups related things together.

For example:

std.debug

contains debugging utilities.

std.mem

contains memory helpers.

std.heap

contains allocators.

std.fs

contains file system APIs.

std.ArrayList

is a dynamic array type.

This style keeps names clear. Instead of having a function named copy floating around globally, Zig gives you names like:

std.mem.copyForwards

That tells you the function belongs to the memory module.

Printing and Debugging

You have already seen:

std.debug.print("Hello\n", .{});

This is useful while learning. It prints text to standard error.

You can also format values:

const std = @import("std");

pub fn main() void {
    const age = 30;
    std.debug.print("age = {}\n", .{age});
}

The {} is a placeholder. The value comes from .{age}.

For strings, use {s}:

const std = @import("std");

pub fn main() void {
    const name = "Zig";
    std.debug.print("Hello, {s}!\n", .{name});
}

The output is:

Hello, Zig!

This formatting style appears everywhere in Zig. You pass the format string first, then the values as a tuple.

Memory Helpers with std.mem

std.mem contains functions for working with bytes, arrays, and slices.

A slice is a view into a sequence of values. For example, []const u8 is often used for text bytes.

Here is a simple comparison:

const std = @import("std");

pub fn main() void {
    const a = "hello";
    const b = "hello";

    if (std.mem.eql(u8, a, b)) {
        std.debug.print("same\n", .{});
    }
}

std.mem.eql compares two slices.

The first argument, u8, tells Zig the element type. The next two arguments are the values to compare.

This is common in Zig. Many standard library functions ask for the element type explicitly.

Allocators with std.heap

Zig does not hide heap allocation. When code needs heap memory, it usually receives an allocator.

The standard library gives you allocator implementations in std.heap.

A common beginner allocator is the general purpose allocator:

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    const buffer = try allocator.alloc(u8, 100);
    defer allocator.free(buffer);

    std.debug.print("allocated {} bytes\n", .{buffer.len});
}

Read this slowly.

var gpa = std.heap.GeneralPurposeAllocator(.{}){};

This creates an allocator object.

defer _ = gpa.deinit();

This cleans it up at the end of the function.

const allocator = gpa.allocator();

This gets the allocator interface.

const buffer = try allocator.alloc(u8, 100);

This allocates 100 bytes.

defer allocator.free(buffer);

This frees the memory before the function returns.

This is one of Zig’s core habits: allocate explicitly, free explicitly, and use defer to keep cleanup close to setup.

Dynamic Arrays with std.ArrayList

A fixed array has a size known at compile time:

const numbers = [_]u8{ 1, 2, 3 };

But sometimes you need an array that can grow while the program runs. For that, the standard library provides std.ArrayList.

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    var list = std.ArrayList(u32).init(allocator);
    defer list.deinit();

    try list.append(10);
    try list.append(20);
    try list.append(30);

    for (list.items) |value| {
        std.debug.print("{}\n", .{value});
    }
}

This creates a dynamic list of u32 values.

var list = std.ArrayList(u32).init(allocator);

The list needs an allocator because it may grow.

try list.append(10);

Appending may allocate memory, so it may fail. That is why we use try.

defer list.deinit();

The list owns memory. It must be cleaned up.

This is the standard pattern for many Zig data structures.

File System APIs with std.fs

std.fs contains APIs for files, directories, and paths.

A simple file read often looks like this:

const std = @import("std");

pub fn main() !void {
    const file = try std.fs.cwd().openFile("hello.txt", .{});
    defer file.close();

    var buffer: [1024]u8 = undefined;
    const n = try file.readAll(&buffer);

    std.debug.print("{s}\n", .{buffer[0..n]});
}

This opens hello.txt from the current working directory.

std.fs.cwd()

means “current working directory.”

openFile("hello.txt", .{})

opens the file.

defer file.close();

closes the file when the function exits.

file.readAll(&buffer)

reads bytes into a fixed buffer.

This example avoids heap allocation. The buffer lives on the stack.

Formatting with std.fmt

Formatting means converting values into text.

You already saw formatting through std.debug.print, but the formatting logic itself belongs to std.fmt.

For example:

const std = @import("std");

pub fn main() !void {
    var buffer: [100]u8 = undefined;

    const text = try std.fmt.bufPrint(&buffer, "x = {}", .{42});

    std.debug.print("{s}\n", .{text});
}

std.fmt.bufPrint writes formatted text into a buffer.

It returns a slice containing the written text.

This is useful when you want text as data, not just printed output.

Parsing Numbers

The standard library can parse text into numbers.

const std = @import("std");

pub fn main() !void {
    const text = "1234";

    const value = try std.fmt.parseInt(u32, text, 10);

    std.debug.print("{}\n", .{value});
}

The arguments are:

std.fmt.parseInt(u32, text, 10)

u32 is the output type.

text is the input string.

10 is the base, meaning decimal.

If the text is invalid, parsing returns an error.

Hash Maps

A hash map stores key-value pairs.

Zig provides hash maps in the standard library.

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    var map = std.StringHashMap(u32).init(allocator);
    defer map.deinit();

    try map.put("alice", 10);
    try map.put("bob", 20);

    if (map.get("alice")) |score| {
        std.debug.print("alice = {}\n", .{score});
    }
}

std.StringHashMap(u32) means: keys are strings, values are u32.

Like ArrayList, the map needs an allocator because it stores data dynamically.

Random Numbers

Random number generation is in std.Random.

A small example:

const std = @import("std");

pub fn main() void {
    var prng = std.Random.DefaultPrng.init(12345);
    const random = prng.random();

    const value = random.intRangeAtMost(u32, 1, 100);

    std.debug.print("{}\n", .{value});
}

This creates a pseudo-random number generator with a seed.

The same seed gives the same sequence. That is useful for tests.

Time

Time utilities live under std.time.

const std = @import("std");

pub fn main() void {
    const start = std.time.nanoTimestamp();

    var sum: u64 = 0;
    for (0..1_000_000) |i| {
        sum += i;
    }

    const end = std.time.nanoTimestamp();

    std.debug.print("sum = {}\n", .{sum});
    std.debug.print("elapsed ns = {}\n", .{end - start});
}

This measures elapsed time in nanoseconds.

This kind of code is useful for simple timing, but real benchmarking needs more care.

JSON

Zig’s standard library includes JSON support.

At a high level, JSON work usually means one of two things:

You parse JSON text into values.

You write values as JSON text.

The exact APIs may change across Zig versions, so for serious JSON code, check the standard library docs for your installed compiler version. Zig’s standard library is still evolving before 1.0, and 0.16.0 includes notable standard library changes around I/O.

Networking and I/O

The standard library also includes networking and I/O APIs.

This area is important, but it is also one of the more version-sensitive parts of Zig. Zig 0.16.0 has major work around the newer std.Io system, so code in older tutorials may not match current APIs.

For beginners, the right approach is:

Learn std.debug.print.

Learn file reading and writing.

Learn buffers and slices.

Learn allocators.

Then come back to advanced I/O and networking after the core language feels natural.

How to Read std Code

Zig’s standard library is written in Zig. That means you can read it.

This is one of the best ways to learn Zig.

When you see:

std.ArrayList(u8)

you can search the standard library source for ArrayList.

When you see:

std.mem.eql

you can inspect how equality is implemented.

At first, the code may look dense. That is normal. Read it in small pieces. Focus on function signatures first.

A function signature tells you:

what arguments the function needs

what type it returns

whether it can fail

whether it needs an allocator

whether something happens at compile time

For example:

pub fn eql(comptime T: type, a: []const T, b: []const T) bool

Even before reading the body, you can learn a lot.

It takes a type T.

It takes two slices of T.

It returns bool.

It does not allocate.

It does not return an error.

That is already useful information.

The Main Beginner Modules

For now, remember these:

ModuleWhat it is for
std.debugPrinting and debugging
std.memMemory and slice helpers
std.heapAllocators
std.fsFiles and directories
std.fmtFormatting and parsing text
std.ArrayListDynamic arrays
std.StringHashMapString-keyed maps
std.timeTime and timestamps
std.RandomRandom number generation
std.testingUnit tests

You do not need to memorize the whole standard library.

Instead, learn the pattern:

const std = @import("std");

Then learn one module at a time.

The Most Important Idea

std is not magic.

It is ordinary Zig code bundled with the compiler. It gives you building blocks, but it follows the same rules as your own code.

That means the standard library also teaches the style of Zig:

explicit allocation

visible errors

small functions

clear types

compile-time parameters where useful

direct access to system features

As you learn Zig, you will keep returning to std. At first, you use it for printing. Then you use it for files, memory, collections, parsing, testing, networking, and building real programs.