Zig defer and errdefer for deterministic cleanup and error rollback
Zig’s defer and errdefer statements give deterministic scope-exit cleanup and error-only rollback; this guide explains LIFO execution, evaluation timing, and common pitfalls with a file example.
22 Feb 2026, 01:40 UTC

Use defer for guaranteed cleanup, errdefer for rollback on error
The practical problem is early returns and errors leaving files, memory or locks open. In Zig, attach cleanup to the end of a block with defer, which runs on any exit, and with errdefer, which runs only when the block exits with an error. This gives deterministic scope-exit cleanup without hidden control flow.
How defer and errdefer attach to a block
A defer is bound to the enclosing block. It runs when the block exits by normal completion, break, continue, return, or error propagation. Multiple defers in the same scope execute in reverse lexical order (last declared runs first, LIFO).
errdefer is similar but runs only on error exit. On success it is skipped, making it suitable for undoing partial work that should be kept on success.
Worked example: create a temp log file with rollback
The pattern is acquire, register cleanup, then do work. If work fails, the file is removed; if work succeeds, the file is kept and closed.
const std = @import("std");
fn writeLog(path: []const u8) !void {
const file = try std.fs.cwd().openFile(path, .{ .write = true });
defer file.close();
errdefer {
file.close() catch {};
std.fs.cwd().deleteFile(path) catch {};
}
try file.writeAll("partial");
// simulate failure
return error.WriteFailed;
}Explanation of the flow:
try std.fs.cwd().openFileacquires the handle. If it errors, no defer is registered.defer file.close()is registered immediately after successful acquisition. It will run on any exit, success or error.errdeferis registered next. It runs only on error exit and attempts to close then delete the file, rolling back the partial creation.- When
return error.WriteFailedexecutes, the block exits with error. The errdefer runs first because it was declared last, then the defer runs. The LIFO order ensures dependent resources are released in reverse acquisition order.
Placeholders: replace path with a real path you have permission to write. The cleanup calls are fallible; errors from cleanup are caught to avoid masking the original failure.
Limits and common mistakes
Deferring fallible cleanup without handling its error can mask the original failure or hide a leak. Catch cleanup errors or log them, do not let them replace the primary error.
Do not register a defer on a resource before the acquisition call succeeds. Writing var f = undefined; defer f.close(); f = try open() will close an uninitialized handle. Register after the try.
errdefer does not run on success. Resources needed on both paths must use defer. Using errdefer for a handle that must always be closed leaks on success.
Assuming defers run top-to-bottom causes incorrect ordering for dependent resources. The last declared defer runs first. For a file and a directory, close file before removing directory by declaring directory cleanup first.
Deferred expressions are evaluated at scope exit, not at declaration. If a captured variable is mutated, the deferred code sees the mutated value.
Practical verification
Write a test function that opens a temporary file, defers close, errdefer, returns an error, and then asserts the file no longer exists after the call. Add print statements before and after the scope to confirm deferred code executes at block exit and not at declaration.
Build with zig build or run with zig run src/main.zig from the project root. The compiler rejects defer used outside a block scope. This is a compile-time check you can rely on.
Limitations: defer cannot be used to transfer control, and it adds code that runs implicitly. Review scopes carefully in large functions to keep the order obvious. For complex rollback, consider an explicit cleanup function called from both defer and errdefer to keep logic centralized.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.