← Table of contents

1.7 A Place to Keep Them

One piece needs no storage - it is simply there, in hand, on the workbench, nearby. A second piece requires a decision: where to put the first. A third requires a policy. A craftsman who can forge a piece but has not figured out where to put it starts from an empty table every time. Sometimes this is called a process. More often, a problem.

What You Need

Until now the work involved a single task, and everything was simple. But two tasks already demand a naming system: task1, task2. With ten, that approach becomes awkward, and if the number of tasks is unknown in advance - it becomes impossible.

A container is needed: somewhere to put tasks and retrieve them when the moment comes. In Rust, a list of elements of the same type uses Vec. Vec<Task> is a list of tasks; the angle brackets tell the compiler that only Task values go into this container. Angle brackets will appear again in chapter 3.4 - for now, read Vec<Task> as “a list of tasks.”

Two operations matter with a container: put in, and take out.

The Build

Create the list and immediately try to add a task:

let tasks = Vec::new();
tasks.push(Task::new(1, "Buy coffee"));

The compiler, as usual, has something to say:

error[E0596]: cannot borrow `tasks` as mutable, as it is not declared as mutable
  --> src/main.rs:XX:XX
   |
XX |     let tasks = Vec::new();
   |         ----- help: consider changing this to be mutable: `mut tasks`
XX |     tasks.push(Task::new(1, "Buy coffee"));
   |     ^^^^^^^^^^^ cannot borrow as mutable

The same situation as with complete in chapter 1.6: modification requires mut. push changes the list - it must be declared mutable:

let mut tasks: Vec<Task> = Vec::new();

Add the first task:

tasks.push(Task::new(1, "Buy coffee"));

Now try retrieving it into a variable and working with it as before:

let mut task = tasks[0];

The compiler reminds:

error[E0507]: cannot move out of index of `Vec<Task>`
   |
   |     let mut task = tasks[0];
   |                    ^^^^^^^^ move occurs because value has type `Task`,
   |                    which does not implement the `Copy` trait

Moving a task out of the list is not allowed - it would leave a hole. tasks[0] is not a separate variable but a position inside the Vec. To work with it, a reference is needed:

let task = &mut tasks[0];

&mut tasks[0] is a mutable reference to the first element inside the list. The task stays in the Vec; task is just the way to reach it. The familiar API works through it:

println!("#{}: {}", task.id, task.title);
task.complete();
if task.is_done() {
    println!("Task 1 done");
}

Put in and take out - confirmed. Add the rest:

tasks.push(Task::new(2, "Buy milk"));
tasks.push(Task::new(3, "Buy eggs"));

push appends to the end of the list - if you want to experiment, milk is at index 1 and eggs at 2.

The Result

fn main() {
    let mut tasks: Vec<Task> = Vec::new();
    tasks.push(Task::new(1, "Buy coffee"));

    let task = &mut tasks[0];
    println!("#{}: {}", task.id, task.title);
    task.complete();
    if task.is_done() {
        println!("Task 1 done");
    }

    tasks.push(Task::new(2, "Buy milk"));
    tasks.push(Task::new(3, "Buy eggs"));
}
#1: Buy coffee
Task 1 done

make ci passes. Vec stores the tasks; push adds them, &mut tasks[0] retrieves one and allows working with it in place.

The complete tq code for this chapter is in 1-a-task/07-a-place-to-keep-them/.

Scroll: Your data is not where you think it is Scroll: The borrow checker has one rule - and it is not the one you think