# Fixed Buffer Allocator

### Fixed Buffer Allocator

A fixed buffer allocator gives memory from a buffer you already own.

It does not ask the operating system for more memory. It does not grow without limit. It can only allocate from the fixed slice you give it.

That makes it useful when you want strict control.

```text
fixed buffer = known amount of memory
fixed buffer allocator = allocator that uses that memory
```

#### A First Example

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

pub fn main() !void {
    var memory: [1024]u8 = undefined;

    var fba = std.heap.FixedBufferAllocator.init(&memory);
    const allocator = fba.allocator();

    const buffer = try allocator.alloc(u8, 100);

    buffer[0] = 42;

    std.debug.print("allocated {d} bytes\n", .{buffer.len});
}
```

This program creates a 1024-byte array on the stack:

```zig
var memory: [1024]u8 = undefined;
```

Then it creates an allocator that uses that array:

```zig
var fba = std.heap.FixedBufferAllocator.init(&memory);
const allocator = fba.allocator();
```

Now any allocation through `allocator` comes from `memory`.

This allocation asks for 100 bytes:

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

Since the fixed buffer has 1024 bytes, this succeeds.

#### The Buffer Has a Hard Limit

A fixed buffer allocator cannot allocate more memory than the backing buffer contains.

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

pub fn main() !void {
    var memory: [16]u8 = undefined;

    var fba = std.heap.FixedBufferAllocator.init(&memory);
    const allocator = fba.allocator();

    const buffer = try allocator.alloc(u8, 100);

    _ = buffer;
}
```

Here, the backing buffer has only 16 bytes, but the program asks for 100 bytes.

That allocation fails.

Because allocation can fail, `alloc` returns an error union, and we use `try`:

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

This is normal Zig behavior. Memory failure is not hidden.

#### Why Use a Fixed Buffer Allocator

Use a fixed buffer allocator when you want memory use to stay inside a known limit.

This is common in:

```text
embedded programs
small tools
temporary scratch memory
tests
parsers
game loops
real-time systems
```

The main benefit is predictability.

You can say:

```text
This part of the program has 4096 bytes.
It cannot allocate more than that.
```

That is a strong guarantee.

#### Stack-Backed Allocation

The backing memory can be a local array.

```zig
var memory: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&memory);
```

This means the allocator uses stack memory as its storage.

That can be useful for short-lived work:

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

fn parseSmallInput(input: []const u8) !void {
    var scratch: [2048]u8 = undefined;

    var fba = std.heap.FixedBufferAllocator.init(&scratch);
    const allocator = fba.allocator();

    const copy = try allocator.dupe(u8, input);

    std.debug.print("input copy: {s}\n", .{copy});
}
```

The memory exists only while `parseSmallInput` is running.

When the function returns, the stack array is gone, so anything allocated from it is also invalid.

That gives us an important rule:

```text
Do not return memory allocated from a local fixed buffer.
```

#### A Broken Example

This function is wrong:

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

fn makeName() ![]u8 {
    var memory: [64]u8 = undefined;

    var fba = std.heap.FixedBufferAllocator.init(&memory);
    const allocator = fba.allocator();

    const name = try allocator.dupe(u8, "Ada");
    return name;
}
```

The returned slice points into `memory`.

But `memory` is a local array. When `makeName` returns, that array no longer exists.

The caller receives a dangling slice.

The fix is to make the caller provide the allocator:

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

fn makeName(allocator: std.mem.Allocator) ![]u8 {
    return try allocator.dupe(u8, "Ada");
}
```

Now the caller decides how long the memory lives.

#### Fixed Buffer Allocator and `free`

A fixed buffer allocator supports the allocator interface, so you may see code that calls `free`:

```zig
allocator.free(buffer);
```

But conceptually, the fixed buffer allocator is about reusing a fixed area of memory, not returning memory to the operating system.

For beginner code, treat it like this:

```text
The backing buffer owns the memory.
The allocator hands out slices of that buffer.
When the backing buffer goes away, all slices from it become invalid.
```

You still need to understand the lifetime. The backing buffer must outlive every allocation made from it.

#### Using It for Temporary Work

A fixed buffer allocator is good for temporary formatting or parsing.

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

pub fn main() !void {
    var memory: [256]u8 = undefined;

    var fba = std.heap.FixedBufferAllocator.init(&memory);
    const allocator = fba.allocator();

    const message = try std.fmt.allocPrint(
        allocator,
        "user {s} has score {d}",
        .{ "Ada", 42 },
    );

    std.debug.print("{s}\n", .{message});
}
```

The formatted string is stored inside `memory`.

No heap allocation from the operating system is needed.

#### Resetting the Allocator

A fixed buffer allocator can be useful for repeated work if you reset it between rounds.

Conceptually:

```text
use buffer for task 1
reset allocator
use buffer for task 2
reset allocator
use buffer for task 3
```

This avoids repeated heap allocation.

The exact reset API can vary across Zig versions, so check the standard library documentation for your installed version. The idea is stable: the allocator can reuse the same fixed memory region.

#### Fixed Buffer vs Arena

A fixed buffer allocator and an arena allocator can look similar because both are good for temporary allocations.

The difference is where memory comes from.

| Allocator | Memory Source | Growth | Best Use |
|---|---|---|---|
| Fixed buffer allocator | A buffer you provide | Cannot grow beyond buffer | Strict memory limit |
| Arena allocator | Backing allocator | Can request more blocks | Many values with same lifetime |
| General purpose allocator | Heap/backend allocator | Can grow as needed | Mixed lifetimes |

A fixed buffer allocator is stricter.

If the buffer is full, allocation fails.

An arena allocator can usually ask its backing allocator for more memory.

#### When to Use It

Use a fixed buffer allocator when the memory limit is part of the design.

For example:

```text
This parser may use at most 64 KiB of temporary memory.
This embedded task has only this static buffer.
This test should prove the code works with limited memory.
This formatting operation should not touch the heap.
```

Those are good fixed-buffer cases.

#### When Not to Use It

Do not use a fixed buffer allocator when memory needs are unknown and may grow large.

Do not use it for long-lived data unless the backing buffer is also long-lived.

Do not return slices from a local fixed buffer.

Do not assume it can magically expand. The size you give it is the size it has.

#### The Core Idea

A fixed buffer allocator turns an existing slice of bytes into an allocator.

It is simple, strict, and predictable.

The main rule is:

```text
The backing buffer must live longer than every allocation made from it.
```

Once you understand that rule, the fixed buffer allocator becomes a useful tool for writing memory-conscious Zig programs.

