Return Values
Functions often need to produce results.
A function may calculate something, create data, search for information, or transform input into output. The result comes back to the caller through a return value.
A simple example:
fn add(a: i32, b: i32) i32 {
return a + b;
}This function:
- receives two integers
- adds them
- returns the result
The return type appears after the parameter list:
i32meaning the function returns a signed 32-bit integer.
The return Keyword
The return keyword immediately exits the function and sends a value back to the caller.
Example:
fn square(x: i32) i32 {
return x * x;
}Calling it:
const result = square(5);After execution:
result = 25The function runs:
x * xthen returns the computed value.
Understanding Function Flow
When a function is called, execution temporarily jumps into the function.
Example:
fn multiply(a: i32, b: i32) i32 {
return a * b;
}Call:
const value = multiply(3, 4);Flow:
main
-> multiply(3, 4)
-> return 12
<- back to mainThe returned value becomes the result of the function call.
Returning Different Types
Functions can return many kinds of values.
Returning Integers
fn getScore() i32 {
return 100;
}Returning Floating Point Values
fn pi() f64 {
return 3.1415926535;
}Returning Booleans
fn isEven(value: i32) bool {
return value % 2 == 0;
}Calling:
const result = isEven(10);Result:
trueReturning Strings
Strings are usually slices.
fn getName() []const u8 {
return "Zig";
}Calling:
const name = getName();Returning Structs
Functions can return complex data structures.
Example:
const Point = struct {
x: i32,
y: i32,
};
fn createPoint() Point {
return Point{
.x = 10,
.y = 20,
};
}Calling:
const p = createPoint();Now:
p.x = 10
p.y = 20This is common in Zig programs.
Returning Arrays
Functions can return arrays.
fn makeArray() [3]i32 {
return [3]i32{ 1, 2, 3 };
}Calling:
const numbers = makeArray();Result:
[1, 2, 3]Returning Early
return exits immediately.
Example:
fn check(value: i32) i32 {
if (value < 0) {
return 0;
}
return value;
}Calling:
check(-5);Flow:
value < 0
return 0The second return never executes.
This pattern is extremely common.
Multiple Return Paths
Functions may return from different branches.
Example:
fn max(a: i32, b: i32) i32 {
if (a > b) {
return a;
}
return b;
}Calling:
const result = max(10, 30);Result:
30Each path must return the correct type.
Void Return Type
Some functions do not return values.
These use void.
Example:
fn printMessage() void {
std.debug.print("Hello\n", .{});
}The function performs an action but returns nothing.
Calling:
printMessage();You cannot store the result because there is no result.
Incorrect:
const x = printMessage();Implicit vs Explicit Returns
Zig requires explicit return.
This is invalid:
fn add(a: i32, b: i32) i32 {
a + b;
}Correct:
fn add(a: i32, b: i32) i32 {
return a + b;
}This design makes control flow easier to read.
Nothing is hidden.
Returning Error Unions
Many Zig functions may fail.
These functions return error unions.
Example:
fn divide(a: f64, b: f64) !f64 {
if (b == 0) {
return error.DivisionByZero;
}
return a / b;
}The return type:
!f64means:
- either an
f64 - or an error
Calling:
const result = try divide(10, 2);This topic becomes very important later in Zig.
Returning Optionals
Functions may also return optional values.
Example:
fn find(value: i32) ?i32 {
if (value == 10) {
return 10;
}
return null;
}The type:
?i32means:
- either an
i32 - or
null
Optionals are Zig’s way of representing “maybe there is a value.”
Named Result Variables
Zig also allows named return variables.
Example:
fn add(a: i32, b: i32) i32 {
const result = a + b;
return result;
}This can improve readability when computations are more complex.
Returning Pointers
Functions may return pointers.
Example:
fn getPointer(value: *i32) *i32 {
return value;
}But pointer lifetimes matter.
Dangerous example:
fn badPointer() *i32 {
var x: i32 = 10;
return &x;
}This is invalid because x disappears after the function ends.
Returning pointers safely is an important topic in systems programming.
Return Type Checking
Zig verifies returned values carefully.
Incorrect example:
fn test() i32 {
return "hello";
}Compiler error:
expected i32, found stringThe compiler prevents invalid return types before execution.
A Complete Example
const std = @import("std");
fn calculateArea(width: f64, height: f64) f64 {
return width * height;
}
fn describe(area: f64) []const u8 {
if (area > 100.0) {
return "large";
}
return "small";
}
pub fn main() void {
const area = calculateArea(12.0, 9.0);
const text = describe(area);
std.debug.print(
"Area: {}, Type: {s}\n",
.{ area, text },
);
}Output:
Area: 108, Type: largeThis program demonstrates several important ideas:
- returning numbers
- returning strings
- returning from branches
- passing returned values into other functions
Functions become much more powerful once they can produce results.
Mental Model
You can think of a function as a transformation.
Input enters:
parametersprocessing happens:
computationoutput leaves:
return valueThis pattern appears everywhere in programming.
A large software system is often just thousands of small transformations connected together.