Pointers

A raw pointer holds the address of a value. *T is a read-only pointer: the value it points at can be read through it. *var T is a writable pointer: the value can also be written. A pointer may be null, may be stored in a field, returned, offset and compared, and may outlive what it points at — the compiler checks only what the pointer type allows, never whether the address is still good. That is the price of the things only a pointer can do: foreign functions, manual allocation, stored links between values, and walking memory by address.

pointer-type = "*" type
             | "*" "var" type
address-of   = "@" place
dereference  = "*" expression
var score = 10;
let writer = @score;        // *var int: score is a var
*writer += 5;               // score is now 15
let reader: *int = @score;  // a read-only view of the same int
let current = *reader;      // 15

For safe, checked access to a value someone else owns, use a reference instead; a pointer is the deliberate step outside those checks.

Taking an address

The prefix @ takes the address of a place: a variable, a field, an element, or the target of another pointer. @x.field and @a[i] address one part of a value. The pointer's writability comes from the place:

Place@place has type
a var binding, or a part of one*var T
a let binding, or a part of one*T
an element of a var T[..] view*var T
an element of a T[..] view*T
a field or element reached through a *var T*var T
error: cannot assign '*int' to '*var int': '@limit' yields a read-only '*T'; declare 'limit' with 'var' for a '*var T'

& never takes an address in Rux: &x is rejected with '&' does not take an address; write '@' to take the address of a value. A pointer is made only from an address, so a plain value where a pointer is expected is the error cannot assign 'int' to '*int'.

Taking the writable address of a variable declared without a value counts as initialising it, because the address exists to be filled through: var info: SystemInfo; GetSystemInfo(@info); leaves info usable. See Initialisation.

Reading and writing through a pointer

The prefix * dereferences a pointer: *p is the value it points at, a place that can be read, and written when p is a *var T:

func Store(target: *var int, value: int) {
    *target = value;
}
error: cannot modify data through read-only pointer '*int'

The pointee type, not the variable it points at, decides what is allowed: a *int cannot write even when it points at a var.

A write through a pointer initialises the storage it addresses and destroys nothing. The pointer cannot tell whether that storage holds a value yet, so *p = value, *p <- value, p[i] = value and a field write through a *var T never run a destructor for what was there before. Code that replaces a value through a pointer destroys or moves out the old one first:

struct Noisy {
    id: int;
}

extend Noisy {
    func ~Noisy(self: &var Noisy) {
        PrintLine("destroy {}", self.id);
    }
}

func Main() -> int {
    var slot = Noisy { id: 1 };
    slot = Noisy { id: 2 };       // assignment: destroys 1
    let p = @slot;
    *p = Noisy { id: 3 };         // through a pointer: 2 is never destroyed
    return 0;                     // destroys 3
}
destroy 1
destroy 3

Moving a value out through a pointer is rejected, because the pointer does not own it: let mine <- *p; fails with cannot move '*p' out of borrowed pointer storage.

Fields, elements and methods

. reaches through a pointer to a struct, and [i] through a pointer to an element; there is no arrow operator. Reads and writes go straight to the pointed-at value:

struct Point {
    x: int;
    y: int;
}

func Main() -> int {
    var point = Point { x: 1, y: 2 };
    let p = @point;
    p.x = 7;                 // point.x is now 7
    let sum = p.x + p.y;
    return 0;
}

A method is not called through a raw pointer, because its receiver is a reference and a pointer proves nothing about the address it holds. Dereference first, (*p).Shift(3), to vouch for it explicitly:

error: cannot create safe receiver '&var Point' from raw pointer '*var Point'
  note: raw pointers do not prove a valid non-null borrow
  help: call the method on an owning value

Pointers nest: **T is a pointer to a pointer, and each * removes one level, so with pp: **var int, **pp = 1 writes the int.

Conversions

FromToHow
*var T*Timplicitly
*T*opaqueimplicitly; *var T likewise to *var opaque
*opaque*Tas *T; likewise as *var T
*T*Uas *U
*T*var Unever, for any U but opaque
a pointeruintas uint — the address as a number
uinta pointeras *T

Losing write access is always allowed; gaining it is not: cannot assign '*int' to '*var int'. A cast with as is the explicit, unchecked way to retype an address — see Casts — but it cannot add write access either, whatever pointee it names, so @x as *var int and @x as *var uint64 on a let x are refused. Untyped memory is the one way through: writable memory reached through an *opaque, such as a fresh allocation, is retyped with as *var T, and a *T may become a *var opaque for a C function that takes a void * it only reads:

error: cannot cast '*int' to '*var uint64', which would give a read-only pointer write access

null

null is the pointer that points at nothing. It converts to every pointer type, and it is the only value a pointer has that is not an address. Compare with == and != before dereferencing a pointer that might be null:

struct Node {
    value: int;
    next: *Node;
}

func Main() -> int {
    var tail = Node { value: 3, next: null };
    var head = Node { value: 1, next: @tail };
    var cursor: *Node = @head;
    while cursor != null {
        PrintLine("{}", cursor.value);
        cursor = cursor.next;
    }
    return 0;
}

Reading or writing through null is not checked: it compiles, and crashes the program when it runs.

null is a pointer value, not absence. It is not a value of an optional type, and where an optional of a pointer is expected it is a present null pointer:

TypeMeaning
(*T)?an optional pointer: none, or a pointer
*T?a pointer to an optional, *(T?)
let present: (*int)? = null;   // present, holding a null pointer
let absent: (*int)? = none;    // absent

*opaque

*opaque and *var opaque point at bytes of no particular type, as C's void * does. Allocation functions return them, and foreign functions take and return them. Any typed pointer converts to one implicitly; to use the memory, cast it back to a typed pointer:

import Memory::{ Alloc, Free };

func Main() -> int {
    let block = Alloc(4 * sizeof(int)) as *var int;
    if block == null {
        return 1;
    }
    defer Free(block);
    block[0] = 1;
    return 0;
}

Arithmetic on an *opaque moves by single bytes.

Comparison

Two pointers compare with == and !=, which compare addresses, and with <, <=, > and >=, which order them — useful when walking a buffer up to an end pointer. See Pointer arithmetic.

What a pointer does not have

A pointer has no length: p.length is type '*var int' has no field 'length'. It cannot be iterated with for, and slicing it needs an explicit end. To treat raw memory as a sequence, build a view of it with a count, p[..n], and iterate that, for value in p[..n]:

error: cannot iterate over pointer '*int32'
  help: iterate a slice of the memory it points at, as in 'for value in p[..count]'

A pointer is 8 bytes on every supported target, whatever it points at.

See also