A program that opens a file must close it. A program that allocates memory must free it. A program that locks a mutex must unlock it.
defer and errdefer
A program that opens a file must close it. A program that allocates memory must free it. A program that locks a mutex must unlock it.
Zig uses defer for this kind of cleanup.
const file = try std.fs.cwd().openFile("data.txt", .{});
defer file.close();The defer statement runs when the current scope exits.
This matters because a scope may exit in more than one way. It may reach the end. It may return a value. It may return an error.
const std = @import("std");
fn showFile() !void {
const file = try std.fs.cwd().openFile("data.txt", .{});
defer file.close();
var buf: [128]u8 = undefined;
const n = try file.read(&buf);
std.debug.print("{s}\n", .{buf[0..n]});
}
pub fn main() !void {
try showFile();
}If file.read succeeds, the file is closed before showFile returns.
If file.read fails, the file is still closed before the error leaves showFile.
That is the point of defer: cleanup is placed next to the resource it cleans up.
The old style is fragile:
const file = try std.fs.cwd().openFile("data.txt", .{});
var buf: [128]u8 = undefined;
const n = try file.read(&buf);
file.close();If file.read fails, execution skips the final file.close(). The resource leaks.
With defer, the close operation is registered immediately:
const file = try std.fs.cwd().openFile("data.txt", .{});
defer file.close();A defer runs at the end of its enclosing block, not necessarily at the end of the function.
{
const file = try std.fs.cwd().openFile("data.txt", .{});
defer file.close();
// use file here
}
// file is already closed hereMultiple defer statements run in reverse order.
defer std.debug.print("first\n", .{});
defer std.debug.print("second\n", .{});
defer std.debug.print("third\n", .{});This prints:
third
second
firstThe reverse order is deliberate. It matches the order in which resources are usually acquired.
const a = try openA();
defer closeA(a);
const b = try openB();
defer closeB(b);b is closed first, then a.
errdefer is similar, but it runs only when the scope exits with an error.
This is useful for ownership transfer.
const std = @import("std");
fn makeBuffer(allocator: std.mem.Allocator) ![]u8 {
const buf = try allocator.alloc(u8, 1024);
errdefer allocator.free(buf);
// more work may fail here
return buf;
}If allocation succeeds and later work fails, errdefer frees the buffer.
If the function succeeds, errdefer does not run. The buffer is returned to the caller, and the caller now owns it.
This is different from defer.
fn wrong(allocator: std.mem.Allocator) ![]u8 {
const buf = try allocator.alloc(u8, 1024);
defer allocator.free(buf);
return buf;
}This function returns a slice to memory that has already been freed. The defer runs when the function returns, even on success.
For returned resources, use errdefer until ownership has been transferred.
A common allocation pattern is:
fn makeThing(allocator: std.mem.Allocator) !Thing {
var thing = Thing{};
errdefer thing.deinit(allocator);
try thing.addPart(allocator);
try thing.addOtherPart(allocator);
return thing;
}If either addPart or addOtherPart fails, the partially-built value is destroyed. If everything succeeds, the caller receives the value and must later call deinit.
errdefer may also capture the error:
fn run() !void {
errdefer |err| std.debug.print("failed: {}\n", .{err});
try stepOne();
try stepTwo();
}If stepOne or stepTwo fails, the error is printed before it is returned.
Use this carefully. Logging every low-level error can make programs noisy. It is usually better to log at the boundary where the program can explain what operation failed.
The rule is simple:
defer runs when the scope exits.
errdefer runs only when the scope exits with an error.
Use defer for resources that must always be released.
Use errdefer for resources that must be released only if construction fails.
Exercise 8-21. Open a file and close it with defer.
Exercise 8-22. Allocate a buffer and free it with defer.
Exercise 8-23. Write a function that allocates a buffer and returns it. Use errdefer.
Exercise 8-24. Add two defer statements and observe their order.