defer
defer runs a statement when control leaves the current block.
const std = @import("std");
pub fn main() void {
defer std.debug.print("second\n", .{});
std.debug.print("first\n", .{});
}The output is:
first
secondThe deferred statement is written first, but it runs later.
The block matters. A deferred statement runs at the end of the block where it was declared.
const std = @import("std");
pub fn main() void {
{
defer std.debug.print("leaving inner block\n", .{});
std.debug.print("inside inner block\n", .{});
}
std.debug.print("after inner block\n", .{});
}The output is:
inside inner block
leaving inner block
after inner blockThis is useful for cleanup. Acquire a resource, then immediately write how it will be released.
const std = @import("std");
pub fn main() !void {
const file = try std.fs.cwd().openFile("input.txt", .{});
defer file.close();
// use file here
}The call to file.close() will run when main returns, whether the function reaches the end or returns early with an error.
This pattern keeps cleanup near acquisition.
const resource = try acquire();
defer resource.release();
use(resource);The release code is not hidden at the bottom of the function. It appears next to the code that made it necessary.
Deferred statements run in reverse order.
const std = @import("std");
pub fn main() void {
defer std.debug.print("third\n", .{});
defer std.debug.print("second\n", .{});
defer std.debug.print("first\n", .{});
}The output is:
first
second
thirdThis order is deliberate. It matches the usual order of cleanup.
const a = try acquireA();
defer releaseA(a);
const b = try acquireB();
defer releaseB(b);b is released before a, because b was acquired after a.
A defer statement can modify variables from the surrounding block.
const std = @import("std");
pub fn main() void {
var n: i32 = 0;
{
defer n += 1;
std.debug.print("{d}\n", .{n});
}
std.debug.print("{d}\n", .{n});
}The output is:
0
1The deferred statement runs after the inner print and before the final print.
defer also runs when a block exits through break, continue, or return.
const std = @import("std");
pub fn main() void {
var i: usize = 0;
while (i < 3) : (i += 1) {
defer std.debug.print("leave iteration\n", .{});
if (i == 1) continue;
std.debug.print("{d}\n", .{i});
}
}A defer inside a loop body runs at the end of each iteration, because each iteration leaves the loop body block.
defer is not a replacement for ownership. It does not decide who owns memory or files. It only says when a statement runs. The programmer still chooses what must be cleaned up.
A common mistake is to defer inside a loop when the intended cleanup should happen after the whole loop.
while (condition) {
const item = try acquire();
defer item.release();
use(item);
}Here item.release() runs at the end of each iteration. That may be correct. If the resource must live beyond the iteration, the defer belongs in an outer block.
Use defer for actions that must happen exactly once when leaving a scope: closing files, unlocking mutexes, freeing memory, restoring state, and printing diagnostic exits.
Exercise 3-25. Write a program with two deferred prints and observe the order.
Exercise 3-26. Put a defer inside an inner block and observe when it runs.
Exercise 3-27. Put a defer inside a loop and observe that it runs once per iteration.
Exercise 3-28. Open a file and use defer to close it.