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.

var values: int64[4] = [10, 20, 30, 40];
let first = @values[0];
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. 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];
    let end = start + values.length;   // one past the last element: never read
    var cursor = start;
    var sum: int64 = 0;
    while cursor < end {
        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 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