v0.4.0

Pointer arithmetic

Adding an integer to a pointer moves it by whole elements, not bytes. If p is a *T, then p + 1 is the address of the next T — sizeof(T) bytes further on — whatever T is. This is how a program walks memory that has no length attached: an allocation, a buffer from a foreign function, the elements behind a view's .data.

ExpressionTypeMeaning
p + nthe type of pn elements after p
n + pthe type of pthe same as p + n
p - nthe type of pn elements before p
p += nmoves the var pointer p forward n elements
p -= nmoves it back n elements
p[i]the pointee, a placethe element i elements after p: *(p + i)

n may have any integer type. The result keeps the pointer's writability: an offset *var T is still a *var T. Every form in the table — the arithmetic and the indexing alike — needs an unsafe context: pointer arithmetic requires 'unsafe' and indexing a raw pointer requires 'unsafe'.

var values: int64[4] = [10, 20, 30, 40];
let first = @values[0];
// Safety: every offset stays inside values.
unsafe {
    let third = first + 2;        // 16 bytes on: sizeof(int64) is 8

    let a = *third;               // 30
    let b = *(third - 1);         // 20
    let c = first[3];             // 40
    let d = third[-1];            // 20: an index may be negative
    first[1] = 25;                // values[1] is now 25
}

Indexing and arithmetic agree — @p[i] is the same address as p + i — and both scale by the pointee's size, including a struct's size with its padding. See Layout.

* binds more tightly than +, so the parentheses in *(p + 2) matter: *p + 2 reads the first element and adds 2 to it. p[2] says the same as *(p + 2) with no parentheses.

Nothing is checked

A pointer carries no length, so neither arithmetic nor p[i] is bounds-checked. An offset past the end of the memory a pointer addresses produces an address that is not one, and reading or writing through it is undefined: the program may crash, or may silently read or overwrite something else. The caller vouches for every offset, which is what the unsafe block around it, and the // Safety: comment above it, record. When the count is known, build a view, p[..n], whose indexing is checked.

Walking a buffer

Pointers order with <, <=, > and >=, so a loop can walk from a start pointer to an end pointer one past the last element. += steps the cursor; there is no ++ or -- on a pointer:

func Main() -> int {
    var values: int64[4] = [10, 20, 30, 40];
    let start = @values[0];
    // Safety: one past the last element is a valid end, and is never read.
    let end = unsafe { start + values.length };
    var cursor = start;
    var sum: int64 = 0;
    while cursor < end {
        // Safety: cursor is below end, so it addresses an element of values.
        unsafe {
            sum += *cursor;
            cursor += 1;
        }
    }
    PrintLine("{}", sum);   // 100
    return 0;
}

Compare with <, not <=: the end pointer marks where to stop, and reading it reads past the array.

Pointer subtraction

Subtracting one pointer from another is not defined:

error: operator '-' cannot combine left operand '*var int64' with right operand '*var int64'

To count the elements between two pointers into the same block, convert both to addresses — which is safe — and divide the distance in bytes by the element size:

let count = ((end as uint) - (start as uint)) / sizeof(int64);   // 4

*opaque

A *opaque has no element type, so arithmetic on it moves by single bytes: block + 8 is eight bytes on. To step by elements, cast it to a typed pointer first. Indexing an *opaque yields no usable value; cast it before reading or writing.

See also