Unsafe and raw memory
Most Beans code is safe: no null, checked indexing, and ownership the compiler
proves. When you need raw memory, for a device, a database page, or C interop,
you enter an unsafe { } block. Inside it, some low-level operations become
available, and their safety becomes your responsibility.
unsafe { let counters: RawPtr<Counter> = RawPtr.alloc(4) // element type's alignment let page: RawPtr<Counter> = RawPtr.alloc_aligned(2, 4096) counters.write(new_value) counters.free()}unsafe gates the operation, not the function. There is no unsafe fn, so the
block always sits at the call site where the raw operation happens.
RawPtr
Section titled “RawPtr”RawPtr<T> is a raw typed pointer to primitive integer, float, bool,
raw-pointer, fixed-array, or extern "C" struct/union memory (these shapes can
nest).
Construct (all unsafe):
RawPtr.alloc(count)gives zeroed unmanaged storage with the element type’s own alignment.RawPtr.alloc_aligned(count, align)asks for a stricter alignment.alignmust be a power of two and never weaker than the element’s own; both are checked at run time.RawPtr.null()andRawPtr.from_address(addr)build pointer values without allocating.
Methods (all unsafe): read(), write(v), read_volatile(),
write_volatile(v), offset(n), address(), is_null(), element_size(),
element_align(), copy_from(src, n) (overlap-safe), fill_zero(n), free().
Integer and bool pointers also provide sequentially-consistent atomic_load,
atomic_store, atomic_compare_exchange; integer pointers add
atomic_fetch_add (returns the old value).
You own lifetime, bounds, alignment, address validity, and matching each
alloc with one free. A null memory operation panics; everything else is on
you. Raw pointers are copyable, so freeing one alias leaves the others dangling.
RawPtr.with_local(inout value, fn(pointer: RawPtr<T>) { ... }) lends a pointer
to one stack value for the duration of the closure.
Slice<T> is a non-owning {pointer, length} view over raw-compatible memory.
Slice.from_raw(ptr, len), get, set, indexing, subslice, as_ptr, and
iteration all require unsafe; reads and writes are bounds-checked. A non-empty
slice rejects a null pointer. You must keep the backing allocation alive and not
use the view after free.
Fixed arrays
Section titled “Fixed arrays”[T; N] is a fixed-size inline array (1 <= N <= 4096) of inline scalar,
RawPtr, nested-array, or struct elements. Unlike raw pointers, fixed arrays
are safe: checked indexing, element assignment on var locals, len(),
equality, and for iteration, all without unsafe. A list-shaped literal takes
fixed-array meaning from its declared spot:
var lanes: [f32; 4] = [1, 2, 3, 4]lanes[0] = 9.0SIMD vector families (Simd4f32, Simd4i32, Simd16u8, …) are available
inside unsafe. A vector’s name is its shape. See
SIMD, arrays, slices for the full operation set.
Intrinsics and inline assembly
Section titled “Intrinsics and inline assembly”std.intrinsicis a closed allowlist of named machine operations (popcount,bswap32,sqrt,fma, …), each requiringunsafe. See std.cpu and std.intrinsic.std.asmallows a small, per-architecture allowlist of assembly templates insideunsafe. See std.asm.
A complete program
Section titled “A complete program”Allocate, write, read back, then free. Every raw step is inside the unsafe
block, and the one alloc is matched by one free:
import std.io
fn main() { unsafe { let cells: RawPtr<i32> = RawPtr.alloc(3) cells.write(10) cells.offset(1).write(20) cells.offset(2).write(30)
let sum: i32 = cells.read() + cells.offset(1).read() + cells.offset(2).read() io.println("sum {sum}")
cells.free() }}Prefer a fixed array or a safe collection when you can: they give checked
indexing and automatic cleanup with no unsafe. Reach for RawPtr and Slice
only for device memory, a hand-laid file format, or
C interop, where the raw layout is the point.