Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Borrowing collection elements

Borrow an element when you want to inspect or change it without moving its owner:

def main() -> Result[(), AllocError | IoError]:
    mut counts = [1, 5]?
    value = &mut counts[0]
    *value = 2
    print(counts)?
    Ok(())
[2, 5]

The loan ends after its last use. While an element reference is live, the collection cannot grow, remove entries, or move. An out-of-range index traps. Dictionaries lend a value through get_mut, which returns an Option instead of trapping on a missing key.

Two list elements written as different number literals are different places, so both can be borrowed at once:

def main() -> Result[(), AllocError | IoError | OverflowError[i64]]:
    mut scores = [10, 20, 30]?
    first = &mut scores[0]
    last = &mut scores[2]
    *first = *first + *last
    *last = 0
    print(scores)?
    Ok(())
[40, 20, 0]

The compiler proves only this literal case. Any other index, such as i or -1, may name the same element, so its loan covers the whole list:

def main() -> Result[(), AllocError | IoError | OverflowError[i64]]:
    mut scores = [10, 20, 30]?
    i = 2
    first = &mut scores[0]
    last = &mut scores[i]
    *first = *first + *last
    Ok(())
while `scores[0]` is exclusively borrowed

Dictionary entries are never separated this way. To exchange two list elements at computed indices, swap them instead.

Borrowed loops always give references, including when the elements are numbers. Printing and comparison inspect those references directly. Use * when a function takes a value or a new collection needs the element itself:

def count_eq[T](values: &list[T], target: &T) -> Result[i64, OverflowError[i64]]:
    mut count = 0
    for value in values:
        if value == target:
            count = count + 1
    Ok(count)

def main() -> Result[(), AllocError | IoError | OverflowError[i64]]:
    values = [1, 2, 1]?
    target = 1
    print(count_eq(&values, &target)?)?
    for value in &values:
        print(*value)?
    Ok(())
2
1
2
1

The same body works for a list[str]. Shared dictionary items() loops likewise give &K keys and &V values; mutable views give &mut V values. A borrowed collection stays protected against changes throughout its loop.

Borrow part of a list

&values[a:b] borrows a run of elements in place. This slice view copies nothing, so unlike a copying slice it needs no ?. A function takes a view as &[T], or as &mut [T] to change the elements:

def total(values: &[i64]) -> Result[i64, OverflowError[i64]]:
    mut sum = 0
    for value in values:
        sum += *value
    Ok(sum)

def reset(values: &mut [i64]) -> ():
    for value in values:
        *value = 0

def main() -> Result[(), AllocError | IoError | OverflowError[i64]]:
    mut scores = [70, 85, 90, 65, 88]?
    print(total(&scores)?, total(&scores[1:])?)?
    reset(&mut scores[:2])
    print(scores)?
    Ok(())
398 328
[0, 0, 90, 65, 88]

The positions clamp as a copying slice’s do. A view supports len, indexing, narrower views, loops, and == with another view, and prints as a list of its elements. An exclusive view also takes indexed assignment and swap. A &[T] parameter also takes a whole list as &scores, which is a view that copies nothing. A view borrows its run of the list, and the list cannot grow while the view is live. Two exclusive views of one list can be live together when one stops where the other starts:

def main() -> Result[(), AllocError | IoError | ZeroDivisionError | OverflowError[i64]]:
    mut scores = [70, 85, 90, 65, 88]?
    half = len(scores) // 2
    low = &mut scores[:half]
    high = &mut scores[half:]
    low[0] = high[0]
    print(scores)?
    Ok(())
[90, 85, 90, 65, 88]

Views whose ranges may overlap, such as &mut scores[:3] and &scores[2:], conflict. Byte buffers have views too. A view cannot be returned or stored yet; the slice reference lists its operations and limits.

Borrow part of a string

&text[a:b] borrows part of a string the same way. Its type is &substr, and its positions count characters, as a copying string slice’s do. Loop over it, compare it with a string, or search it without copying:

def count(text: &substr, letter: &str) -> Result[i64, OverflowError[i64]]:
    mut n = 0
    for c in text:
        if c == letter:
            n += 1
    Ok(n)

def main() -> Result[(), AllocError | IoError | OverflowError[i64]]:
    line = "name: Ada Lovelace"
    value = &line[6:]
    print(value, len(value))?
    print(&line[:4] == "name", "Love" in value, count(&line, "a")?)?
    first = value[:3]?
    print(first)?
    Ok(())
Ada Lovelace 12
True True 3
Ada

A function takes a substring as &substr, and a whole string as &line or a literal. Copy the text with value[a:b]? when you need an owned str, for example to call a str method or keep it after the string changes. The substring view reference lists every operation.