Anonymous Functions
An anonymous function is a function without a permanent name.
Many programming languages support anonymous functions heavily. JavaScript, Python, Rust, and Go all provide different forms of them.
Zig takes a more conservative approach.
Zig does not currently have full closure-based anonymous functions like JavaScript or Python. Instead, Zig prefers:
- named functions
- compile-time function generation
- structs with methods
- explicit state passing
This design keeps behavior predictable and avoids hidden allocations and hidden captures.
Even though Zig’s approach is different, understanding anonymous-function concepts is still important because you will encounter them in other languages and in Zig design discussions.
What Anonymous Functions Usually Mean
In many languages:
function value without a declared nameJavaScript example:
const add = function(a, b) {
return a + b;
};Python example:
square = lambda x: x * xThese functions are created inline.
Zig’s Philosophy
Zig intentionally avoids automatic hidden behavior.
Closures in many languages often capture variables automatically:
function makeCounter() {
let count = 0;
return function() {
count += 1;
return count;
};
}The inner function silently captures:
countThis hidden state may require heap allocation or compiler-generated structures.
Zig prefers explicit data structures instead.
Named Functions as Values
Even without traditional anonymous functions, Zig functions are still values.
Example:
const std = @import("std");
fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn main() void {
const op = add;
const result = op(2, 3);
std.debug.print("{}\n", .{result});
}Output:
5This gives much of the flexibility people want from anonymous functions.
Inline Struct-Based Behavior
Instead of closures, Zig often uses structs.
Example:
const Adder = struct {
value: i32,
fn apply(self: Adder, x: i32) i32 {
return x + self.value;
}
};Using it:
const add10 = Adder{
.value = 10,
};
const result = add10.apply(5);Result:
15This behaves similarly to a closure:
captured value = 10But everything is explicit.
Explicit State Is Important
Compare these two ideas.
Hidden capture:
function silently stores outside variablesExplicit state:
struct explicitly contains stateZig strongly prefers the second approach.
This makes ownership and memory behavior easier to understand.
Compile-Time Function Generation
Zig often replaces anonymous-function patterns with comptime.
Example:
fn makeAdder(comptime amount: i32)
fn(i32) i32
{
}Conceptually:
generate specialized behavior at compile timeZig relies heavily on compile-time programming instead of runtime closures.
You will study this deeply later.
Callback Style APIs
Many languages use anonymous functions for callbacks.
JavaScript example:
items.forEach(function(item) {
console.log(item);
});In Zig, this is usually written with named functions.
Example conceptually:
fn printItem(item: i32) void {
std.debug.print("{}\n", .{item});
}Then passed as a normal function pointer.
Why Zig Avoids Heavy Closure Systems
Closure systems can introduce:
- hidden allocations
- hidden memory ownership
- hidden captures
- unclear lifetimes
- implicit heap usage
Zig prefers:
- explicit memory behavior
- visible ownership
- predictable performance
- simple compiler rules
This aligns with Zig’s systems-programming goals.
Function Factories with Structs
Suppose we want customizable behavior.
In JavaScript:
function makeMultiplier(n) {
return function(x) {
return x * n;
};
}In Zig:
const Multiplier = struct {
factor: i32,
fn apply(self: Multiplier, x: i32) i32 {
return x * self.factor;
}
};Using it:
const times3 = Multiplier{
.factor = 3,
};
const result = times3.apply(4);Output:
12Again, the captured state is explicit.
Temporary Inline Structs
Sometimes Zig uses anonymous structs inline.
Example:
const value = .{
.x = 10,
.y = 20,
};This is anonymous data, not an anonymous function, but it follows the same philosophy:
simple explicit structures instead of hidden magicMethods as Behavioral Units
Methods often replace closure usage.
Example:
const Counter = struct {
value: i32,
fn increment(self: *Counter) void {
self.value += 1;
}
};The struct stores state.
The method stores behavior.
This combination replaces many closure patterns naturally.
Generic Behavior Without Closures
Zig frequently uses generic functions.
Example:
fn process(
comptime T: type,
value: T,
) void {
}Compile-time polymorphism often removes the need for runtime anonymous functions.
Simulating Closures Manually
You can manually build closure-like systems.
Example:
const Callback = struct {
context: *anyopaque,
call: *const fn(*anyopaque) void,
};This pattern appears in low-level libraries.
The callback stores:
- function pointer
- explicit context pointer
C libraries commonly use this style too.
Example Callback Context Pattern
const Context = struct {
count: i32,
};
fn callback(ptr: *anyopaque) void {
const ctx: *Context =
@ptrCast(@alignCast(ptr));
ctx.count += 1;
}This style is verbose but extremely explicit.
Nothing is hidden.
Anonymous Functions vs Readability
Anonymous functions can sometimes reduce readability.
Example:
items.map(x => x * 2)
.filter(x => x > 10)
.reduce((a, b) => a + b)Compact code is not always clearer code.
Zig generally favors explicit named steps instead.
A Complete Zig Example
const std = @import("std");
const Printer = struct {
prefix: []const u8,
fn print(
self: Printer,
text: []const u8,
) void {
std.debug.print(
"{s}: {s}\n",
.{
self.prefix,
text,
},
);
}
};
pub fn main() void {
const info = Printer{
.prefix = "INFO",
};
const error_log = Printer{
.prefix = "ERROR",
};
info.print("starting");
error_log.print("failed");
}Output:
INFO: starting
ERROR: failedThis behaves similarly to closure-based customization, but the state is stored explicitly inside structs.
Mental Model
Many languages use anonymous functions like this:
behavior + hidden captured stateZig prefers:
behavior + explicit structuresInstead of relying heavily on runtime closure systems, Zig encourages:
- named functions
- structs
- methods
- compile-time specialization
- explicit context passing
This keeps programs easier to reason about, especially in low-level systems programming where memory behavior matters.