v0.4.0

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. Because the compiler cannot check what a pointer addresses, every operation that reaches through one is written inside an unsafe block.

pointer-type = "*" type
             | "*" "var" type
address-of   = "@" place
dereference  = "*" expression
unsafe-block = "unsafe" block
var score = 10;
let writer = @score;        // *var int: score is a var
let reader: *int = @score;  // a read-only view of the same int
// Safety: both pointers address score, which is live for the whole block.
unsafe {
    *writer += 5;           // score is now 15
    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. Taking an address is safe; using it is not. 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:

unsafe 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 = move 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(&var self) {
        PrintLine("destroy {}", self.id);
    }
}

func Main() -> int {
    var slot = Noisy { id: 1 };
    slot = Noisy { id: 2 };       // assignment: destroys 1
    let p = @slot;
    // Safety: p addresses slot, which is live.
    unsafe {
        *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 = move *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, inside unsafe:

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

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

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

error: cannot call method 'Shift', whose receiver is '&var Point', through raw pointer '*var Point'
  note: a raw pointer does not prove a valid, non-null borrow, so it is not borrowed implicitly
  help: dereference it where it is known to be valid: '(*p).Shift(...)'

A method that works on addresses declares a raw receiver, *self or *var self, and is called on the pointer itself; a field it reads through self needs unsafe like any other.

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

FromToHowNeeds unsafe
*var T*Timplicitlyno
*T*opaqueimplicitly; *var T likewise to *var opaqueno
*opaque*Tas *T; likewise as *var Tyes
*T*Uas *Uyes
*T*var Unever, for any U but opaque—
a pointeruintas uint — the address as a numberno
uinta pointeras *Tyes

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. A cast that gives an address a type it did not have needs an unsafe context; one that only forgets what a pointer says is safe:

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

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 != — which are safe — 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 {
        // Safety: cursor is not null and addresses a live Node of this list.
        unsafe {
            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));
    if block == null {
        return 1;
    }
    // Safety: block was allocated above and is freed once, when Main returns.
    defer unsafe { Free(block); }
    // Safety: block holds room for four ints.
    unsafe {
        let values = block as *var int;
        values[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. Comparing pointers is safe. See Pointer arithmetic.

What a pointer does not have

A pointer has no length: p.length is an error. 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.

Unsafe code

The compiler checks references, bounds, ownership and borrows, but it cannot check what a raw pointer addresses, what a foreign function does, or what an unsafe func expects of its caller. The operations that depend on such facts are allowed only in an unsafe context — an unsafe { … } block or the body of an unsafe func — so every place a program's soundness rests on a promise can be found by searching for unsafe.

Needs an unsafe contextSafe
*p, *p = v, p[i], a range p[a..b], a field p.x through *T or *var T, and @p.x or @p[i]taking an address with @
p + n, n + p, p - n, p += n, p -= ncopying, comparing and null-testing pointers
a cast that gives an address a new type: an integer to a pointer, *opaque to *T, *A to *Ba cast that only forgets: *var T to *T, or to *opaque
calling an extern function, an asm function or an unsafe func, or naming one as a valueconverting a pointer to an integer
reading or writing an extern variable@ of an extern variable
making an interface handle from a pointer, p as Shapeborrowing a value as an interface view, &Shape

Each such operation outside an unsafe context is an error that names it:

error: dereferencing a raw pointer requires 'unsafe'
  note: the compiler cannot prove that a raw pointer addresses a live value of its type
  help: wrap it in 'unsafe { ... }' and say why it holds in a '// Safety:' comment above it

The others read indexing a raw pointer requires 'unsafe', accessing field 'next' through a raw pointer requires 'unsafe', pointer arithmetic requires 'unsafe', casting '*opaque' to '*var Node' requires 'unsafe', call to unsafe function 'Copy' requires 'unsafe', call to extern function 'write' requires 'unsafe' and making interface value 'Shape' from pointer '*var Square' requires 'unsafe'.

An unsafe block only grants permission; nothing about the program's behaviour changes. In value position it yields its last expression written without a ;, so a single read is let first = unsafe { *p };. At the start of a statement it is the statement, ends at its } and yields nothing, so a value it ends with is an error (this 'unsafe' block yields a value of type 'int' that is not used). Its locals are destroyed at its end, return, break and continue leave it as they leave any block, and defer unsafe { … } defers it. A block that holds no operation needing it, or one inside another unsafe context, is the warning unnecessary 'unsafe' block.

unsafe func declares a function whose caller carries an obligation the compiler cannot check, such as Memory::Copy, whose ranges must be valid for the length it is given. A call to it needs an unsafe context, and its body is one. The keyword stands directly before func, as in pub unsafe func, also for a method. An extern or asm function is always unsafe to call, so unsafe on one is an error, and a destructor, an operator and an interface requirement or its implementation cannot be unsafe, because nothing marks where they are called: destructor '~Holder' cannot be 'unsafe', because nothing marks where it is called. A function whose safety does not depend on its caller stays safe and keeps its raw operations in unsafe blocks inside its body.

rux lint asks for the reason: its rule unsafe.comment warns about an unsafe block unless the comment directly above the line holding it has a line starting Safety:, and about an unsafe func unless one does above its declaration — usually a /// Safety: line of its documentation, saying what a caller must uphold. The comment says why the operation is sound at that place, as the examples on this page do.

See also