Function Parameters
Function parameters are the inputs of a function.
They allow functions to work with different values instead of hardcoded data.
Without parameters, functions are limited:
fn printNumber() void {
std.debug.print("{}\n", .{42});
}This function can only print 42.
With parameters:
fn printNumber(value: i32) void {
std.debug.print("{}\n", .{value});
}Now the function can print any integer.
printNumber(10);
printNumber(99);
printNumber(-5);Output:
10
99
-5Parameters make functions reusable.
Parameter Syntax
A parameter has two parts:
name: typeExample:
value: i32valueis the parameter namei32is the parameter type
A function with one parameter:
fn square(x: i32) i32 {
return x * x;
}Here:
- parameter name:
x - parameter type:
i32 - return type:
i32
Multiple Parameters
Functions can have multiple parameters.
fn add(a: i32, b: i32) i32 {
return a + b;
}Calling it:
const result = add(10, 20);The values are passed in order:
a = 10
b = 20Parameters are separated by commas.
fn move(x: i32, y: i32, speed: f32) void {
}Arguments vs Parameters
These words are related but different.
| Term | Meaning |
|---|---|
| Parameter | variable in the function definition |
| Argument | actual value passed during the call |
Example:
fn add(a: i32, b: i32) i32 {
return a + b;
}Here a and b are parameters.
add(5, 8);Here 5 and 8 are arguments.
Programmers sometimes use the words interchangeably in casual conversation, but technically they are different.
Parameters Create Local Variables
Inside the function, parameters behave like local variables.
Example:
fn double(value: i32) i32 {
const result = value * 2;
return result;
}value exists only inside the function.
Outside the function, it does not exist.
pub fn main() void {
double(10);
// ERROR
// value does not exist here
}This isolation helps keep programs organized.
Type Checking
Zig checks parameter types strictly.
Example:
fn multiply(a: i32, b: i32) i32 {
return a * b;
}Correct:
multiply(3, 4);Incorrect:
multiply("hello", "world");The compiler rejects invalid types before the program runs.
This is one reason Zig programs are reliable.
Integer Parameters
Common integer parameter types:
| Type | Meaning |
|---|---|
u8 | unsigned 8-bit integer |
u32 | unsigned 32-bit integer |
i32 | signed 32-bit integer |
usize | size/index integer |
Example:
fn setVolume(level: u8) void {
std.debug.print("Volume: {}\n", .{level});
}u8 allows values from 0 to 255.
Floating Point Parameters
Floating-point types store decimal numbers.
Common types:
| Type | Meaning |
|---|---|
f32 | 32-bit float |
f64 | 64-bit float |
Example:
fn area(radius: f64) f64 {
return 3.14159 * radius * radius;
}Calling:
const result = area(5.0);Boolean Parameters
Booleans store true or false.
Example:
fn setEnabled(enabled: bool) void {
if (enabled) {
std.debug.print("Enabled\n", .{});
} else {
std.debug.print("Disabled\n", .{});
}
}Calling:
setEnabled(true);
setEnabled(false);Output:
Enabled
DisabledString Parameters
Strings in Zig are usually slices of bytes.
Example:
fn greet(name: []const u8) void {
std.debug.print("Hello, {s}!\n", .{name});
}Calling:
greet("Alice");Output:
Hello, Alice!This type:
[]const u8means:
[]→ sliceconst→ read-onlyu8→ bytes
You will learn slices in detail later.
For now, think of this as Zig’s standard string type.
Passing Structs
Functions can receive structs.
Example:
const Point = struct {
x: i32,
y: i32,
};
fn printPoint(point: Point) void {
std.debug.print("({}, {})\n", .{
point.x,
point.y,
});
}Calling:
const p = Point{
.x = 10,
.y = 20,
};
printPoint(p);Output:
(10, 20)Functions can work with complex data, not just primitive values.
Passing Pointers
Sometimes copying data is expensive.
Instead of passing the whole value, you pass a pointer.
Example:
fn increment(value: *i32) void {
value.* += 1;
}Calling:
var number: i32 = 10;
increment(&number);After the call:
number = 11The operator:
&numbergets the address of number.
The type:
*i32means “pointer to i32.”
Pointers are extremely important in Zig.
Parameters Are Immutable by Default
Function parameters cannot normally be reassigned.
Example:
fn test(value: i32) void {
// ERROR
// value = 5;
}This prevents accidental modification.
If you need a mutable variable, create a new one:
fn test(value: i32) void {
var local = value;
local += 1;
}This rule improves clarity.
Parameter Shadowing
Avoid reusing parameter names.
Bad example:
fn test(value: i32) void {
const value = 10;
}This creates confusion.
Use distinct names instead.
Too Many Parameters
Functions with too many parameters become hard to use.
Bad:
fn createUser(
name: []const u8,
age: u8,
height: f32,
weight: f32,
country: []const u8,
city: []const u8,
active: bool,
) void {
}This is difficult to read and easy to misuse.
Often a struct is better:
const User = struct {
name: []const u8,
age: u8,
height: f32,
weight: f32,
country: []const u8,
city: []const u8,
active: bool,
};
fn createUser(user: User) void {
}This approach scales better.
A Complete Example
const std = @import("std");
fn calculateArea(width: f64, height: f64) f64 {
return width * height;
}
fn printArea(area: f64) void {
std.debug.print("Area: {}\n", .{area});
}
pub fn main() void {
const area = calculateArea(5.0, 3.0);
printArea(area);
}Output:
Area: 15Notice how each function has a focused job:
calculateAreacomputesprintAreadisplaysmaincoordinates the flow
This separation is one of the core ideas of clean software design.