Skip to content

std.mem

std.mem is the raw memory helper module. It provides byte copy/move/set operations, pointer search and comparison, raw value moves, checked view construction, exact volatile scalar access, and the accepted safe wipe operation for a valid exclusive byte view. Raw pointer functions are unsafe because callers must prove pointer validity, alignment, initialization state, and any aliasing requirements.

Migration status: wipe is the accepted RFC 0019 target addition and is not yet present in the current lib/std inventory. RFC 0050 removes the checked-in from_cstr fake-owner helper from the accepted target without an alias. RFC 0053 replaces the checked-in two-argument slice fake-owner helper with the owner-anchored view overloads documented below. The current source still exports slice and does not yet implement view. RFC 0054 keeps output-tail initialization out of std.mem: owning slices use OutputSpan<T>, while sparse raw storage uses the separately accepted SDK-internal RawStorage<T> target documented below. RFC 0056 replaces the checked-in public write_at, read_at, and drop_at experiment with the single-slot MaybeUninit<T> target. That migration is also pending: the current source still exports the three helpers and does not yet export MaybeUninit<T>. RFC 0058 additionally moves AllocError here and removes the whole public std.allocator module. Allocation itself remains compiler/runtime-private; std.mem gains no public raw allocation, reallocation, or release function. RFC 0059 places bounded terminated-byte roles in std.ffi, not in this raw-memory module. RFC 0075 replaces the transitional public @zynx.volatile.read/write spelling with the exact std.mem.volatile.load/store overloads documented below. That migration is pending; the backing semantic primitive becomes compiler-private. RFC 0076 places the separate bounded host-provided MMIO authority in std.mmio; it does not widen this raw-pointer API.

How It Works

The functions operate on raw pointers and byte counts. They do not own storage and do not run element constructors or destructors unless the specific function says so. Use direct casts and pointer arithmetic for raw address operations.

Zynx
import std.mem;

fn main() {
	let bytes: [u8; 3] = [0, 0, 0];
	unsafe {
		mem.set(bytes.ptr, 7, bytes.length);
		assert mem.find(bytes.ptr, 7, bytes.length) != null;
	}
	assert bytes[0] == 7;
}

Exact Volatile Scalar Access

std.mem.volatile exposes two ordinary unsafe function names. Each has exactly eight overloads; there is no generic public operation:

PayloadLoadStore
i8unsafe load(source: *i8) -> i8unsafe store(destination: *mut i8, value: i8) -> void
u8unsafe load(source: *u8) -> u8unsafe store(destination: *mut u8, value: u8) -> void
i16unsafe load(source: *i16) -> i16unsafe store(destination: *mut i16, value: i16) -> void
u16unsafe load(source: *u16) -> u16unsafe store(destination: *mut u16, value: u16) -> void
i32unsafe load(source: *i32) -> i32unsafe store(destination: *mut i32, value: i32) -> void
u32unsafe load(source: *u32) -> u32unsafe store(destination: *mut u32, value: u32) -> void
i64unsafe load(source: *i64) -> i64unsafe store(destination: *mut i64, value: i64) -> void
u64unsafe load(source: *u64) -> u64unsafe store(destination: *mut u64, value: u64) -> void
Zynx
// RFC 0075 target; implementation pending.
import std.mem;

unsafe {
	let status = mem.volatile.load(status_register);
	let result = mem.volatile.store(control_register, status | 1);
	assert result == void();
}

bool, floats, raw pointers, usize, isize, 128-bit integers, custom scalars, and aggregates are not overload candidates. Signed payloads preserve their raw same-width representation; these functions do not perform numeric or endian conversion. store returns the ordinary void() value and may appear in an expression context. Neither operation is available during constant evaluation.

Every dynamically reached call must become exactly one target-certified native volatile access of the same width. It may not be eliminated, invented, duplicated, merged, split, speculated onto another path, or lowered to a helper, library call, or emulation sequence. Relative source evaluation order between volatile calls is preserved. A target that cannot certify that operation emits a hard target-compilation diagnostic. Every generic Wasm/WASI target rejects this API. RFC 0076's host-provided MMIO extension uses separate nominal Region methods; it does not enable these raw-pointer overloads.

The pointer must be non-null, naturally aligned, live for the complete access, and carry ordinary provenance and access authority for its storage. The API does not turn an integer address into valid provenance or mint an allocation, mapping, device capability, or MMIO authority. Memory outside ordinary live storage needs a nominal mapping or target package whose contract establishes those facts. RFC 0076 provides exactly that mapping boundary for device registers through std.mmio.Region, without exposing its pointer through this API.

