Slices
A slice is a view of a run of contiguous elements that belong to something else — an array, an allocation, the text of a string literal. It holds two things: where the first element is, and how many elements there are. It owns none of them, so copying a slice copies only those two words, whatever its length.
There are two slice types:
| Type | Name | Through the view you may |
|---|---|---|
T[..] | read-only slice | read the elements |
var T[..] | writable slice | read and write the elements |
slice-type = [ "var" ] type "[" ".." "]"
slicing = expression "[" range-expression "]"
The .. reads as "some number of": the element type is fixed, the count is carried in the value. One function taking an int32[..] therefore accepts every length.
func Total(values: int32[..]) -> int32 {
var sum: int32 = 0;
for value in values {
sum += value;
}
return sum;
}
Making a slice
| From | Written | Gives |
|---|---|---|
| an array, implicitly | Total(numbers) | T[..] of the whole array |
| an array, with a range | numbers[..], numbers[1..4] | var T[..] from a writable array place, T[..] otherwise |
| another slice, with a range | view[1..] | the same writability as view |
| a pointer and a count | p[..n], p[a..b] | var T[..] from *var T, T[..] from *T |
| a string literal | "hello" | char8[..], always read-only |
| an array literal | let v: int32[..] = [1, 2, 3]; | a view of the literal's elements |
| the empty literal | let e: var int32[..] = []; | a view of nothing, with null data |
A string literal is a char8[..] — or a char16[..] or char32[..] with a c16 or c32 prefix — over text stored in a read-only section of the program. Its .length counts code units, not characters. See Text.
Slicing
Indexing an array, a slice or a pointer with a range produces a slice of part of it. An omitted start means the first element, an omitted end the last:
| Expression | Elements of [10, 20, 30, 40, 50] | Meaning |
|---|---|---|
numbers[1..4] | 20, 30, 40 | indexes 1 up to, not including, 4 |
numbers[1..=3] | 20, 30, 40 | indexes 1 to 3 inclusive |
numbers[2..] | 30, 40, 50 | from index 2 to the end |
numbers[..2] | 10, 20 | from the start up to 2 |
numbers[..=1] | 10, 20 | from the start to 1 inclusive |
numbers[..] | all five | the whole sequence |
A range held in a variable slices the same way: with let r: int..int = 1..4;, numbers[r] is numbers[1..4]. A slice of a slice counts from the start of the view, not of the storage beneath it:
let numbers: int32[5] = [10, 20, 30, 40, 50];
let middle = numbers[1..4]; // 20, 30, 40
let inner = middle[1..]; // 30, 40
Nothing is copied: every one of these views points into numbers.
A range subscript is checked like an index. s[start..end] needs start <= end <= s.length, and an inclusive end must be below the length. Bounds written as literals that run backwards are rejected while compiling, and so is a constant bound past the end of a fixed array, such as numbers[1..9] on an int32[5]; anything else that falls outside the sequence stops the program:
error: range start cannot be greater than its end
error: slice end 9 is out of range for an array of 5 elements
Panic: index out of range
Members
| Member | Type | Meaning |
|---|---|---|
.length | uint64 | the number of elements |
.data | *T for T[..], *var T for var T[..] | the address of the first element |
.data is a raw pointer, for code that has to leave the language's checks behind — handing a buffer to a foreign function, for one. The data of an empty slice may be null.
Indexing and iteration
s[i] is the element at position i of the view, counting from zero, with an index of any integer type. It is checked against .length exactly as an array index is, and one past the end stops the program with Panic: index out of range. for walks the elements in order:
func Largest(values: int32[..]) -> int32 {
var best = values[0];
for value in values {
if value > best {
best = value;
}
}
return best;
}
A for loop visits exactly .length elements of the view it was given.
Spreading a slice with ... passes its elements one by one to a variadic parameter: with func Sum(args: int32...), the call Sum(values...) gives Sum every element of values.
Writability belongs to the view
Whether elements may be written depends on the view's type, never on the binding that holds it:
func Fill(values: var int32[..], value: int32) {
for i in 0..values.length {
values[i] = value;
}
}
func Main() -> int {
var storage: int32[4] = [1, 2, 3, 4];
let writable = storage[..]; // var int32[..]: the array place is writable
writable[0] = 10; // a 'let' binding, but a writable view
var cursor: int32[..] = storage;
cursor = storage[2..]; // a 'var' binding may be pointed elsewhere
Fill(storage[1..3], 0); // a writable view of part of the array
return 0;
}
The binding decides whether the variable can be pointed at another view; the view decides whether the elements can change. So a var binding of a read-only view still cannot write an element, and a let binding of a writable view cannot be repointed:
error: cannot modify elements through read-only slice 'int32[..]'
help: use a 'var T[..]' view to write through the sequence
error: cannot modify immutable variable 'view'
help: declare 'view' with 'var' to make it mutable
Writability flows in one direction:
- A
var T[..]converts implicitly to aT[..]of the same elements, wherever a read-only view is expected. The reverse is rejected:cannot assign 'int32[..]' to 'var int32[..]'. - A view of an array is writable only when the array place is. With
let fixed: int32[3] = [1, 2, 3];,fixed[..]is anint32[..], andFill(fixed[..], 0)fails withargument 1 to 'Fill' has type 'int32[..]', but parameter 'values' requires 'var int32[..]'. - A sub-slice keeps its parent's writability, and a view of a pointer takes the pointer's.
- A string literal's bytes are read-only, always:
error: cannot assign string literal to 'var char8[..]' because literal text is stored in a read-only section
A writable parameter is given an explicit view, storage[..]; an array passed on its own only becomes a read-only one.
A method that takes a var T[..] must be given a view, too. An array passed on its own is reported at the call, with the candidate it missed:
error: no matching overload for method 'Fill' on type 'Filler' with argument types (int32[3])
note: candidate 'Fill(values: var int32[..])' declared at …
Pass buffer[..].
A view does not keep its storage alive
A slice only points at storage, and the storage must outlive every view of it. A view of a local array is valid until that array goes out of scope; a view of an allocation, until the allocation is freed. String literals live for the whole program, so a view of one may be returned freely.
func Platform() -> char8[..] {
return "Windows"; // static storage: fine to return
}
Returning a view of a local array would leave the caller holding a view of storage that no longer exists, so the compiler refuses it. It follows the view through everything a function does with it: a slice of the array, the array itself where a slice is expected, a local given such a view, a structure, array or tuple built from one, and the result of a call handed one. Storing such a view through a pointer or a reference is refused the same way. A by-value array parameter is the function's own copy, so it counts as a local:
func First() -> char8[..] {
let letters: char8[3] = ['a', 'b', 'c'];
return letters[..2]; // error
}
error: returned value views local array 'letters', which is freed when 'First' returns
note: 'letters' is declared at 2:5
note: a slice is a view of storage it does not own, and that storage must outlive it
help: return an owned container such as a 'Vector', or take the storage as a parameter
Comparison
== and the other comparison operators are not defined on slices. Comparing two views would compare where they point rather than what they contain:
error: operator '==' is not defined for slice type 'int32[..]'
note: a slice is a view, so comparing the views would compare addresses rather than elements
help: declare '==' on 'int32[..]', or compare the elements one at a time
Methods on a slice type
An extension may add methods to a slice of one concrete element type, extend int[..] or extend char8[..]; each element type has its own method set. A generic slice extension is rejected — a reusable algorithm over slices is an ordinary generic function:
error: cannot extend slice type 'T[..]' because element type 'T' is not defined
help: extend a slice of one concrete element type, for example 'extend int[..]'
Layout
A slice is 16 bytes, aligned to 8, on every supported target: .data at offset 0 and .length at offset 8. Both T[..] and var T[..] have this layout; the elements are not part of it. See Layout.
See also
- Arrays — the storage a slice most often views
- Ranges — the bounds a slice is cut with
- Pointer slicing — a view of raw memory, built from a pointer and a count
- Text — string literals as
char8[..] - Learn: Slice, Writable slice, Variadic