# Allocation Failure Handling

### Allocation Failure Handling

Allocation can fail.

This is one of the most important facts in Zig memory management. When your program asks for heap memory, the request may not succeed. The system may be out of memory. A fixed buffer may be full. A custom allocator may reject the request because of a memory limit.

Zig does not hide this.

When you allocate memory, the result is usually an error union:

```zig
const buffer = try allocator.alloc(u8, 1024);
```

The call may return a slice of bytes, or it may return an error.

#### Allocation Returns an Error

The common allocation error is:

```zig
error.OutOfMemory
```

You usually see it through `try`:

```zig
fn makeBuffer(allocator: std.mem.Allocator) ![]u8 {
    return try allocator.alloc(u8, 1024);
}
```

The return type is:

```zig
![]u8
```

That means:

```text
either an error
or a []u8 slice
```

If allocation fails, `try` returns the error from `makeBuffer`.

If allocation succeeds, `try` unwraps the slice and gives it to the rest of the function.

#### Why Zig Forces You to Notice

Some languages treat allocation failure as something rare or fatal. Zig treats it as part of the function contract.

That makes APIs clearer.

This function can fail:

```zig
fn makeMessage(allocator: std.mem.Allocator) ![]u8 {
    return try std.fmt.allocPrint(allocator, "hello {s}", .{"zig"});
}
```

The `![]u8` return type tells the caller:

```text
This function may fail.
One possible reason is allocation failure.
```

The caller must then decide what to do.

#### Simple Propagation with `try`

The most common beginner pattern is to propagate the error.

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

fn makeBuffer(allocator: std.mem.Allocator, size: usize) ![]u8 {
    return try allocator.alloc(u8, size);
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    const buffer = try makeBuffer(allocator, 1024);
    defer allocator.free(buffer);

    buffer[0] = 42;
}
```

Here, `makeBuffer` can fail. `main` can also fail because its return type is:

```zig
!void
```

So `main` is allowed to return an error.

This is clean for small programs and command-line tools.

#### Handling the Error with `catch`

Sometimes you do not want to propagate the error. You want to handle it directly.

Use `catch`.

```zig
const buffer = allocator.alloc(u8, 1024) catch |err| {
    std.debug.print("allocation failed: {}\n", .{err});
    return err;
};
```

This code says:

```text
Try to allocate memory.
If it fails, print the error and return it.
```

You can also choose a fallback behavior.

```zig
const buffer = allocator.alloc(u8, 1024) catch {
    std.debug.print("not enough memory\n", .{});
    return;
};
```

This works only inside a function where returning without a value is valid.

#### Handling `OutOfMemory` Specifically

You may want to handle only `OutOfMemory`.

```zig
const buffer = allocator.alloc(u8, 1024) catch |err| switch (err) {
    error.OutOfMemory => {
        std.debug.print("not enough memory\n", .{});
        return err;
    },
};
```

This looks verbose, but it is explicit.

You know exactly which error is being handled.

For allocation, the error set is often small, but the same pattern applies to larger error sets.

#### Cleanup After Partial Success

Allocation failure becomes more interesting when a function allocates several things.

Consider this function:

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

const User = struct {
    name: []u8,
    email: []u8,
};

fn makeUser(
    allocator: std.mem.Allocator,
    name: []const u8,
    email: []const u8,
) !User {
    const name_copy = try allocator.dupe(u8, name);
    const email_copy = try allocator.dupe(u8, email);

    return User{
        .name = name_copy,
        .email = email_copy,
    };
}
```

This has a bug.

If `name_copy` succeeds but `email_copy` fails, the function returns an error and loses the first allocation. That leaks memory.

We need cleanup for partial success.

#### Use `errdefer`

`errdefer` runs only when the function exits with an error.

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

const User = struct {
    name: []u8,
    email: []u8,

    pub fn deinit(self: User, allocator: std.mem.Allocator) void {
        allocator.free(self.name);
        allocator.free(self.email);
    }
};

