1.10 The List in Parts
Taking what is needed from the shelf rather than the whole row is not a skill - it is obvious. The obvious has an unpleasant quality: everyone understands it except the tools. The task list is no exception - it can only be viewed in full.
The Attempt
add has a name. The next step is clear: give the review a name too. By analogy with
add - &Vec<Task> as the input:
fn list(tasks: &Vec<Task>) {
for task in tasks {
println!("#{}: {} - {:?}", task.id, task.title, task.status);
}
}
A call with one task works:
add(&mut tasks, Task::new(1, "Buy coffee"));
list(&tasks);
But more is needed: showing not the whole list but only part of it. After adding the second and third tasks - only those:
add(&mut tasks, Task::new(2, "Buy milk"));
add(&mut tasks, Task::new(3, "Buy eggs"));
list(&tasks[1..3]);
The compiler responds:
error[E0308]: mismatched types
--> src/main.rs:XX:XX
|
XX | list(&tasks[1..3]);
| ---- ^^^^^^^^^^^^ expected `&Vec<Task>`, found `&[Task]`
| |
| arguments to this function are incorrect
What the Compiler Knows
&tasks[1..3] is not a reference to a Vec. It is &[Task]: a reference to a contiguous
section of tasks, starting at index 1 and up to 3 (not including). A slice does not know
where the data comes from - a Vec, an array, another slice.
&Vec<Task> and &[Task] are different types. A function with a &Vec<Task> parameter
accepts only a reference to a whole Vec - &[Task] does not fit. The reverse works
differently: if a function takes &[Task], it can receive both &tasks (the full list) and
&tasks[1..3] (a part). Rust automatically coerces &Vec<Task> to &[Task] at the call
site - this is a built-in rule of the language.
Range syntax:
| Expression | What it takes |
|---|---|
&tasks[1..3] | index 1 up to 3 (not including) |
&tasks[1..] | index 1 to the end |
&tasks[..2] | from the start up to 2 (not including) |
&tasks[..] | the whole list |
The Enchantment
One change - &Vec<Task> to &[Task]:
fn list(tasks: &[Task]) {
for task in tasks {
println!("#{}: {} - {:?}", task.id, task.title, task.status);
}
}
Now list accepts both the whole list and any part of it. Two calls in main:
fn main() {
let mut tasks: Vec<Task> = Vec::new();
add(&mut tasks, Task::new(1, "Buy coffee"));
list(&tasks);
add(&mut tasks, Task::new(2, "Buy milk"));
add(&mut tasks, Task::new(3, "Buy eggs"));
let task = &mut tasks[0];
task.complete();
if task.is_done() {
println!("Task 1 done");
}
list(&tasks[1..]);
}
#1: Buy coffee - Todo
Task 1 done
#2: Buy milk - Todo
#3: Buy eggs - Todo
make ci passes. list takes a slice - and can now show any part of the list.
The complete
tqcode for this chapter is in1-a-task/10-the-list-in-parts/.
Lore: A Slice and the Whole List
list(&tasks) also works - passing the full list without specifying a range. &tasks has
type &Vec<Task>, but when passed to a function with a &[Task] parameter, Rust coerces
it automatically.
In practice this means: functions that only read a list are better written with &[Task].
They become more general - they work with any source of data, not just a Vec.