# defer, errdefer and Ownership — Zig

Source: https://www.geekswithgeeks.com/en/zig/m-defer

> Release resources reliably and document who owns memory.

## Cleanup next to acquisition

Zig has no destructors, so cleanup is written explicitly, but **right next to the acquisition** using **`defer`**: `const file = try openFile(); defer file.close();`. Deferred statements run when the enclosing **scope** exits, in **reverse order** of declaration, whether by normal flow, `return` or `break`. **`errdefer`** runs only when the scope exits **with an error**, which solves the classic problem of building something in several steps: if step three fails, `errdefer` frees what steps one and two allocated, but on success the resources are returned to the caller intact. `errdefer |err|` can capture the error, for logging. Because there is no ownership system in the compiler, Zig code relies on **conventions** and documentation: a function returning allocated memory says the **caller owns** it and must free it with the same allocator; types holding resources have an `init`/`deinit` pair; and functions taking slices usually **borrow** them for the duration of the call only. The `DebugAllocator` and `std.testing.allocator` catch leaks and double frees during development, compensating for the lack of compile-time checks.

## Building a result safely with errdefer

Partial allocations are freed only on failure.

```zig
const std = @import("std");

const Report = struct {
    title: []u8,
    lines: [][]u8,

    pub fn deinit(self: *Report, allocator: std.mem.Allocator) void {
        for (self.lines) |line| allocator.free(line);
        allocator.free(self.lines);
        allocator.free(self.title);
        self.* = undefined;                         // help catch use-after-deinit in Debug
    }
};

/// Caller owns the returned Report and must call deinit with the same allocator.
fn buildReport(allocator: std.mem.Allocator, title: []const u8, totals: []const u64) !Report {
    const owned_title = try allocator.dupe(u8, title);
    errdefer allocator.free(owned_title);           // only if a later step fails

    const lines = try allocator.alloc([]u8, totals.len);
    errdefer allocator.free(lines);

    var built: usize = 0;
    errdefer for (lines[0..built]) |line| allocator.free(line);

    for (totals, 0..) |t, i| {
        lines[i] = try std.fmt.allocPrint(allocator, "line {d}: {d} paise", .{ i + 1, t });
        built += 1;
    }
    return .{ .title = owned_title, .lines = lines };
}

test "buildReport frees everything" {
    var report = try buildReport(std.testing.allocator, "September", &.{ 120_000, 30_000 });
    defer report.deinit(std.testing.allocator);     // std.testing.allocator fails the test on leaks
    try std.testing.expectEqualStrings("line 2: 30000 paise", report.lines[1]);
}
```

## Writing the checkout time when you check in

defer is like writing your checkout time on the hotel card the moment you check in, so you never forget to leave. errdefer is a note saying "if the trip is cancelled, return the deposit", which only matters when something goes wrong.

**Quiz:** When does an errdefer statement run?

- [ ] Always at scope exit
- [x] Only when the scope exits by returning an error
- [ ] Only on success
- [ ] At program start

*Answer:* Only when the scope exits by returning an error. errdefer is for cleanup that must happen only on the error path.
