Indexers

Arrays, slices and pointers are indexed by the language. Any other type indexes itself by declaring the indexing operators in an extend block, the same way it declares == or +: [] reads one element and []= writes one.

Syntax

extend T {
    func [](self: &T, index: I) -> E { … }
    func []=(self: &var T, index: I, value: E) { … }
}

[] and []= are written with no space inside the brackets and none before the =. v[i] on such a type is a call to [], and v[i] = x is a call to []=.

struct Vect {
    data: int32[10];
}

extend Vect {
    func [](self: &Vect, index: uint) -> int32 {
        return self.data[index];
    }

    func []=(self: &var Vect, index: uint, value: int32) {
        self.data[index] = value;
    }

    func [](self: &Vect, span: int..int) -> int32[..] {
        return self.data[span];
    }
}

func Main() -> int {
    var v = Vect { data: [0; 10] };
    v[2] = 7;
    v[3] = v[2] + 1;
    let middle = v[2..5];
    PrintLine("{} {} {}", v[2], v[3], middle.length);
    return 0;
}

Shape

Each operator has exactly one shape, checked where it is declared:

OperatorReceiverOther parametersResult
[]self: &Tthe indexthe element
[]=self: &var Tthe index, then the valuenone

Neither takes type parameters, a variadic or a default value. Anything else is refused with the expected shape:

error: the '[]' operator on 'Bad' must have signature 'func [](self: &Bad, index: I) -> E'
  note: an index reads one element and evaluates to it
  help: the index and element types are the author's to choose; overloads are separated by the index type

Index types

Nothing fixes the index type: an integer, a range, an enum, a tuple, a structure. Overloads of either operator are separated by their index type, so one type may answer several index forms — v[i] and v[a..b] above. An index that no overload accepts is an error: no '[]' on 'Vect' accepts an index of type 'char8[..]'. An unsuffixed literal index takes the parameter's type, as in any call.

A generic type substitutes its type arguments into the operators, so func [](self: &Box<T>, index: uint) -> T returns the element type of each instantiation.

Reading and writing are separate

The two operators are independent. A type may declare only [] and be read but not written, only []= and be written but not read, or both:

Declared on Gridgrid[i]grid[i] = x
[] onlyreadscannot assign through the '[]' operator on 'Grid'
[]= onlytype 'Grid' cannot be indexedwrites
bothreadswrites

Declaring both does not make the pair act as one place. [] returns a value, not a place — a reference cannot be returned — so:

Rejected, for a Grid of CellsErrorWrite instead
grid[i] += x, grid[i]++operator '+=' cannot read and write through the '[]' operator on 'Grid' at oncegrid[i] = grid[i] + x
grid[i].mark = xcannot assign through the '[]' operator on 'Grid'read, change, write back
let r: &Cell = grid[i];cannot assign 'Cell' to '&Cell'a copy: let c = grid[i];

Combining the read and the write would evaluate the receiver and the index twice, so the language leaves the two steps to be written out.

Borrowing

Either call borrows the receiver whole for its duration, exactly as a method call does. []= needs a receiver it may write, so a let binding or a &T access is refused — cannot modify immutable variable 'fixed' — while [] works on either. The value written by v[i] = x reaches []= as an ordinary by-value argument, so it is copied, or with v[i] <- x moved. A read never counts as a partial move: its result is a fresh value.

In v[keys[j]] = x, only the outer subscript writes; keys[j] is a read.

Built-in indexing stays built in

Arrays, slices and pointers keep their own indexing, which no extend block can displace. Built-in indexing of an array or slice is bounds-checked on every target and in every build profile; see Arrays. A declared indexer checks whatever its body checks.

See also