Hello, Zig!
1 Hello, Zig
Every programming book starts here. Let’s not pretend we’re above it.
1.1 The code
Here’s the entire program:
code/ch01/hello.zig
const std = @import("std");
pub fn main() void {
std.debug.print("Hello, Zig!\n", .{});
}And here’s what happens when you run it:
zig run code/ch01/hello.zigThat’s it. One file, no build system, no project scaffolding. zig run compiles and executes in one step.
↯ A tale of two hello worlds
Let’s compare what it takes to go from “I have nothing” to “Hello, World!” in Zig versus modern Python — the kind with uv, pyproject.toml, and virtual environments.
Zig:
# Step 1: Install Zig
# Download a single archive. Extract it. Add it to PATH.
# On macOS: brew install zig
# On Windows: winget install zig.zig
# Step 2: Write the program
echo 'const std = @import("std");
pub fn main() void {
std.debug.print("Hello, World!\n", .{});
}' > hello.zig
# Step 3: Run it
zig run hello.zig
# => Hello, World!Three steps. The compiler is the build system is the test runner. One binary, about 200 MB uncompressed (most of which is a bundled C compiler and libc headers for cross-compilation).
Python (the modern way):
# Step 1: Install Python
# Which Python? System? Homebrew? pyenv? Microsoft Store? deadsnakes PPA?
python3 --version # 3.13, let's say
# Step 2: Install uv (a fast package/project manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Step 3: Create a project
uv init hello-python
cd hello-python
# Creates: pyproject.toml, .python-version, hello.py, README.md
# Step 4: Create a virtual environment
uv venv
# On Unix: source .venv/bin/activate
# On Windows: .venv\Scripts\activate
# Step 5: Write hello world
echo 'print("Hello, World!")' > hello.py
# Step 6: Run it
uv run hello.py
# => Hello, World!
# Step 7: Need a dependency
uv add requests
# Modifies pyproject.toml, updates uv.lock, installs into the venv
# Step 8: Need Jupyter (if you're writing a book, say)
uv pip install jupyter
# On Windows, the bash kernel doesn't work (pexpect needs Unix
# pseudo-terminals), so you'd use a Python subprocess wrapper instead
# Step 9: Jupyter kernel installed to the wrong place
python -m bash_kernel.install --prefix .venv
# Kernels installed to your user directory aren't visible to the venvThat’s nine steps, and this is the streamlined version. The ecosystem has options at every stage — pip, pipx, poetry, pdm, hatch, conda — and reasonable people disagree about which combination is correct.
None of this is a knock on Python. uv is an excellent tool. pyproject.toml is a real improvement over setup.py and requirements.txt. The ecosystem keeps getting better. But when the best-practice setup involves multiple tools and several configuration files before you write a line of code, it’s worth noticing what Zig chose instead: ship one binary and make it do everything.
Different languages, different trade-offs. But for someone coming from that world, Zig’s toolchain simplicity is striking.
(For a real-world example of these trade-offs, see the “Python situation” tangent in the Colophon — it documents what it took to get this book’s code execution pipeline working.)
But even this tiny program is doing things that might look strange if you’re coming from Python or JavaScript. Let’s talk about them.
1.2 What’s going on
const std = @import("std"); — This imports the standard library. @import is a builtin function (you can tell because of the @ prefix). Zig has no magic globals and no implicit prelude. If you want the standard library, you ask for it explicitly.
pub fn main() void — pub makes it visible outside this file. fn declares a function. main is the entry point. void means it returns nothing. Every part of this signature is explicit — Zig doesn’t infer function return types.
std.debug.print("Hello, Zig!\n", .{}); — This is the part that trips people up. Why debug.print? Where’s println? And what on earth is .{}?
debug.print writes to stderr, not stdout. That’s deliberate — it’s a debugging tool, not an output mechanism. Zig wants you to be intentional about I/O, and we’ll see the “real” way to write to stdout later.
The .{} is an anonymous struct literal — here it’s an empty tuple of format arguments. Think of it like Python’s "hello {}".format() but with compile-time checking. If your format string has placeholders and your argument tuple doesn’t match, Zig catches it at compile time. Not at runtime. Not in a test. At compile time.
1.3 Try it yourself
If you’ve got Zig installed, you can run this right now:
zig run code/ch01/hello.zigIf you don’t have Zig installed yet, flip back to Chapter 0 for setup instructions. It’s a single binary download — this is one of Zig’s selling points.