Создание списков с помощью Vec

RustBeginner
Практиковаться сейчас

Введение

Переменная хранит одно значение, однако многим программам нужен упорядоченный список, длина которого может изменяться. Vec<T> в Rust — это расширяемая последовательность: каждый элемент имеет тип T, элементы сохраняют свой порядок, а сам вектор владеет ими.

Вы создадите список заказов в кафе, безопасно прочитаете один элемент по позиции, переберёте все заказы, не потребляя список, а затем измените каждую сохранённую строку через изменяемые ссылки.

Создание и расширение вектора

На этом шаге вы добавите три строки, которыми владеет вектор, в изначально пустой вектор.

Перейдите в подготовленный проект и откройте его исходный файл:

cd /home/labex/project/cafe-orders
nano src/main.rs

Объявление Vec<String> означает «вектор, элементы которого являются значениями String, которыми владеет вектор». Это та же угловая скобочная запись для контейнерных типов, которую вы видели в Option<u32>: здесь тип содержащихся элементов — String. Позже, в лабораторной работе по обобщённым типам, вы узнаете, как определять такие универсальные типы. Vec::new() создаёт пустой вектор. Связь объявлена с mut, потому что добавление элементов изменяет вектор.

Замените комментарий шага 1 тремя вызовами push:

    orders.push(String::from("Latte"));
    orders.push(String::from("Tea"));
    orders.push(String::from("Cake"));

push добавляет один элемент в конец вектора, сохраняя порядок вставки. Сохраните файл с помощью Ctrl+O, нажмите Enter, затем выйдите с помощью Ctrl+X. Проверьте код и запустите программу:

cargo check
cargo run --quiet

Вывод должен быть таким:

Order count: 3
Orders: ["Latte", "Tea", "Cake"]

len() сообщает, что в векторе три элемента. Заполнитель :? использует отладочное форматирование Rust, чтобы вывести весь вектор, включая кавычки вокруг строк.

Безопасное чтение элемента по позиции

На этом шаге вы прочитаете второй элемент вектора, не рискуя получить панику из-за выхода за границы.

Нумерация позиций вектора начинается с нуля, поэтому индекс 1 обозначает второй заказ. Метод get возвращает Option<&String>: Some содержит общую ссылку, если указанная позиция существует, а None означает, что позиция отсутствует. Ссылка позволяет прочитать элемент, пока вектор сохраняет владение им.

Откройте исходный файл:

nano src/main.rs

Замените комментарий шага 2 следующим кодом:

    match orders.get(1) {
        Some(order) => println!("Second order: {order}"),
        None => println!("Second order: missing"),
    }

Этот код читает элемент, не перемещая его String из вектора. Сохраните файл и выйдите из редактора, затем запустите программу:

cargo run --quiet

В конце вывода должна появиться новая строка:

Second order: Tea

Эта строка подтверждает, что позиция 1 существует и что порядок вставки сохранился.

Перебор с общей ссылкой

На этом шаге вы переберёте все заказы, сохранив возможность использовать вектор после цикла.

Запись &orders заимствует вектор, а не перемещает его в цикл for. Поэтому каждое значение цикла является общей ссылкой на одну сохранённую строку String.

Откройте исходный файл:

nano src/main.rs

Замените комментарий шага 3 этим циклом и следующей проверкой:

    for order in &orders {
        println!("Queued: {order}");
    }
    println!("Stored after display: {}", orders.len());

Сохраните файл и выйдите из редактора, затем запустите программу:

cargo run --quiet

Новые строки должны выглядеть так:

Queued: Latte
Queued: Tea
Queued: Cake
Stored after display: 3

Итоговое количество можно получить после цикла, потому что цикл заимствовал вектор и возвращал каждую общую ссылку перед переходом к следующей итерации.

Изменение через изменяемые ссылки

На этом шаге вы получите изменяемую ссылку на каждый элемент вектора и измените элементы непосредственно перед выполнением цикла вывода.

Для одного значения одновременно может существовать только одна изменяемая ссылка. Цикл for по &mut orders соблюдает это правило: он предоставляет изменяемую ссылку на один элемент, использует её в текущей итерации, а затем переходит к следующему элементу.

Откройте исходный файл:

nano src/main.rs

Замените комментарий шага 4 следующим кодом:

    for order in &mut orders {
        order.push_str(" - ready");
    }

push_str добавляет заимствованный текст к каждой строке String, которой владеет вектор. При вызове этого метода Rust автоматически использует изменяемый объект String, на который указывает текущая ссылка.

Сохраните файл и выйдите из редактора, затем проверьте код и запустите программу:

cargo check
cargo run --quiet

Полный вывод должен быть таким:

Order count: 3
Orders: ["Latte", "Tea", "Cake"]
Second order: Tea
Queued: Latte - ready
Queued: Tea - ready
Queued: Cake - ready
Stored after display: 3

Обновлённые строки очереди подтверждают, что все элементы были изменены непосредственно в векторе. Итоговое количество подтверждает, что вектор по-прежнему владеет теми же тремя строками.

Итоги

Вы создали и расширили Vec<String>, безопасно обратились к элементу по позиции с помощью get, перебрали вектор, не потребляя его, и изменили каждый элемент через изменяемые ссылки.