← Оглавление

1.7 Полка

Одно изделие не требует хранения - оно просто есть, в руках, на верстаке, рядом. Второе изделие требует решения: куда девать первое. Третье - политику. Мастер, умеющий выковать изделие, но не придумавший, куда его класть, каждый раз начинает с пустого стола. Иногда это называют процессом. Чаще - проблемой.

Что нужно

До сих пор мы работали с одной задачей, всё было просто и понятно. Но что если будет нужно две, тогда уже требуется система индексов: task1, task2. С десятью этот подход становится неудобным, а если количество задач заранее неизвестно - просто невозможным!

Нужен контейнер: место, куда можно складывать и в нужный момент забирать задачи. В Rust для списка элементов одного типа используют Vec. Vec<Task> - список задач; угловые скобки говорят компилятору, что в этот контейнер войдут только Task. С угловыми скобками мы встретимся снова в главе 3.4, а пока достаточно читать Vec<Task> как “список задач”.

С контейнером важно уметь делать две вещи: положить и взять.

Сборка

Создаём список и сразу пробуем положить задачу:

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

Компилятор как обычно подсказывает:

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

Та же история, что и с complete в главе 1.6: изменение требует mut. push меняет список - его нужно сделать изменяемым:

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

Добавим первую задачу:

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

Теперь хочется её достать в переменную и работать так же, как и в прошлой главе:

let mut task = tasks[0];

Компилятор напоминает:

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

Переместить задачу из списка нельзя - в списке осталась бы дыра. tasks[0] - не отдельная переменная, а позиция внутри Vec. Чтобы работать с ней, нужна ссылка:

let task = &mut tasks[0];

&mut tasks[0] - изменяемая ссылка на первый элемент прямо внутри списка. Задача остаётся в Vec; task - это способ к ней обратиться. Весь знакомый API работает через неё:

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"));

push отправляет элементы в конец списка, если захочешь поэкспериментировать - у milk индекс - 1, у eggs - 2.

Итог

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 проходит. Vec хранит задачи; push кладёт, &mut tasks[0] достаёт и позволяет работать с элементом на месте.

Полный код tq для этой главы - 1-a-task/07-a-place-to-keep-them/.

Свиток: Your data is not where you think it is Свиток: The borrow checker has one rule - and it is not the one you think