Parameters and arguments
When we talk about parameters or arguments of a function, both terms are often used as if they were synonyms. Although they are similar, there is a subtle difference:
- Parameters are defined in the declaration of a function. They are part of its definition.
|
// fmt and args are parameters of print pub fn print(comptime fmt: []const u8, args: anytype) void { |
- Arguments are the concrete values that are passed when calling a function.
|
// we pass "Hello {s}.\n" and .{"Programmer"} as arguments print("Hello {s}.\n", .{"Programmer"}); |
It is very common to use both terms interchangeably, but knowing this difference can help you be more precise when writing and explaining code.
In Zig, by default, function parameters are immutable: they cannot be modified directly. When we pass a value to a function, it is passed by copy. This means that inside the function body, we work with a copy of the original data. Parameters are “detached” from the variables or constants used at the call site.
When passing large structures, the compiler may optimize the call by using an internal reference to that data (a pointer) instead of copying the entire content. But as programmers, we cannot know whether it chose one or the other without inspecting the generated machine code.
The most important thing to remember is that the parameter we receive inside the function body is always a copy. Even if you pass a pointer, you receive a copy of that pointer. If we take the memory address of that copy, as we do below, we must be careful: it is a local address on the function stack and becomes invalid once the function returns.
inc_return_ref.zig |
|
const std = @import("std"); const print = std.debug.print; // increments a value and returns the memory address // pointed to by parameter n_x fn inc(n_x: *u8) *const *u8 { // we print the address contained in n_x print("Address arriving contained in n_x = {}\n", .{n_x}); // we print the address of n_x print("Address of n_x: {}\n", .{&n_x}); print("What n_x points to: {}\n", .{&n_x.*}); // we modify the data pointed to by the address // contained in n_x n_x.* += 1; return &n_x; } pub fn main() void { // variable var n_a: u8 = 41; print("Value n_a = {}\n", .{n_a}); // print the address of the variable print("Address of n_x in main {}\n", .{&n_a}); // increment n_a const p_x = inc(&n_a); print("Get the pointer: {}\n", .{p_x}); print("But it already shows undefined behavior, it points to {}\n", .{p_x.*}); // we print the incremented n_a print("Value n_a = {}\n", .{n_a}); } |
|
$ zig run inc_return_ref.zig |
|
Value n_a = 41 Address of n_x in main u8@7ffdf073a808 Address arriving contained in n_x = u8@7ffdf073a808 Address of n_x: *u8@7ffdf073a788 What n_x points to: u8@7ffdf073a808 Get the pointer: *u8@7ffdf073a788 But it already shows undefined behavior, it points to u8@6570756365520000 Value n_a = 42 |