Defining Functions
Functions are reusable blocks of code.
Instead of writing the same logic again and again, you place the logic inside a function and call it whenever you need it.
In Zig, functions are one of the most important building blocks of a program. Every Zig program starts from a function called main.
A function can:
- receive input
- perform work
- return a result
A simple function looks like this:
fn sayHello() void {
std.debug.print("Hello!\n", .{});
}This defines a function named sayHello.
Let us break it apart carefully.
The Structure of a Function
fn sayHello() void {
std.debug.print("Hello!\n", .{});
}Each piece has meaning.
| Part | Meaning |
|---|---|
fn | declares a function |
sayHello | function name |
() | parameter list |
void | return type |
{} | function body |
The body contains the code that runs when the function is called.
Calling a Function
Defining a function does not run it.
You must call it.
const std = @import("std");
fn sayHello() void {
std.debug.print("Hello!\n", .{});
}
pub fn main() void {
sayHello();
}Output:
Hello!The line:
sayHello();calls the function.
Execution jumps into the function body, runs the code, then returns back to the caller.
Why Functions Matter
Functions help organize programs.
Without functions:
pub fn main() void {
std.debug.print("Loading file...\n", .{});
std.debug.print("Parsing file...\n", .{});
std.debug.print("Saving result...\n", .{});
}As programs grow, everything becomes mixed together.
With functions:
const std = @import("std");
fn loadFile() void {
std.debug.print("Loading file...\n", .{});
}
fn parseFile() void {
std.debug.print("Parsing file...\n", .{});
}
fn saveResult() void {
std.debug.print("Saving result...\n", .{});
}
pub fn main() void {
loadFile();
parseFile();
saveResult();
}Now the program is easier to read.
The main function describes the high-level flow of the program.
Function Names
Function names should describe what the function does.
Good names:
readFile()
parseJson()
calculateTotal()
sendRequest()Bad names:
doThing()
stuff()
x()
abc()A good function name reduces mental effort when reading code.
Zig commonly uses camelCase for functions.
Functions Without Return Values
Some functions only perform actions.
Example:
fn printBanner() void {
std.debug.print("=== APP START ===\n", .{});
}The return type is void.
void means “no value is returned.”
Functions That Return Values
Functions can also produce values.
Example:
fn add(a: i32, b: i32) i32 {
return a + b;
}This function:
- receives two integers
- adds them
- returns the result
The return type is:
i32meaning a signed 32-bit integer.
Using the function:
const std = @import("std");
fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn main() void {
const result = add(10, 20);
std.debug.print("{}\n", .{result});
}Output:
30Understanding return
The return keyword immediately exits the function.
Example:
fn square(x: i32) i32 {
return x * x;
}Once return runs, the function ends.
Any code after it does not execute.
fn test() i32 {
return 5;
// never runs
return 10;
}Zig detects unreachable code in many situations.
Parameters
Parameters are inputs to a function.
Example:
fn greet(name: []const u8) void {
std.debug.print("Hello, {s}!\n", .{name});
}Here:
name: []const u8means:
- parameter name:
name - parameter type: string slice
Calling it:
greet("Alice");
greet("Bob");Output:
Hello, Alice!
Hello, Bob!Functions become flexible because parameters allow different input values.
Multiple Parameters
Functions can accept many parameters.
fn multiply(a: i32, b: i32, c: i32) i32 {
return a * b * c;
}Calling:
const value = multiply(2, 3, 4);Result:
24Parameters are separated by commas.
Type Checking
Zig checks parameter types strictly.
Example:
fn add(a: i32, b: i32) i32 {
return a + b;
}This works:
add(1, 2);This fails:
add("hello", "world");The compiler stops with an error because strings are not integers.
This strictness helps prevent bugs.
Function Signatures
A function signature describes:
- function name
- parameter types
- return type
Example:
fn divide(a: f64, b: f64) f64Meaning:
- function name:
divide - parameters: two
f64 - returns: one
f64
When programmers discuss APIs, they often talk about function signatures.
Public Functions
Earlier we saw:
pub fn main() voidThe word pub means public.
Without pub, a function is private to the current file.
Example:
fn helper() void {}Private function.
pub fn api() void {}Public function.
Most helper functions should remain private unless other files need access.
Functions as Building Blocks
Large programs are built from many small functions.
Instead of writing one giant function:
pub fn main() void {
// 5000 lines
}good programs divide work into smaller pieces:
loadConfig();
connectDatabase();
startServer();Small functions are:
- easier to test
- easier to debug
- easier to reuse
- easier to understand
A Complete Example
const std = @import("std");
fn add(a: i32, b: i32) i32 {
return a + b;
}
fn printResult(value: i32) void {
std.debug.print("Result: {}\n", .{value});
}
pub fn main() void {
const result = add(5, 7);
printResult(result);
}Output:
Result: 12This program already demonstrates an important idea:
- one function computes
- another function prints
maincoordinates everything
Each function has a clear responsibility.
Mental Model
You can think of a function like a machine.
Input goes in:
a, bwork happens inside:
a + boutput comes out:
resultFunctions let you package logic into reusable units.
This is one of the foundations of programming, not just in Zig, but in nearly every programming language.