Slices
A slice is a borrowed view of a contiguous run of elements. Where an array's length is part of its type ([i32; 5]), a slice's length is a runtime value — so the same function can work over arrays of any size or over a subrange of an array.
Slices come in two flavors:
Slice(T)— read-only viewMutSlice(T)— read/write view
Constructing a Slice
Build a slice by borrowing a range of an array:
fn main() -> i32 {
let arr: [i32; 5] = [10, 20, 30, 40, 50];
let whole: Slice(i32) = &arr[..]; // all 5 elements
let middle: Slice(i32) = &arr[1..4]; // 3 elements: [20, 30, 40]
let _tail: Slice(i32) = &arr[2..]; // [30, 40, 50]
let _head: Slice(i32) = &arr[..3]; // [10, 20, 30]
let n: i32 = @cast(middle.len(), i32);
n // 3
}
The same syntax with &mut produces a MutSlice(T):
fn main() -> i32 {
let mut arr: [i32; 5] = [1, 2, 3, 4, 5];
let m: MutSlice(i32) = &mut arr[..];
m[2] = 42;
arr[2] // 42
}
The .. form is the whole array. lo..hi selects elements lo (inclusive) through hi (exclusive). The bounds must be usize.
Slice Methods
Both slice kinds have:
s.len() -> usize— number of elementss.is_empty() -> bool— whetherlen()is zero
Reads use s[i], with bounds checking that panics on out-of-range indices. Writes through a MutSlice(T) use m[i] = value.
fn first_two_sum(s: Slice(i32)) -> i32 {
let n: usize = s.len();
if n >= 2 {
let mut total: i32 = 0;
let mut i: usize = 0;
for x in s {
if i < 2 { total = total + x; }
i = i + 1;
}
total
} else {
0
}
}
fn main() -> i32 {
let arr = [10, 20, 30, 40];
first_two_sum(&arr[..]) // 30
}
Indexing through a slice parameter. Currently
s[i]works on a local slice variable but not on a slice received as a function parameter. To index in a function, iterate withfor x in s(which works in both cases). This restriction will go away as the slice ABI is finished — see ADR-0064.
Iterating Over a Slice
for x in slice walks the elements in order:
fn sum(s: Slice(i32)) -> i32 {
let mut total: i32 = 0;
for x in s {
total = total + x;
}
total
}
fn main() -> i32 {
let arr: [i32; 5] = [10, 20, 30, 40, 50];
sum(&arr[1..4]) // 90 (20 + 30 + 40)
}
The for-each form makes slices the natural way to write algorithms that don't care about array length.
Generic Functions
Because a slice's length is dynamic, a single function can accept arrays of any size:
fn max(s: Slice(i32)) -> i32 {
let mut best: i32 = 0;
let mut seen: bool = false;
for x in s {
if !seen || x > best {
best = x;
seen = true;
}
}
best
}
fn main() -> i32 {
let small: [i32; 3] = [3, 7, 1];
let big: [i32; 6] = [9, 2, 4, 8, 5, 6];
max(&small[..]) + max(&big[..]) // 7 + 9 = 16
}
This pattern — "take a slice, not an array" — is the standard way to write reusable code over collections.
Slice Lifetimes
Like other reference forms, a slice is scope-bound: the underlying array must outlive every slice that views it. Slices can't be returned from a function, stored in a struct field that outlives the call, or otherwise allowed to escape. The compiler enforces this so a slice never points to memory that's been freed or reused.
For the formal rules, see the Slices spec chapter and ADR-0064.