Volatile access is neither atomic nor tear-free. It creates no happens-before edge, synchronization, fence, or ordering against ordinary memory or std.atomic, and supplies no cache/DMA maintenance, interrupt or bus ordering, posted-write completion, device protocol, or endian conversion. Use a target-specific API for those guarantees. volatile<T> remains only C ABI pointer-qualifier metadata. Direct ordinary-source @zynx.volatile.* calls are rejected; any semantic primitive below these SDK wrappers is compiler-private.

Checked Views Over Raw Storage

view is the one public raw-pointer-to-checked-view boundary. It is overloaded by the anchor's role:

Zynx
unsafe fn view<T, A, region L>(
	ptr: *T,
	length: usize,
	anchor: A in L,
) -> [T] in L;

unsafe fn view<T, A, region L>(
	ptr: *mut T,
	length: usize,
	anchor: &A in L,
) -> &[T] in L;

An ordinary anchor produces a shared [T]; an explicit exclusive loan &anchor produces an exclusive &[T]. A writable pointer by itself does not grant exclusive access. The anchor is last so ptr and length are evaluated before its loan activates. The returned view remains in the anchor's region: the anchor cannot move, drop, or reallocate while the view is live, and an exclusive view also prevents any independent access through the anchor.

Zynx
fn bytes<region L>(self in L) -> [u8] in L {
	unsafe {
		return mem.view(self.ptr, self.length, self);
	}
}

fn bytes<region L>(&self in L) -> &[u8] in L {
	unsafe {
		return mem.view(self.ptr, self.length, &self);
	}
}

The caller of either overload must prove that every nonempty range is non-null, correctly aligned, within one live object or allocation with provenance for the complete range, and free of length * sizeof(T) overflow. Exactly length slots must contain initialized valid T values for the complete returned region. Shared construction requires no conflicting writes; exclusive construction requires exclusive access for the complete range. A zero-length view may use null, but it still borrows its anchor. A nonempty zero-sized element view has logical length and requires a canonical aligned pointer; it does not justify physical allocation or pointer arithmetic.

view never turns raw storage into an owner. It cannot represent uninitialized or partially initialized memory, unused owning-slice capacity, or a destination that a foreign call will initialize. An owning slice exposes that capacity only through OutputSpan<T>, which owns the allocation and tracks one contiguous live suffix. Foreign output can access a raw destination only through the byte-specific OutputSpan<u8>.spare_ptr() and unsafe advance(count) boundary. Audited non-byte foreign producers may use a private compiler/SDK trusted primitive over MaybeUninit<T> or the span state, but no generic raw output pointer is exported. The lower-level hole-bearing storage needed by arenas and hash tables is not a checked view or output span. The accepted target uses SDK-internal RawStorage<T> and keeps liveness in the owning nominal container. There is no view_mut, from_raw_parts, two-argument slice, or unanchored checked-view constructor.

When a checked owner, array, or view already exists, use safe borrowing and range slicing instead of reconstructing its view through raw fields:

Zynx
let read: [u8] in _ = bytes[start..<end];
let write: &[u8] in _ = &bytes[start..<end];

Single-Slot Staged Initialization

The accepted target exposes MaybeUninit<T> for exactly one staged slot. It is not a collection, allocation owner, output buffer, or checked range. Its operations are conceptually:

Zynx
// RFC 0056 target; implementation pending.
var slot = MaybeUninit<T>();

unsafe {
	(&slot).initialize(<-value);
	let read: *T = slot.ptr;
	let write: *mut T = (&slot).ptr;
	let value: ^T = (&slot).take();
}

initialize performs a no-drop move into a slot that the caller proves is uninitialized. take moves the complete valid T out and returns the slot to the uninitialized state. Both are unsafe because MaybeUninit<T> deliberately stores no runtime liveness tag: double initialization can lose a cleanup obligation, while taking an uninitialized or invalid value violates the unsafe contract. Ordinary assignment is never substituted for initialize.

The single role-directed ptr property neither initializes nor takes the slot. Shared slot.ptr yields *T; explicit exclusive (&slot).ptr yields *mut T. There is no second ptr_mut spelling. The projection exists for an audited foreign or intrinsic operation; the caller must still prove what that operation initialized before invoking take. Dropping MaybeUninit<T> never drops a possible T payload. Correct code must take and ordinarily drop or transfer every value it initialized.

Ordinary aggregates and array or slice literals may contain explicitly constructed MaybeUninit<T> values. Each element remains an independent single-slot state machine. There is no dedicated constructor that retypes a fresh raw allocation as a checked MaybeUninit<T> range, no use of mem.view to make that retyping valid, and no range-wide assume_init or publication operation. There is likewise no dedicated uninitialized-buffer API, ArrayBuilder, or ownership-adoption operation. Contiguous owning-slice output uses OutputSpan<T>, sparse core-container capacity uses SDK-internal RawStorage<T>, and a completely initialized borrowed range uses mem.view. Publishing a completed built-in allocation as ^[T] or ^str remains a private compiler/SDK transition. Foreign-owned allocation remains in a nominal resource wrapper and may expose an anchored initialized view; it is not adopted as a built-in owner. No ManuallyDrop<T>, forget, or leak API exists. Alternative cleanup belongs only to the defining nominal type through language-level discard self, which still drops every residual field.

