Memory Management

Oxynium does not have a garbage collector or reference counting. Memory management is manual for heap-allocated values, but most programs never need to think about this — Str, List<T>, and other standard types manage their own memory internally.

The only explicit memory primitive is Ptr<T>.

Ptr<T>

primitive Ptr<T> — an unmanaged pointer to a heap-allocated value of type T. The programmer is fully responsible for memory.

Creating a pointer

let p = Ptr.make!<Int>(42)

This allocates 8 bytes on the heap, stores 42, and returns a pointer to that memory.

Methods

MethodSignatureDescription
makestatic <From>(val: From) Ptr<From>Allocate and store a value
unwrap(self) TDereference the pointer; no null check
is_null(self) BoolTrue if the address is 0 (null pointer)
Str(self) Str"Ptr(<address>)"
let p = Ptr.make!<Int>(42)
print(p.is_null().Str())    // false
print(p.unwrap().Str())     // 42

Null pointers

def main() {
    let null_ptr = #unchecked_cast(Ptr<Int>, 0)
    print(null_ptr.is_null().Str())    // true
    // calling null_ptr.unwrap() causes undefined behaviour
}

Safety

Ptr<T> bypasses the type system entirely. Dereferencing a null or invalid pointer is undefined behaviour. Prefer Option<T> or Result<T, E> for safe optional/fallible values. Use Ptr<T> only when interfacing with memory, system calls, or external C functions.

Unsafe type casting (#unchecked_cast)

#unchecked_cast(TargetType, expression)

The bits of expression are reinterpreted directly as TargetType. No conversion occurs. Equivalent to a C-style transmute.

Common uses:

#unchecked_cast(Ptr<Int>, 0)    // create a null pointer
#unchecked_cast(Int, my_ptr)    // pointer address as integer
#unchecked_cast(Int, 'A')       // 65

Completely unsafe. Misuse causes undefined behaviour. Prefer the safe conversion methods (Int.Bool(), Char.Int(), etc.) where possible.

Inline assembly (#asm)

For complete control over generated code, #asm embeds raw x86-64 NASM assembly:

#asm ReturnType "assembly string"

Must appear inside a function body. The assembly string must be a compile-time literal. For non-Void return types, the assembly must push the result onto the stack.

Parameters are available on the stack relative to rbp:

  • 1st parameter: [rbp + 16]
  • 2nd parameter: [rbp + 24]
  • nth parameter: [rbp + (8 + n*8)]
def asm_passthrough(arg: Str) Str {
    return #asm Str "
        push qword [rbp + 16]
    "
}
print(asm_passthrough("hi"))    // hi

Using #asm makes your code non-portable to architectures other than x86-64.