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.debugcontains debugging utilities.
std.memcontains memory helpers.
std.heapcontains allocators.
std.fscontains file system APIs.
std.ArrayListis 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.copyForwardsThat 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.eqlyou 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) boolEven 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:
| Module | What it is for |
|---|---|
std.debug | Printing and debugging |
std.mem | Memory and slice helpers |
std.heap | Allocators |
std.fs | Files and directories |
std.fmt | Formatting and parsing text |
std.ArrayList | Dynamic arrays |
std.StringHashMap | String-keyed maps |
std.time | Time and timestamps |
std.Random | Random number generation |
std.testing | Unit 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.