fn makeUser(
    allocator: std.mem.Allocator,
    name: []const u8,
    email: []const u8,
) !User {
    const name_copy = try allocator.dupe(u8, name);
    errdefer allocator.free(name_copy);

    const email_copy = try allocator.dupe(u8, email);
    errdefer allocator.free(email_copy);

    return User{
        .name = name_copy,
        .email = email_copy,
    };
}
```

Now read the function carefully.

If `name_copy` succeeds, `errdefer allocator.free(name_copy)` is registered.

If `email_copy` fails, the function exits with an error, so the `errdefer` runs and frees `name_copy`.

If both allocations succeed, the function returns `User`. The `errdefer` statements do not run because the function did not return an error.

At that point, the returned `User` owns both allocations.

The caller must clean up:

```zig
const user = try makeUser(allocator, "Ada", "ada@example.com");
defer user.deinit(allocator);
```

This is a core Zig pattern.

#### `defer` vs `errdefer`

Use `defer` for cleanup that should always happen when leaving the scope.

Use `errdefer` for cleanup that should happen only if the function fails.

```zig
const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer);
```

This frees `buffer` when the scope exits, whether the function succeeds or fails.

```zig
const buffer = try allocator.alloc(u8, 1024);
errdefer allocator.free(buffer);
```

This frees `buffer` only if the function exits with an error.

Use `errdefer` when ownership will be transferred on success.

#### Ownership Transfer on Success

In `makeUser`, ownership transfers to the caller when the function succeeds.

That is why `defer` would be wrong inside `makeUser`.

```zig
fn brokenMakeName(allocator: std.mem.Allocator) ![]u8 {
    const name = try allocator.dupe(u8, "Ada");
    defer allocator.free(name);

    return name;
}
```

This returns a slice, but `defer` frees it before the caller can use it.

That creates a dangling slice.

Correct version:

```zig
fn makeName(allocator: std.mem.Allocator) ![]u8 {
    const name = try allocator.dupe(u8, "Ada");
    errdefer allocator.free(name);

    return name;
}
```

In this simple function, the `errdefer` is not strictly necessary because there are no later fallible operations after the allocation. But it shows the idea.

If the function allocates and then does more work that may fail, use `errdefer`.

#### Fallible Initialization Pattern

Many Zig types follow this shape:

```zig
const Thing = struct {
    data: []u8,

    pub fn init(allocator: std.mem.Allocator) !Thing {
        const data = try allocator.alloc(u8, 1024);
        errdefer allocator.free(data);

        return Thing{ .data = data };
    }

    pub fn deinit(self: Thing, allocator: std.mem.Allocator) void {
        allocator.free(self.data);
    }
};
```

This gives the type a clear lifecycle:

```text
init may allocate and fail
successful init returns owned resources
deinit releases those resources
```

The caller uses it like this:

```zig
const thing = try Thing.init(allocator);
defer thing.deinit(allocator);
```

This pattern scales well as structs become more complex.

#### Avoid Panic for Normal Allocation Failure

A panic is a crash.

You can panic on allocation failure:

```zig
const buffer = allocator.alloc(u8, 1024) catch @panic("out of memory");
```

But this should not be your default habit.

Use `try` when the caller can handle or report the error.

Use `catch` when you have a local recovery strategy.

Use panic only when allocation failure means the program cannot reasonably continue.

For many command-line tools, returning an error from `main` is good enough.

#### Fixed Buffer Allocator Makes Failure Easy to Test

A fixed buffer allocator is useful for testing allocation failure.

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

test "allocation can fail" {
    var memory: [8]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&memory);

    const allocator = fba.allocator();

    const result = allocator.alloc(u8, 100);

    try std.testing.expectError(error.OutOfMemory, result);
}
```

The buffer has only 8 bytes. The request asks for 100 bytes. The allocation must fail.

This kind of test is useful because it proves your code handles memory failure instead of assuming success.

#### The Core Idea

Allocation failure is normal in Zig’s type system.

An allocation returns either memory or an error. Use `try` to propagate the error. Use `catch` to handle it. Use `errdefer` to clean up partial work when a function fails after some allocations have already succeeded.

The rule is:

```text
Every fallible allocation path must either transfer ownership safely or clean up what it already owns.
```

