Raw pointers

A pointer stores a native memory address. Xenon keeps pointer operations explicit in both types and expressions.

Pointer types

Add * to a type to form a pointer. Pointer types may be nested, and void* represents an untyped memory address.

int* number;
byte** byteTable;
void* memory;

Address and dereference

The address-of operator & obtains a pointer to an addressable value. The dereference operator * reads or writes the pointed-to value.

int value = 10;
int* pointer = &value;

int copy = *pointer;
*pointer = 42;

Null and readonly data

null represents an empty pointer value. A readonly T* pointer provides read-only access to the pointed-to data.

byte* output = null;

byte First(readonly byte* input)
{
    return *input;
}

The restriction applies to access through this pointer, not to reassignment of the pointer variable. Use T* readonly for a fixed pointer to mutable data, or readonly T* readonly to restrict both. See the four pointer forms for examples.

const is reserved for compile-time constant declarations: const T* and const T& are invalid.

Raw storage and object lifetimes

A raw pointer is always non-owning. It carries an address and pointee access, but no allocation origin, element count, automatic cleanup, or runtime Initialized/Empty metadata. The program remains responsible for knowing which objects are live and which allocator owns the storage.

A mutable T* nevertheless has direct lifetime authority over its addressed storage. Assignment directly to *pointer or pointer[index] is placement construction: it starts a lifetime and does not first destroy whatever bytes were there. destruct ends the object lifetime without releasing storage, and move transfers the value out. Placement does not propagate into members: pointer->Field, (*pointer).Field, pointer[index].Field, and nested member paths refer to fields of an already-live object and use normal replacement semantics, including destruction or release of the previous field value.

Resource* slots = cast<Resource*>(malloc(
    sizeof(Resource) * cast<nuint>(2), alignof(Resource)));

slots[0] = Resource();       // begins a lifetime; no prior destructor
Resource value = move slots[0];
slots[0] = Resource();       // address can host a new lifetime
destruct(slots[0]);       // ends it, storage remains
free(slots);              // releases bytes only

Because raw storage has no hidden lifetime state, an invalid double construction, double destruction, use after move, or deallocation with live objects is not repaired by runtime metadata. Before overwriting a live non-trivial value, call destruct yourself. To destroy and release a typed allocation in one operation, use delete.

A readonly T* grants access only. Placement assignment, move, destruct, and delete through it are rejected. Storage-only free accepts typed and untyped pointers, including readonly pointees, but does not make releasing live objects correct.

Typed and untyped pointer casts

Explicit cast converts T* to void* and void* back to a typed pointer without changing the address. It may add readonly access but cannot remove it. Arbitrary casts between unrelated typed pointers remain rejected; use void* as the deliberate untyped boundary.

void* raw = malloc(sizeof(Resource), alignof(Resource));
Resource* resource = cast<Resource*>(raw);
// readonly Resource* input = ...;
// Resource* writable = cast<Resource*>(input); // invalid: removes readonly

Function pointers

function R(P...)* is a raw native code pointer whose return and parameter types are part of its identity. Taking a function address is explicit, and the pointer is called with ordinary call syntax.

namespace Example;

int Add(int left, int right)
{
    return left + right;
}

int Main()
{
    function int(int, int)* callback = &Add;
    int result = callback(20, 22);
    return result;
}

There is no implicit function-to-pointer conversion: write &Add, not Add. Signatures must match exactly; ordinary numeric conversions do not make two callback signatures compatible. Free functions and static methods have addresses. Bound instance methods are not function pointers because they would require a hidden receiver.

A function pointer may be null and may be stored in a struct field. C-compatible structs containing callbacks may cross extern/export either by pointer or by value, and callback signatures may themselves use C-compatible structs by value. Because a raw function pointer always uses the native C ABI, this recursive compatibility rule applies wherever its type is declared, including locals and fields; arrays, ownership wrappers, atomic storage and non-C-compatible structs cannot appear directly in its signature. It carries no ownership, captured state, reference count or automatic lifetime management. Calling a null function pointer follows the same invalid-pointer policy as other raw pointer operations.

Members and indexing

Use -> for member access through a struct pointer. Indexing computes the address of an element and returns the value stored there.

Player* player = GetPlayer();
player->Health -= 10;

int first = values[0];
values[1] = 22;

Raw pointers do not carry array lengths. Built-in array metadata and bounds checks apply to array values, not to arbitrary pointer indexing.

Base pointers and references

Implicit Derived* to Base* conversion preserves the numeric address, including a null pointer. Binding a derived value to Base& addresses the same offset-zero base subobject. No pointer adjustment is needed, even if only the derived type introduces interface or virtual dispatch.

Derived value = Derived();
Base* pointer = &value;
Base& reference = value;
pointer->Value = 42; // writes value's Base subobject

Pointer equality still requires matching pointer types; the address guarantee does not introduce mixed-type comparisons or pointer-to-integer casts. cast<T> does not provide struct pointer downcasts, and derived-to-base value slicing is not supported.

Indexing a Base* uses sizeof(Base). A pointer to the base subobject of one derived object does not turn a Derived[] into a Base[]: use the actual element type to traverse an array of derived objects. Standalone Base[] stride remains unchanged when descendants are added.

See stable inheritance layout for storage and ABI rules. Destruction through a base pointer selects derived cleanup only when the base contract has a virtual destructor.