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.
fixed buffer = known amount of memory
fixed buffer allocator = allocator that uses that memoryA First Example
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:
var memory: [1024]u8 = undefined;Then it creates an allocator that uses that array:
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:
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.
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:
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:
embedded programs
small tools
temporary scratch memory
tests
parsers
game loops
real-time systemsThe main benefit is predictability.
You can say:
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.
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:
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:
Do not return memory allocated from a local fixed buffer.A Broken Example
This function is wrong:
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:
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:
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:
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.
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:
use buffer for task 1
reset allocator
use buffer for task 2
reset allocator
use buffer for task 3This 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:
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:
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.