# Allocators: Explicit Memory Management — Zig

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

> Choose and use allocators for different lifetimes.

## No hidden allocations

Zig has no global heap that functions use behind your back. Any function that needs dynamic memory takes a **`std.mem.Allocator`** parameter, an interface with `alloc`, `free`, `create`, `destroy`, `realloc` and `dupe`. The **caller chooses the strategy**: **`std.heap.page_allocator`** asks the OS for pages directly (simple, slow for small allocations); **`std.heap.DebugAllocator`** (formerly `GeneralPurposeAllocator`) detects **leaks, double frees and use-after-free** in development; **`std.heap.ArenaAllocator`** wraps another allocator and frees everything at once with `deinit`, ideal for request-scoped or phase-scoped data; **`std.heap.FixedBufferAllocator`** allocates from a buffer you provide, with no heap at all, ideal for embedded or hot paths; **`std.heap.smp_allocator`** is a fast general-purpose allocator for release builds; and **`std.heap.c_allocator`** uses C's `malloc` when linking libc. **`std.testing.allocator`** fails tests that leak. Passing allocators explicitly makes memory use **visible, testable and swappable**: the same parsing code can use an arena in a server and a fixed buffer on a microcontroller.

## Choosing an allocator

The caller picks the strategy; library code just accepts an Allocator.

![A function box with an allocator plug, connected to several interchangeable plugs: a page stack, a debug magnifier, an arena bin and a fixed-size tray.](assets/figures/zig/section-4-map.svg) — Figure 4.1 — Interchangeable allocators behind one interface.

## Debug, arena and fixed-buffer allocators

The same function used with three allocation strategies.

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

// Library code: allocates, and documents that the caller owns the result
fn formatReceipt(allocator: std.mem.Allocator, customer: []const u8, total_paise: u64) ![]u8 {
    return std.fmt.allocPrint(allocator, "Receipt for {s}: Rs {d}.{d:0>2}", .{
        customer, total_paise / 100, total_paise % 100,
    });
}

pub fn main() !void {
    // 1. DebugAllocator: reports leaks when deinit is called
    var debug_alloc: std.heap.DebugAllocator(.{}) = .init;
    defer _ = debug_alloc.deinit();
    const gpa = debug_alloc.allocator();

    const r1 = try formatReceipt(gpa, "Asha", 24_855);
    defer gpa.free(r1);                                  // caller frees
    std.debug.print("{s}\n", .{r1});                     // Receipt for Asha: Rs 248.55

    // 2. Arena: many allocations, one free
    var arena = std.heap.ArenaAllocator.init(gpa);
    defer arena.deinit();                                // frees everything allocated below
    const a = arena.allocator();
    for ([_][]const u8{ "Ravi", "Meera", "Kabir" }) |name| {
        const r = try formatReceipt(a, name, 9_900);
        std.debug.print("{s}\n", .{r});                  // no individual free needed
    }

    // 3. FixedBufferAllocator: no heap at all
    var buffer: [128]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const r3 = try formatReceipt(fba.allocator(), "Zoya", 100);
    std.debug.print("{s}\n", .{r3});                     // fails with OutOfMemory if 128 bytes is not enough
}
```

## Arenas simplify lifetimes

When many allocations share one lifetime (one HTTP request, one compiler pass, one frame of a game), an arena removes almost all `free` calls and the bugs that come with them; you free everything with a single `deinit`.

**Quiz:** Why do Zig functions that allocate take an Allocator parameter?

- [ ] Because Zig has a garbage collector
- [ ] For faster compilation
- [x] So callers control and can see the memory strategy, and allocation is never hidden
- [ ] Because global variables are forbidden

*Answer:* So callers control and can see the memory strategy, and allocation is never hidden. Explicit allocators make memory behaviour visible and swappable.
