Zig Errdefer
Errdefer is a statement that runs code only when its function returns an error. It is the standard way to undo partial work after a failure.
Errdefer runs before the caller sees the error
attempt registers an errdefer and then returns an error. The cleanup line prints before the caller line.
const std = @import("std");
fn attempt(should_fail: bool) !void {
const out = std.io.getStdOut().writer();
errdefer out.print("cleanup ran\n", .{}) catch {};
if (should_fail) {
return error.SaveFailed;
}
try out.print("saved fine\n", .{});
}
pub fn main() !void {
const out = std.io.getStdOut().writer();
attempt(true) catch {
try out.print("caller handled the error\n", .{});
};
}Errdefer stays silent on success
The same function now succeeds. No cleanup line appears between the two prints.
const std = @import("std");
fn attempt(should_fail: bool) !void {
const out = std.io.getStdOut().writer();
errdefer out.print("cleanup ran\n", .{}) catch {};
if (should_fail) {
return error.SaveFailed;
}
try out.print("saved fine\n", .{});
}
pub fn main() !void {
const out = std.io.getStdOut().writer();
try attempt(false);
try out.print("done\n", .{});
}Errdefer also guards value-returning functions
halved fails on a negative input, so its errdefer prints. The good call adds no extra line.
const std = @import("std");
fn halved(n: i32) !i32 {
const out = std.io.getStdOut().writer();
errdefer out.print("halved failed\n", .{}) catch {};
if (n < 0) {
return error.Negative;
}
return @divTrunc(n, 2);
}
pub fn main() !void {
const out = std.io.getStdOut().writer();
const bad = halved(-6) catch -1;
const good = try halved(10);
try out.print("{d}\n", .{bad});
try out.print("{d}\n", .{good});
}Example
const std = @import("std");
fn load(step: i32) !void {
const out = std.io.getStdOut().writer();
errdefer out.print("undoing the load\n", .{}) catch {};
if (step == 0) {
return error.LoadFailed;
}
try out.print("loaded step {d}\n", .{step});
}
pub fn main() !void {
const out = std.io.getStdOut().writer();
load(0) catch {
try out.print("caller saw the failure\n", .{});
};
try load(2);
try out.print("program finished\n", .{});
}Key points
- Errdefer: errdefer registers a statement to run when the function returns an error.
- Success path: the registered statement stays silent when the function returns a value.
- Timing: the errdefer statement runs while the function exits, before the caller handles the error.
- Placement: write errdefer right after the work it may need to undo.
- Defer: a plain defer runs on every exit, while errdefer runs only on the error exit.
In this example, the first call to load fails, so its errdefer prints the undo line before the caller prints its own line. The second call succeeds and prints its loaded line with no undo line after it. The final line shows the program carried on after both calls.
Note
one function can register several errdefer statements, and they run in reverse order.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
A function registers an errdefer and then returns a normal value. What happens to the errdefer statement?