RustArena

Step 6 of 6

Slices in real code

Slices are everywhere in real Rust code, because they let one function work on any data without caring who owns it, and without copying it. This step puts the pieces together.

Iterating slices

All of these loops work on a slice s (and on a vector or array, through a slice):

Loop You get
for x in s or for x in s.iter() a &T for each element (the same thing, two spellings)
for x in s.iter_mut(), when s is a &mut [T] a &mut T for each element
for i in 0..s.len() each index, for when you need positions
for word in text.split_whitespace() each word, as a &str slice of text
for line in text.lines() each line, as a &str slice of text

A zero-copy log reader

This program reads a log held in one String and never copies any of its text: every line and every level is a slice pointing into log.

// "[LEVEL] message": the level is between the brackets.
fn level_of(line: &str) -> &str {
    let bytes = line.as_bytes();
    for i in 0..bytes.len() {
        if bytes[i] == b']' {
            return &line[1..i];
        }
    }
    ""
}

fn count_level(lines: &[&str], level: &str) -> usize {
    let mut n = 0;
    for line in lines {
        if level_of(line) == level {
            n += 1;
        }
    }
    n
}

fn main() {
    let log = String::from("[INFO] started\n[ERROR] disk full\n[WARN] slow\n[ERROR] timeout");
    let mut lines = Vec::new();
    for line in log.lines() {
        lines.push(line);          // a &str slice of log: nothing copied
    }
    println!("{} lines, {} errors", lines.len(), count_level(&lines, "ERROR"));
    println!("first level: {}", level_of(lines[0]));
}
4 lines, 2 errors
first level: INFO

Look at the types: lines is a Vec<&str>, a vector of slices that all borrow from log. count_level takes &[&str], a slice of string slices. Each line in its loop is a &&str (a reference to an element); Rust lets you pass it where a &str is expected. And because every slice borrows from log, the borrow checker won't let log be changed or dropped while lines is in use.

Idiomatic signatures

Your function needs to... Take Rather than
read text &str &String or String
read a list &[T] &Vec<T> or Vec<T>
change elements in place &mut [T] &mut Vec<T>
add or remove elements &mut Vec<T>
add to text &mut String
keep the value (store it) String, Vec<T>

And for return values:

It returns... Return
part of its input &str or &[T], a slice of the input
new data String or Vec<T>, owned

Workplace note: these signatures are what reviewers look for. A function taking &str or &[T] works with every caller and makes no copies, and returning slices of the input is how fast parsers stay fast. When a slice has to outlive the data it points into (say, you're reading lines one at a time and each line is dropped after its loop round), call .to_string() or .to_vec() to get an owned copy: the compiler will tell you when (E0597).

Common mistakes

Your task

Write longest_word, which returns the longest word in text (words are separated by whitespace), as a slice of text. Compare lengths in characters (.chars().count()); on a tie, the first word wins. If there are no words, return "".

Challenge 2

Write longest_name, which takes a slice of Strings and returns the longest name (by .len(), first on a tie) as a &str, or "" if the slice is empty. A &String element can be assigned to a &str variable directly.

Challenge 3

A scoreboard arrives as lines of name score. Write parse_line, which splits one line into the name (a slice of the line) and the score (a u32), then finish main: print the player with the highest score (the first one on a tie) and the total. The starter reads the whole input into one String, so every name can stay a slice of it: no copies. For the input:

ferris 42
corro 17
sunny 41

it prints:

top: ferris (42)
total: 100

Previous: Mutable slices: &mut [T]