Введение
Явный цикл for часто оказывается самым понятным способом обработки коллекции. В Rust также есть итераторы — объекты, которые выдают по одному элементу за раз и позволяют объединять операции в понятный конвейер обработки данных.
Вы сохраните рабочий цикл как эталонный вариант, получите тот же результат с помощью iter и sum, создадите небольшое замыкание, преобразуете показания с помощью map, исключите недопустимые показания с помощью filter, а затем объедините все операции. Все изменения выполняются в подготовленном проекте Cargo, поэтому вы сможете сосредоточиться на новых возможностях Rust.
Получение той же суммы с помощью итератора
На этом этапе вы сравните подготовленный цикл for с итератором, который вычисляет ту же сумму.
Проект находится в /home/labex/project/reading-pipeline, а его программа — в src/main.rs. Подготовленная конструкция vec![12, 18, -1, 24, 30] использует макрос vec! как сокращённый способ создать вектор с указанными значениями. По смыслу это эквивалентно созданию пустого вектора и добавлению каждого значения с помощью push, как в предыдущей лабораторной работе по Vec. Существующий цикл заимствует этот вектор с помощью &readings, перебирает элементы типа &i32 и использует *reading, чтобы получить целое число, на которое ссылается эта ссылка.
Перейдите в каталог проекта и запустите исходный вариант:
cd /home/labex/project/reading-pipeline
cargo run --quiet
Loop total: 83
Сумма включает все показания, в том числе -1. Теперь откройте исходный файл:
nano src/main.rs
Замените комментарий Step 1 следующим кодом:
let iterator_total: i32 = readings.iter().sum();
println!("Iterator total: {iterator_total}");
readings.iter() создаёт итератор, который заимствует каждый элемент вектора. Он не удаляет элементы и не потребляет сам вектор. Метод sum() последовательно складывает полученные элементы; стандартная библиотека Rust умеет складывать целые числа, полученные через ссылки. Аннотация : i32 указывает тип результата, который должен построить sum.
Сохраните файл с помощью Ctrl+O, нажмите Enter, а затем выйдите с помощью Ctrl+X. Перед запуском проверьте код:
cargo check
cargo run --quiet
Loop total: 83
Iterator total: 83
Одинаковые строки показывают, что цикл и итератор описывают одно и то же вычисление. Вектор остаётся доступным для следующих этапов, потому что iter() только заимствует его.
Преобразование показаний с помощью замыкания и map
На этом этапе вы определите замыкание и примените его к каждому показанию с помощью map.
Замыкание — это небольшая безымянная функция, которую можно сохранить в переменной или напрямую передать адаптеру итератора. В форме |parameter: Type| expression параметры указываются между вертикальными чертами, а результатом функции становится значение выражения. Здесь замыкание для калибровки будет добавлять смещение, равное двум.
Откройте исходный файл:
nano src/main.rs
Замените комментарий Step 2 следующим кодом:
let add_offset = |reading: i32| reading + 2;
let adjusted: Vec<i32> = readings.iter().map(|reading| add_offset(*reading)).collect();
println!("Adjusted all: {adjusted:?}");
map вызывает своё замыкание один раз для каждого элемента итератора и выдаёт возвращаемые значения. Типы проходят такой путь: &i32 из iter() → скопированное значение i32 из *reading → скорректированное значение i32 из add_offset → элементы Vec<i32>, созданные с помощью collect(). Форматировщик :? выводит вектор в удобном для чтения отладочном виде.
Сохраните файл и выйдите из редактора, затем проверьте и запустите программу:
cargo check
cargo run --quiet
Новая последняя строка должна выглядеть так:
Adjusted all: [14, 20, 1, 26, 32]
Даже недопустимое значение -1 было преобразовано в 1: map изменяет элементы, но не решает, какие элементы следует оставить.
Сохранение только допустимых показаний с помощью filter
На этом этапе вы примените filter, чтобы оставить неотрицательные показания, не изменяя исходный вектор.
Адаптер filter у итератора вызывает замыкание-предикат для каждого кандидата. Предикат возвращает true, чтобы оставить элемент, или false, чтобы исключить его. Откройте исходный файл:
nano src/main.rs
Замените комментарий Step 3 следующим кодом:
let valid: Vec<i32> = readings
.iter()
.copied()
.filter(|reading| *reading >= 0)
.collect();
println!("Valid only: {valid:?}");
Цепочку методов можно продолжать на отступах после точки. iter() заимствует вектор, а copied() превращает каждый заимствованный элемент &i32 в i32. Это не требует существенных затрат, потому что целые числа реализуют трейт Copy. Затем предикат filter получает временную ссылку на каждый кандидат типа i32, чтобы проверить его, не потребляя значение. Поэтому в сравнении используется *reading. Типы проходят такой путь: &i32 → скопированное значение i32 → сохранённое значение i32 → Vec<i32>.
Сохраните файл и выйдите из редактора, затем запустите программу:
cargo run --quiet
Последняя строка должна выглядеть так:
Valid only: [12, 18, 24, 30]
Отрицательное значение отсутствует, а сохранённые значения остались в исходном порядке.
Создание полного конвейера обработки
На этом этапе вы объедините фильтрацию и преобразование, соберёте обработанные показания и вычислите их сумму.
Адаптеры итераторов работают лениво: filter и map описывают операции, но элементы проходят через них только тогда, когда потребляющая операция запрашивает результат. collect и sum являются потребляющими операциями. Они потребляют конвейер итераторов, но не исходный заимствованный вектор.
Откройте исходный файл:
nano src/main.rs
Замените комментарий Step 4 следующим кодом:
let valid_adjusted: Vec<i32> = readings
.iter()
.copied()
.filter(|reading| *reading >= 0)
.map(add_offset)
.collect();
let valid_total: i32 = valid_adjusted.iter().sum();
println!("Valid adjusted: {valid_adjusted:?}");
println!("Valid adjusted total: {valid_total}");
Сначала конвейер исключает -1, затем map(add_offset) передаёт каждое оставшееся значение i32 замыканию, которое вы определили ранее. Имя замыкания можно передать адаптеру напрямую, если типы его параметров и возвращаемого значения подходят этому адаптеру. Второй итератор заимствует собранный вектор и суммирует его элементы.
Сохраните файл и выйдите из редактора, затем проверьте и запустите программу:
cargo check
cargo run --quiet
Две последние строки должны выглядеть так:
Valid adjusted: [14, 20, 26, 32]
Valid adjusted total: 92
Порядок операций имеет значение: фильтрация перед преобразованием не позволяет недопустимому показанию превратиться в значение, которое выглядит как допустительное положительное число. Исходный вектор readings только заимствовался, поэтому его по-прежнему можно использовать после этого.
Итоги
Вы сравнили явный цикл с итератором, создали и повторно использовали замыкание, преобразовали значения с помощью map, выбрали значения с помощью filter, собрали новый вектор и вычислили сумму с помощью sum. Кроме того, вы сохранили исходный вектор, начиная каждый конвейер с заимствованного итератора.


