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:

TypeNameThrough the view you may
T[..]read-only sliceread the elements
var T[..]writable sliceread 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

FromWrittenGives
an array, implicitlyTotal(numbers)T[..] of the whole array
an array, with a rangenumbers[..], numbers[1..4]var T[..] from a writable array place, T[..] otherwise
another slice, with a rangeview[1..]the same writability as view
a pointer and a countp[..n], p[a..b]var T[..] from *var T, T[..] from *T
a string literal"hello"char8[..], always read-only
an array literallet v: int32[..] = [1, 2, 3];a view of the literal's elements
the empty literallet 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:

ExpressionElements of [10, 20, 30, 40, 50]Meaning
numbers[1..4]20, 30, 40indexes 1 up to, not including, 4
numbers[1..=3]20, 30, 40indexes 1 to 3 inclusive
numbers[2..]30, 40, 50from index 2 to the end
numbers[..2]10, 20from the start up to 2
numbers[..=1]10, 20from the start to 1 inclusive
numbers[..]all fivethe 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

MemberTypeMeaning
.lengthuint64the 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 a T[..] 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 an int32[..], and Fill(fixed[..], 0) fails with argument 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