Functions in Zig
Learn how to define and call functions in Zig - parameters, return values, pass-by-value vs pointers, recursion, error unions, and higher-order functions with Docker-ready examples
Functions are the primary unit of code organization in Zig. As a systems language built around the idea of “no hidden control flow,” Zig’s functions are refreshingly predictable: parameters are immutable by default, there is no operator-overloading magic hiding extra calls, and every function’s return type — including whether it can fail — is spelled out in its signature.
Zig is multi-paradigm (imperative, procedural, and functional), and its function model reflects that. You get plain procedural functions like C, error-aware functions through error unions, and functional patterns like passing functions as values and writing generic functions with compile-time type parameters. What you won’t find is implicit behavior: there are no default arguments, no method overloading, and no surprise allocations.
In this tutorial you’ll learn how to define and call functions, the difference between passing values and passing pointers, how recursion works, how functions report errors with error unions, and how to write higher-order and generic functions using Zig’s comptime type parameters.
Defining and Calling Functions
A function is declared with the fn keyword, followed by a parameter list with explicit types, the return type, and a body. Use void when a function returns nothing. The pub keyword (seen on main) makes a function visible outside its file.
Create a file named functions.zig:
| |
Every parameter must have an explicit type, and the return type is never inferred — Zig favors clarity over brevity. Note that Zig has no default parameters and no function overloading: each function name maps to exactly one function. To emulate optional configuration, Zig programmers commonly pass an anonymous struct of options instead.
Parameters Are Immutable: Value vs Pointer
Function parameters in Zig are immutable — inside a function, you cannot reassign a parameter, and the function receives a copy of the argument’s value. To let a function modify the caller’s variable, you pass a pointer (*T) and dereference it with .*.
Create a file named parameters.zig:
| |
Here &value takes the address of value, producing a *i32. Inside doubleInPlace, n.* reads and writes through that pointer. This explicit distinction between pass-by-value and pass-by-pointer is central to Zig’s “what you see is what you get” philosophy — there is no hidden pass-by-reference.
Recursion
Functions can call themselves. Recursion is fully supported and is the natural way to express problems like factorials and Fibonacci numbers.
Create a file named recursion.zig:
| |
Each recursive call gets its own stack frame. Because Zig gives you precise control over integer widths, the return types (u64, u32) are part of the contract — choosing u64 for factorial avoids overflow that a narrower type would trigger.
Functions That Can Fail: Error Unions
Many functions need to signal failure. Zig encodes this directly in the return type using an error union, written ErrorSet!ReturnType. The caller must handle the error explicitly — either with catch or by capturing both branches with if/else.
Create a file named errors.zig:
| |
The ! in DivError!i32 means “this returns an i32 or an error from DivError.” There is no way to accidentally ignore the failure — the compiler forces you to deal with it. @errorName converts an error value to its name as a string slice, which is handy for printing.
Higher-Order and Generic Functions
Zig’s functional side shines through function pointers and compile-time generics. You can pass a function as an argument using the *const fn(...) ... pointer type, and you can write a single function that works for many types by accepting a comptime T: type parameter.
Create a file named higher_order.zig:
| |
applyTwice receives increment or triple as a value and calls it. The max function uses comptime T: type so the same source generates specialized versions for i32 and f64 at compile time — this is how Zig does generics without templates or macros. Each call site resolves T during compilation, so there is no runtime cost.
Running with Docker
You can run every example without installing Zig locally by using the official Alpine Zig image.
| |
Expected Output
Running functions.zig:
3 + 4 = 7
square(5) = 25
Hello, Zig!
Running parameters.zig:
inside (copy): 42
after tryToDouble: 21
after doubleInPlace: 42
Running recursion.zig:
factorial(5) = 120
factorial(10) = 3628800
fibonacci(10) = 55
Running errors.zig:
10 / 2 = 5
Cannot divide: DivisionByZero
Running higher_order.zig:
applyTwice(increment, 5) = 7
applyTwice(triple, 2) = 18
max i32: 20
max f64: 3.5
Key Concepts
- Explicit signatures — Every parameter and the return type must be typed; Zig never infers a function’s return type, keeping call sites unambiguous.
- Immutable parameters — Functions receive copies and cannot reassign parameters. To mutate a caller’s variable, pass a pointer (
*T) and dereference with.*. - No defaults or overloading — Zig has no default arguments and no function overloading; pass an anonymous struct of options to emulate optional configuration.
- Error unions — A return type of
ErrorSet!Tforces callers to handle failure withcatchorif/else; errors can never be silently ignored. - Recursion — Functions may call themselves; choose integer widths (
u32,u64) deliberately to avoid overflow in recursive accumulations. - Function pointers — Functions are values; accept them with the
*const fn(...) ...type to build higher-order functions. - Compile-time generics — A
comptime T: typeparameter lets one function serve many types, resolved during compilation with zero runtime cost — Zig’s alternative to templates and macros.
Next Steps
Continue to I/O Operations to learn how Zig reads from standard input, writes formatted output, and works with files — building on the error-handling patterns introduced here.
Running Today
All examples can be run using Docker:
docker pull kassany/alpine-ziglang:0.14.0
Comments
Loading comments...
Leave a Comment