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:
| Operator | Receiver | Other parameters | Result |
|---|---|---|---|
[] | self: &T | the index | the element |
[]= | self: &var T | the index, then the value | none |
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 Grid | grid[i] | grid[i] = x |
|---|---|---|
[] only | reads | cannot assign through the '[]' operator on 'Grid' |
[]= only | type 'Grid' cannot be indexed | writes |
| both | reads | writes |
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 Cells | Error | Write instead |
|---|---|---|
grid[i] += x, grid[i]++ | operator '+=' cannot read and write through the '[]' operator on 'Grid' at once | grid[i] = grid[i] + x |
grid[i].mark = x | cannot 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
Operators
A type overloads a binary operator by declaring a function named after it. Comparisons derive from == and <, and structures compare field by field.
Iteration
for walks arrays, slices and ranges directly, and any other type through Next(self: &var T) -> Item? or an Iterate method that hands out an iterator.