SDK-Internal Sparse Storage

The accepted RawStorage<T> target is an internal affine owner for one raw built-in-domain allocation. It represents a properly aligned MaybeUninit<T> slot prefix followed, when an internal container needs it, by alignment padding and a private byte tail. It owns allocation and deallocation, but deliberately does not own an initialized [T] range and does not know which slots contain live values.

“SDK-internal” is a distribution/module-boundary description, not a new source visibility modifier. RFC 0011 still has only default-private declarations and explicit export: the compiler-trusted SDK may share this implementation through a non-public target/module, while no public std root re-exports it and ordinary packages cannot name that internal dependency.

Only the enclosing nominal container may use its trusted slot initialization, take, drop, pointer, and tail projections. The container's own authoritative metadata supplies every liveness proof: Arena<T> uses its dense occupied array and RawTable<E> uses its control bytes. There is no liveness bitmap in RawStorage, no public slot token, and no public raw-storage constructor, method, import path, or layout escape. Ordinary code continues to use MaybeUninit<T> for a single staged value, OutputSpan<T> for a contiguous append-only suffix, and mem.view only for a completely initialized borrowed range.

Dropping RawStorage<T> releases its one raw block and never scans or drops T. The enclosing container must first take or destroy every slot marked live by its authoritative metadata. A checked loan into a slot is anchored in the whole container, not in the raw block alone, so the loan also prevents the metadata change, removal, or reallocation that could invalidate it.

RawStorage<T> has no grow, reserve, realloc, clone, or iterator. Because it cannot interpret holes, resizing is always a container-level transaction: checked layout and destination allocation finish first, then only known-live values move through short trusted init/arm or take/disarm transitions with no checked or user-code edge in the middle. Zero-sized values retain logical slot and cleanup counts without payload allocation or element-pointer arithmetic.

Copy And Move

Use copy only for non-overlapping regions. Use move when source and destination may overlap.

Zynx
import std.mem;

fn main() {
	let src: [u8; 2] = [1, 2];
	let dst: [u8; 2] = [0, 0];

	unsafe {
		mem.copy(dst.ptr, src.ptr, src.length);
	}
	assert dst[1] == 2;
}

Non-Elidable Wipe

wipe(out: &[u8]) overwrites exactly the passed live byte region. The compiler, optimizer, and linker may not remove or coalesce away that overwrite. It does not allocate or throw, and the overwrite pattern is deliberately unspecified.

The guarantee does not extend to earlier copies, registers, stack spills, swap, core dumps, or storage outside the passed region. There is no generic wipe<T>.

Ownership, Allocation, Errors, And Unspecified Behavior

std.mem never allocates and never throws. Its raw-pointer functions operate on storage owned by the caller and bypass ordinary borrow, bounds, and initialization checks. Passing invalid pointers, wrong lengths, wrong alignment, uninitialized values, aliased regions where the function forbids overlap, or dead storage is outside their documented contract. wipe is the exception: it accepts an ordinary valid exclusive byte view and performs no unchecked pointer operation at the call site.

The checked-in read_at, write_at, and drop_at functions are transitional, not target API. In particular, the current write_at body uses ordinary raw assignment even though the accepted language contract makes that operation replacement of an existing initialized value. RFC 0056 removes all three helpers without aliases. Single-slot staged code uses MaybeUninit<T>; RawStorage<T> and OutputSpan<T> keep their stronger container-level state machines behind their own boundaries.

Do not use those primitives to reimplement owning-slice tail growth in ordinary code. OutputSpan<T> supplies the checked prefix invariant, bounds trap, reverse cleanup, and explicit commit/cancel transition for that case.

Use std.bytes for safe borrowed byte views, typed owners and nominal resource types for storage ownership, and std.bit for integer bit operations. Public code cannot allocate or adopt raw storage through std.mem.

When a byte-oriented API needs a Bytes value over foreign storage, first form an owner-anchored initialized [u8] with mem.view, then use the safe bytes.Bytes(view) constructor. Bytes never accepts (ptr, length) directly.

Text At C Boundaries

A str is a counted {ptr, size} view, may contain U+0000, and does not guarantee ptr[size] == 0 in any role. Pass its pointer only to a size-aware foreign operation and pass the size alongside it. A foreign operation that requires terminated bytes receives an explicitly constructed std.ffi.CStr; dynamic text is checked for interior NUL and copied.

