Raw Pointers
Raw pointers (ptr and *T) are low-level, unchecked memory addresses. They are the foundation upon which safe abstractions like Ptr<T>, Span<T>, and Box<T> are built. Most Vex code should use those safe abstractions; raw pointers are for FFI, systems programming, and building new safe abstractions.
Pointer Types
| Syntax | Name | Description |
|---|---|---|
ptr | Untyped opaque pointer | A raw memory address with no type information |
*T | Typed immutable pointer | Points to a value of type T, read-only |
*T! | Typed mutable pointer | Points to a value of type T, read-write |
let raw: ptr = vex_malloc(64) // untyped pointer
let typed: *i32 = raw as *i32 // cast to typed
let mut_ptr: *i32! = raw as *i32! // mutable typed pointerObtaining Pointers
From Allocations
let p = vex_malloc(1024) as *u8 // from raw allocation
let box_ptr = Box.new(42) // Box manages the pointer for youFrom References
let x = 42
let ref_to_x: &i32 = &x
let ptr_to_x: *i32 = ref_to_x as *i32From Arrays and Slices
let arr = [1, 2, 3, 4]
let first_ptr: *i32 = &arr[0] // pointer to first element
let array_ptr: *i32 = arr.as_ptr() // pointer to array dataNull Pointers
let null_i32: *i32 = null_ptr // null typed pointer
let null_raw: ptr = null_ptr // null untyped pointerReading and Writing Through Pointers
Reading and writing through raw pointers requires unsafe blocks:
unsafe {
let p = vex_malloc(4) as *i32!
p.write(42) // write value
let val = p.read() // read value (returns 42)
}Without unsafe, raw pointer dereference is a compile error:
let p: *i32 = somePointer
// let val = p.read() // ERROR: raw read requires unsafePointer Arithmetic
WARNING: Do NOT perform manual pointer arithmetic using integer casts (ptr as i64 + offset). This is unsafe, unportable, and defeats alignment guarantees. Use the safe builtin types instead.
The Correct Way: Use Ptr<T>, Span<T>, or RawBuf
// WRONG -- never do this
// let addr = raw_ptr as i64 + index * sizeof_i32
// let val = *(addr as *i32)
// CORRECT -- use Ptr<T>
let p = Ptr.of(base_ptr)
p.writeAt(3, 100) // write to element 3 (stride computed automatically)
let val = p.readAt(3) // read from element 3
// CORRECT -- use RawBuf for byte-level access
let buf = RawBuf.of(raw_ptr)
buf.store<i32>(offset, 42) // typed store at byte offset
let val = buf.load<i32>(offset)Casting Between Pointer Types
let raw: ptr = vex_malloc(64)
// Cast to typed pointers
let bytes: *u8 = raw as *u8
let ints: *i32 = raw as *i32
let mutable: *i32! = raw as *i32!
// Cast back to untyped
let opaque: ptr = ints as ptr
// Cast to integer (for debugging, logging)
let addr: i64 = raw as i64Alignment Requirements
When casting between pointer types, ensure the pointer satisfies the alignment requirements of the target type:
let raw = vex_malloc(64) // 16-byte aligned on most platforms
// Safe: u8 has no alignment requirement
let bytes: *u8 = raw as *u8
// Potentially unsafe: i32 requires 4-byte alignment
// vex_malloc returns 16-byte aligned, so this is fine
let ints: *i32 = raw as *i32FFI and Raw Pointers
Raw pointers are the primary mechanism for C interop:
extern "C" {
fn memcpy(dest: ptr, src: ptr, n: usize): ptr
fn strlen(s: *u8): usize
}
unsafe {
let src = "hello" as *u8
let len = strlen(src) // 5
let buf = vex_malloc(64)
memcpy(buf, src, 6) // copy including null terminator
}Safety Rules
- Always use
unsafeblocks for raw pointer dereference. - Never do integer-based pointer arithmetic (
ptr as i64 + N). UsePtr<T>,Span<T>, orRawBuf. - Ensure alignment when casting between pointer types.
- Ensure the pointed-to memory is live when reading/writing.
- Free allocated memory when done -- raw pointers have no automatic cleanup.
- Prefer safe abstractions (
Box<T>,Ptr<T>,Span<T>) over raw pointers whenever possible.
When to Use Raw Pointers
- FFI with C libraries
- Building new safe abstractions (writing the internals of
Vec<T>,Box<T>, etc.) - Systems programming with memory-mapped I/O
- Interfacing with hardware
When NOT to Use Raw Pointers
- General application code -- use
Box<T>,Vec<T>,Ptr<T>,Span<T> - Array access -- use
Span<T>orVec<T> - Dynamic allocation -- use
Box.new()orVec.new() - Pointer arithmetic -- use
Ptr<T>.readAt()orRawBuf.load<T>()