Ptr<T> — Typed Generic Pointer
Ptr<T> is Vex's modern, type-safe pointer wrapper. It replaces the pattern of raw *T pointers with as cast chains, providing a clean method-based API with zero runtime overhead.
Prelude Type
Ptr<T> is a prelude type — available in all Vex programs without any import. Just use it directly.
Overview
fn main() {
// Allocate 10 integers
let! p = Ptr.allocN<i32>(10);
// Write at specific indices
p.writeAt(0, 100);
p.writeAt(1, 200);
p.writeAt(2, 300);
// Read
let val = p.readAt(1); // 200
$println("Value: {}", val);
// Done
p.free();
}Compare with the old C-style approach:
// Old way — unsafe, casts everywhere
let raw = alloc(40) as *i32!;
unsafe { *raw = 100; *(raw + 1) = 200; *(raw + 2) = 300; }
let val = unsafe { *(raw + 1) };
free(raw as *void);Struct Definition
struct Ptr<T> {
raw: *T
}Ptr<T> is a single-field struct wrapping a raw pointer. After optimization, it has exactly the same memory layout and performance as *T.
Creating Pointers
From Allocation
// Allocate a single element (uninitialized)
let! p = Ptr.alloc<i32>();
p.write(42);
// Allocate and initialize with a value
let! p = Ptr.allocWith<i64>(9999);
// Allocate N contiguous elements
let! arr = Ptr.allocN<f64>(1000);From Raw Pointers
Interop with existing raw pointer code:
let x = 42;
let raw: *i32 = &x;
// Raw → Ptr
let p = Ptr.of<i32>(raw);
let val = p.read(); // 42
// Ptr → Raw (for FFI, legacy code)
let back: *i32 = p.asRaw();Special Values
let null = Ptr.null<i32>(); // null pointer
null.isNull(); // true
null.isValid(); // falseReading and Writing
Single Element
let! p = Ptr.alloc<i32>();
// Write
p.write(42);
// Read
let val = p.read(); // 42Indexed Access
For arrays allocated with allocN:
let! arr = Ptr.allocN<i32>(5);
// Write at index
arr.writeAt(0, 10);
arr.writeAt(1, 20);
arr.writeAt(2, 30);
// Read at index
let v = arr.readAt(1); // 20Pointer Arithmetic
All arithmetic is element-level — p.add(3) advances by 3 elements (not 3 bytes):
let! p = Ptr.allocN<i32>(10);
p.writeAt(0, 100);
p.writeAt(5, 500);
let p5 = p.add(5); // p + 5*sizeof(i32)
let val = p5.read(); // 500
// Signed offset (can go backward)
let back = p5.offset(-2); // p + 3 elements
// Subtract
let p3 = p5.sub(2); // same as offset(-2)Distance Between Pointers
let! start = Ptr.allocN<i32>(100);
let p50 = start.add(50);
let dist = start.distanceTo(&p50); // 50 (elements, not bytes)Type Casting
Cast between pointer types via asOpaque():
let! ip = Ptr.alloc<i64>();
ip.write(0x41424344);
// Cast to Ptr<u8> via opaque pointer
let bp = Ptr.of<u8>(ip.asOpaque() as *u8);
let firstByte = bp.read(); // 0x44 (little-endian)Bulk Operations
let! src = Ptr.allocN<i32>(3);
src.writeAt(0, 100);
src.writeAt(1, 200);
src.writeAt(2, 300);
let! dst = Ptr.allocN<i32>(3);
// Copy N elements from src to dst (two directions)
dst.copyFrom(&src, 3); // dst ← src
src.copyTo(&dst, 3); // src → dst (same result)
// Compare two memory blocks (byte-level, like memcmp)
let cmp = src.compare(&dst, 3); // 0 = equal
// Fill bytes (like memset) — e.g., zero-initialize 12 bytes
dst.writeBytes(0, 12); // fills 12 bytes with 0x00
// Swap values at two pointer locations
let! a = Ptr.allocWith<i32>(111);
let! b = Ptr.allocWith<i32>(222);
a.swap(&b);
// a.read() == 222, b.read() == 111Reference Conversion
Convert a Ptr<T> into Vex's reference system:
let! x: i32 = 42;
let p = Ptr.of<i32>(&x as *i32);
// Immutable reference
let r: &i32 = p.asRef();
$println(*r); // 42
// Mutable reference
let mr: &i32! = p.asMut();
*mr = 100; // x is now 100WARNING
asRef() and asMut() require the pointer to be valid and non-null. Using them on a null or freed pointer is undefined behavior.
Alignment Check
Required for SIMD operations and specialized allocators:
let! p = Ptr.allocN<i32>(64);
if p.isAligned(16) {
// Safe for SSE/NEON operations
$println("16-byte aligned");
}
if p.isAligned(32) {
// Safe for AVX operations
$println("32-byte aligned");
}Memory Management
let! p = Ptr.alloc<i32>();
p.write(42);
p.free(); // deallocates and sets to null
// After free:
p.isNull(); // trueWARNING
free() deallocates the entire allocation, not just one element. Call it on the pointer returned by alloc() / allocN(), not on an offset pointer.
FFI Interop
Ptr<T> works seamlessly with C FFI:
extern "C" {
fn fread(buf: ptr, size: u64, count: u64, stream: ptr): u64;
}
fn readBytes(stream: ptr, count: usize): Ptr<u8> {
let! buf = Ptr.allocN<u8>(count);
fread(buf.asOpaque(), 1, count as u64, stream);
return buf;
}Method Reference
Constructors
| Method | Description |
|---|---|
Ptr.null<T>() | Null pointer |
Ptr.of<T>(p) | Wrap raw *T pointer |
Ptr.alloc<T>() | Allocate single element |
Ptr.allocWith<T>(val) | Allocate + initialize |
Ptr.allocN<T>(n) | Allocate N elements |
Core Operations
| Method | Description |
|---|---|
.read() | Read value at pointer |
.write(val) | Write value at pointer |
.readAt(idx) | Read at element index |
.writeAt(idx, val) | Write at element index |
Arithmetic
| Method | Description |
|---|---|
.add(n) | Forward N elements |
.sub(n) | Backward N elements |
.offset(n) | Signed element offset |
.distanceTo(&other) | Element distance |
Conversion & Checks
| Method | Description |
|---|---|
.asRaw() | Get underlying *T |
.asOpaque() | Get as ptr (for FFI) |
.addr() | Get address as i64 |
.isNull() | Check if null |
.isValid() | Check if non-null |
.asRef() | Convert to immutable reference &T |
.asMut() | Convert to mutable reference &T! |
.isAligned(n) | Check n-byte alignment |
Memory
| Method | Description |
|---|---|
.copyFrom(&src, n) | Copy N elements from src |
.copyTo(&dest, n) | Copy N elements to dest |
.compare(&other, n) | Compare N elements (memcmp) |
.writeBytes(val, n) | Fill n bytes with val (memset) |
.swap(&other) | Swap values at two locations |
.free() | Deallocate and null-out |
See Also
- RawBuf — Zero-cost byte-level memory accessor
- Span<T> — Bounds-checked fat pointer built on Ptr<T>
- Raw Pointers — Legacy raw
*Tdocumentation - VUMM — Automatic ownership with
Box<T>