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
- Keeping a slice of a value that's about to be dropped, like a line
Stringthat only lives for one loop round (E0597). Read everything into oneStringfirst, or keep an owned copy. - Mixing up
&[&str]and&[String]. Both are fine parameter types; use whichever matches the data you have. - Returning
Stringwhen a slice would do. If the result is part of the input, return a slice and let the caller decide whether to copy it.
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