The removed from_cstr reinterpreted foreign storage as a ^str, inventing a drop obligation and allocator provenance that the caller did not own. There is no replacement raw-pointer text constructor. std.ffi.CStr is the separate accepted terminated-byte role: initialized foreign storage first becomes an owner-anchored bounded byte view through mem.view, then CStr.find searches only that bound. Foreign ownership is never adopted; an owning ^CStr is an explicit checked copy in the built-in domain.

API Reference

Types

TypeDescription
MaybeUninit<T>One untagged staged slot with role-directed .ptr (*T shared, *mut T exclusive) and unsafe no-drop initialize/owner-returning take. Accepted target API; implementation pending.

Errors

ErrorValueDescription
AllocErrorOutOfMemoryA checked public allocation could not represent or obtain its requested layout. Accepted target identity; implementation migration from std.mem.AllocError is pending.

Functions

FunctionSignatureParametersReturnsDescription
copyunsafe copy(dst: *mut u8, src: *u8, size: usize)dst: destination, src: source, size: byte extentvoidCopies non-overlapping bytes.
moveunsafe move(dst: *mut u8, src: *u8, size: usize)dst: destination, src: source, size: byte extentvoidMoves bytes with overlap support.
setunsafe set(dst: *mut u8, value: u8, size: usize)dst: destination, value: byte, size: byte extentvoidSets bytes to a value.
zerounsafe zero(ptr: *mut u8, size: usize)ptr: destination, size: byte extentvoidSets bytes to zero.
wipewipe(out: &[u8])out: valid exclusive byte regionvoidOverwrites the exact region and cannot be removed by optimization; pattern unspecified. Accepted target API, implementation pending.
compareunsafe compare(a: *u8, b: *u8, size: usize) -> i32a: left, b: right, size: byte extenti32Returns 0 when byte ranges are equal.
findunsafe find(ptr: *u8, byte: u8, size: usize) -> *u8ptr: haystack, byte: needle, size: byte extent*u8Finds a byte or returns null.
viewunsafe view<T, A, region L>(ptr: *T, length: usize, anchor: A in L) -> [T] in Lptr: first initialized element, length: element count, anchor: shared lifetime source[T] in LCreates a checked shared view tied to the anchor. Accepted target API; implementation pending.
viewunsafe view<T, A, region L>(ptr: *mut T, length: usize, anchor: &A in L) -> &[T] in Lptr: first initialized writable element, length: element count, anchor: exclusive lifetime source&[T] in LCreates a checked exclusive view tied to the anchor. Accepted target API; implementation pending.
volatile.loadEight exact overloads listed under Exact Volatile Scalar Access, one each for i8, u8, i16, u16, i32, u32, i64, and u64source: provenance-valid naturally aligned live scalar addressthe exact payload typePerforms exactly one target-certified native same-width volatile load. Accepted target API; implementation pending.
volatile.storeEight exact overloads listed under Exact Volatile Scalar Access, one each for i8, u8, i16, u16, i32, u32, i64, and u64destination: provenance-valid naturally aligned live scalar address, value: raw same-width bitsvoidPerforms exactly one target-certified native same-width volatile store. Accepted target API; implementation pending.
byte_atunsafe byte_at(ptr: *u8, index: usize) -> u8ptr: source pointer, index: byte indexu8Reads a byte at ptr[index] without bounds checks. Caller must prove pointer validity and index bounds.
swap_bytesunsafe swap_bytes(a: *mut u8, b: *mut u8, size: usize)a: first region, b: second region, size: byte extentvoidSwaps bytes between non-overlapping regions.

Status

std.mem is the accepted public unsafe memory helper API. RFC 0019 additionally accepts safe wipe, whose implementation remains pending; RFC 0050 removes from_cstr from this module without an alias, and RFC 0059 supplies the nominal bounded successor in std.ffi. RFC 0053 removes the two-argument slice and adds only the two owner-anchored view overloads for fully initialized storage. RFC 0054 assigns contiguous owning-slice output to OutputSpan<T> rather than adding a second raw-memory view constructor. The accepted sparse-storage target adds only SDK-internal RawStorage<T> and no public std.mem symbol. RFC 0056 adds only the single-slot MaybeUninit<T> public staging boundary and removes write_at, read_at, and drop_at; it rejects dedicated raw-range construction/retyping, range-wide conversion/publication, and ownership adoption. RFC 0075 adds only the eight exact integer overloads of each std.mem.volatile.load/store name and removes the public intrinsic spelling; unsupported targets fail closed instead of splitting or emulating an access. RFC 0076 adds the distinct host-provided std.mmio.Region wrapper without extending this raw-pointer surface. For the exact currently exported transitional source surface, see Standard Library Inventory.

Released under the MIT License.