The arena allocator (also called linear allocator or bump allocator) is the simplest and fastest allocation strategy. Memory is allocated by “bumping” an offset forward. Individual allocations cannot be freed - instead, all allocations are freed at once with reset().
import "conjure/allocators/arena" as arenaWhen to Use Arena Allocators
Arena allocators are perfect when:
- Many allocations share the same lifetime (e.g., all per-frame data)
- You can bulk-free everything at once (e.g., at end of frame/level/request)
- You want maximum allocation speed (O(1) constant time)
- You don’t need individual deallocation
Common use cases:
- Per-frame game data (particles, render commands, AI scratch data)
- Per-level resources (geometry, entities, lightmaps)
- Per-request server data (HTTP handlers, database queries)
- Temporary string building and parsing
Creating an Arena
new
Creates a new arena allocator with the specified capacity.
// Automatic heap allocation (most common)
var myArena = arena.new(1024 * 1024) // 1 MB arena
defer arena.destroy(&myArena)
// With provided buffer (you manage the memory)
var buffer = memory.alloc(1024 * 1024)
var myArena = arena.new(1024 * 1024, buffer)
defer memory.free(buffer)
// Using stack memory (fixed-size array)
var stackBuffer [4096]u8
var stackArena = arena.new(4096, &stackBuffer)Signature:
func new(capacity usize, buffer rawptr = null) ArenaIf buffer is null (the default), heap memory is allocated automatically.
destroy
Frees the backing memory of an arena created without a provided buffer.
arena.destroy(&myArena)Signature:
func destroy(a *Arena)Allocating Memory
alloc
Allocates a block of memory with default alignment (8 bytes).
var data = arena.alloc(&myArena, 256)
if data == null {
io.println("Out of arena memory!")
}Signature:
func alloc(a *Arena, size usize) rawptrReturns null if there isn’t enough space remaining.
allocAligned
Allocates memory with a specific alignment.
// Allocate 16-byte aligned data for SIMD operations
var simdData = arena.allocAligned(&myArena, 64, 16)Signature:
func allocAligned(a *Arena, size usize, alignment usize) rawptrThe alignment must be a power of 2.
allocZeroed
Allocates memory and sets all bytes to zero.
var zeroedData = arena.allocZeroed(&myArena, 1024)
// All 1024 bytes are guaranteed to be 0Signature:
func allocZeroed(a *Arena, size usize) rawptrallocType
Allocates memory for a specific type (convenience function).
struct Entity { x f32, y f32, health i32 }
var entity = arena.allocType[Entity](&myArena)Signature:
func allocType[T](a *Arena) rawptrFreeing Memory
free
Individual free is a no-op for arenas. This function exists for API consistency but does nothing.
arena.free(&myArena, ptr) // Does nothingSignature:
func free(a *Arena, ptr rawptr)reset
Resets the arena, making all memory available again. This is the primary way to “free” arena memory.
// At the end of each frame
arena.reset(&frameArena)Signature:
func reset(a *Arena)resetZeroed
Resets the arena AND zeros all previously allocated memory.
// Clear sensitive data before reset
arena.resetZeroed(&secureArena)Signature:
func resetZeroed(a *Arena)Resizing Allocations
resize
Attempts to resize the most recent allocation.
var data = arena.alloc(&myArena, 100)
// Need more space...
data = arena.resize(&myArena, data, 100, 200)
if data == null {
io.println("Not enough space to resize!")
}Signature:
func resize(a *Arena, ptr rawptr, oldSize usize, newSize usize) rawptrIf the pointer is the most recent allocation and there’s space, this extends it in-place. Otherwise, it allocates new memory and copies the old data.
Savepoints (Temporary Memory)
Savepoints allow you to mark a position and later restore to it, effectively “freeing” all allocations made after the savepoint.
savepoint
Creates a savepoint at the current position.
var save = arena.savepoint(&myArena)
// Make temporary allocations
var temp1 = arena.alloc(&myArena, 256)
var temp2 = arena.alloc(&myArena, 512)
processData(temp1, temp2)
// Restore - temp1 and temp2 memory is now available again
arena.restore(save)Signature:
func savepoint(a *Arena) ArenaSavepointrestore
Restores the arena to a previous savepoint.
arena.restore(save)Signature:
func restore(save ArenaSavepoint)Introspection
used
Returns bytes currently allocated.
var bytesUsed = arena.used(&myArena)
io.println("Arena using #{bytesUsed} bytes")Signature:
func used(a *Arena) usizeremaining
Returns bytes available for allocation.
var bytesLeft = arena.remaining(&myArena)Signature:
func remaining(a *Arena) usizecapacity
Returns total arena capacity.
var total = arena.capacity(&myArena)Signature:
func capacity(a *Arena) usizeisEmpty / isFull
Check arena state.
if arena.isEmpty(&myArena) {
io.println("Arena has no allocations")
}
if arena.isFull(&myArena) {
io.println("Arena is full!")
}Signatures:
func isEmpty(a *Arena) bool
func isFull(a *Arena) boolTypes
Arena
The main arena allocator structure.
struct Arena {
buffer rawptr // Backing memory
capacity usize // Total size
offset usize // Current position
prevOffset usize // Previous position (for resize)
}ArenaSavepoint
Represents a saved position in the arena.
struct ArenaSavepoint {
arena *Arena // The arena this belongs to
offset usize // Saved offset
prevOffset usize // Saved previous offset
}Example: Per-Frame Allocation
import "conjure/allocators/arena" as arena
import "conjure/io"
var frameArena = arena.create(1024 * 1024) // 1 MB
struct Particle { x f32, y f32, vx f32, vy f32, life f32 }
struct RenderCommand { type u8, data [64]u8 }
func gameLoop() {
while running {
// All frame allocations come from the arena
var particles = *[1000]Particle(arena.alloc(&frameArena, 1000 * Particle.size))
var commands = *[500]RenderCommand(arena.alloc(&frameArena, 500 * RenderCommand.size))
updateParticles(particles)
buildRenderCommands(commands)
render(commands)
// Single O(1) operation frees ALL frame allocations
arena.reset(&frameArena)
}
}Example: Level Loading
import "conjure/allocators/arena" as arena
var levelArena = arena.create(64 * 1024 * 1024) // 64 MB for level data
func loadLevel(path string) {
// Reset to clear previous level
arena.reset(&levelArena)
// Load all level data into the arena
var geometry = arena.alloc(&levelArena, geometrySize)
var textures = arena.alloc(&levelArena, textureDataSize)
var entities = arena.alloc(&levelArena, entityDataSize)
var navmesh = arena.alloc(&levelArena, navmeshSize)
// Parse and populate...
loadGeometry(geometry, path)
loadTextures(textures, path)
// etc.
}
func unloadLevel() {
// Everything freed instantly
arena.reset(&levelArena)
}Example: Nested Savepoints
import "conjure/allocators/arena" as arena
var scratch = arena.create(64 * 1024)
func processFile(path string) {
var outerSave = arena.savepoint(&scratch)
var fileData = arena.alloc(&scratch, fileSize)
readFile(path, fileData)
// Process each section
for section in sections {
var innerSave = arena.savepoint(&scratch)
var sectionData = arena.alloc(&scratch, section.size)
processSectionData(sectionData)
// Free section-specific allocations
arena.restore(innerSave)
}
// Free all file allocations
arena.restore(outerSave)
}