Shanraq.org Shanraq.org
Итераторы: читать, менять или передавать владение
IT

Rust: с нуля до своего органайзера Урок 41 из 50

Итераторы: читать, менять или передавать владение

Урок 41. Выбрать способ обхода задач, не потеряв данные раньше времени.

Текст переведён с помощью ИИ.

Предварительно: урок 20 о заимствовании, урок 24 о Vec, урок 40 об органайзере. Исходное состояние — копия проекта из урока 40; сохраните её отдельно. Здесь ради одного нового понятия main.rs заменяется короткой программой, данные из прежнего запуска не переносятся.

Образ и опорная карта

У библиотекаря есть три способа пройти вдоль полки: посмотреть на корешки, исправить карточки, вынести книги. Итератор — объект, который выдаёт элементы по одному и помнит место обхода. Аналогия кончается там, где начинается владение Rust: iter() даёт общие ссылки &Task, iter_mut() — изменяемые &mut Task, into_iter() забирает элементы из списка. После последнего способа прежний Vec использовать нельзя. Ссылки и владение разобраны в уроках 19–21.

Список → iter (читать) / iter_mut (менять) / into_iter (передать) → следующий элемент либо конец. next() делает один шаг: возвращает Some с элементом или None в конце. let mut reader нужен потому, что каждый шаг меняет положение итератора. Создание итератора само по себе не проходит список — он ленив. Цикл for запрашивает элементы до конца. Три прохода в примере не существуют одновременно: изменяемый доступ начинается после завершения чтения. if let Some(task) выполняет тело только при найденном элементе. !task.done означает «ещё не готово».

Запустите и наблюдайте

В папке проекта замените src/main.rs кодом ниже и выполните знакомую по уроку 6 команду cargo run. Дополнительных библиотек нет. Сообщения Cargo не входят в показанный вывод программы. На узком экране длинную строку кода можно прокрутить пальцем внутри светлого блока; сама страница остаётся на месте.

struct Task {
    id: u32,
    title: String,
    done: bool,
}

fn main() {
    let mut tasks = vec![
        Task {
            id: 1,
            title: String::from("Читать Rust"),
            done: false,
        },
        Task {
            id: 2,
            title: String::from("Записать идею"),
            done: false,
        },
    ];
    let mut reader = tasks.iter();
    if let Some(task) = reader.next() {
        println!("Первая: {}", task.title);
    }
    for task in tasks.iter_mut() {
        if task.id == 2 {
            task.done = true;
        }
    }
    for task in tasks.iter() {
        println!("Задача {}: {}", task.id, task.title);
    }
    let mut done_count = 0;
    for task in tasks.iter() {
        if task.done {
            done_count += 1;
        }
    }
    println!("Готово: {}", done_count);
    for task in tasks.into_iter() {
        println!("Владеем: {}", task.title);
    }
}
Первая: Читать Rust
Задача 1: Читать Rust
Задача 2: Записать идею
Готово: 1
Владеем: Читать Rust
Владеем: Записать идею

Сначала читается первая задача, затем меняется отметка задачи 2. Последний цикл получает названия во владение и завершает использование списка. Ошибка новичка — написать tasks.len() после into_iter(): значение уже передано; компилятор это остановит. Для простой печати обычно достаточно iter(). Всё ещё хранится только в памяти одного запуска.

Вспомните без подсказки

  1. Какой из трёх способов нужен, чтобы отметить задачу готовой?
  2. Почему после into_iter() нельзя снова читать tasks?
  3. Пройдёт ли список одно лишь let reader = tasks.iter()?

Задание

Обязательное. До цикла into_iter() посчитайте отдельным проходом, сколько задач ещё ожидают выполнения, и выведите Ожидают: 1. Сохраните уже показанный порядок строк. Затем объясните, почему счётчик не требует передачи владения.

Ответы

Показать ответы

Заведите pending_count = 0, обойдите tasks.iter() и увеличьте счётчик при !task.done. Делайте это до into_iter().

Эталон после попытки

struct Task {
    id: u32,
    title: String,
    done: bool,
}

fn main() {
    let mut tasks = vec![
        Task {
            id: 1,
            title: String::from("Читать Rust"),
            done: false,
        },
        Task {
            id: 2,
            title: String::from("Записать идею"),
            done: false,
        },
    ];
    let mut reader = tasks.iter();
    if let Some(task) = reader.next() {
        println!("Первая: {}", task.title);
    }
    for task in tasks.iter_mut() {
        if task.id == 2 {
            task.done = true;
        }
    }
    for task in tasks.iter() {
        println!("Задача {}: {}", task.id, task.title);
    }
    let mut done_count = 0;
    for task in tasks.iter() {
        if task.done {
            done_count += 1;
        }
    }
    println!("Готово: {}", done_count);
    let mut pending_count = 0;
    for task in tasks.iter() {
        if !task.done {
            pending_count += 1;
        }
    }
    println!("Ожидают: {}", pending_count);
    for task in tasks.into_iter() {
        println!("Владеем: {}", task.title);
    }
}
Первая: Читать Rust
Задача 1: Читать Rust
Задача 2: Записать идею
Готово: 1
Ожидают: 1
Владеем: Читать Rust
Владеем: Записать идею

После проверки

Проверка: счётчик добавлен до передачи владения, исходные ID и отметки не меняются. Официальная глава об итераторах. Если путаются ссылки, вернитесь к уроку 20.

Предыдущий урок · Оглавление

Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом

Комментарии (0)

Пока нет комментариев. Будьте первым.