Как написать функцию на Rust, которая принимает итератор?
Использование обобщений (generics) с трейтом Iterator
Основная проблема в вашем коде — попытка использовать dyn Iterator (динамическую диспетчеризацию) без упаковки в Box. В Rust предпочтительный способ — статическая диспетчеризация через обобщения. Вот правильное решение:
fn sum_squares(items: impl Iterator<Item = i32>) -> i32 {
items.map(|x| x * x).sum()
}
fn main() {
let vec = vec![1, 2, 3, 4, 5];
// Работает с итератором по ссылкам
println!("{}", sum_squares(vec.iter().copied())); // 55
// Работает с IntoIterator
println!("{}", sum_squares(vec.into_iter())); // 55
// Работает с диапазоном
println!("{}", sum_squares(1..=5)); // 55
}
Детальное объяснение
- Ключевое слово
implв позиции параметра означает "анонимный обобщённый тип, реализующий трейт Iterator". Это синтаксический сахар для полной записи с дженериками. - Полная запись с дженериками выглядит так:
fn sum_squares<I: Iterator<Item = i32>>(items: I) -> i32 {
items.map(|x| x * x).sum()
}
Оба варианта эквивалентны. Выбор зависит от стиля: impl короче и читаемее для простых случаев, дженерики дают больше возможностей (например, можно указать дополнительные ограничения).
Что насчёт владения?
Обратите внимание на .copied() для итератора по ссылкам. vec.iter() возвращает Iterator<Item = &i32>, а ваша функция ожидает Item = i32. Чтобы преобразовать ссылки в значения, используйте:
.copied()— для типов, реализующихCopy(i32, f64, bool и т.д.).cloned()— для типов, реализующихClone(String, Vec и т.д.)
Более гибкий вариант: принимаем всё, что можно превратить в итератор
Если вы хотите, чтобы функция принимала не только итераторы, но и коллекции (Vec, массив, HashMap), используйте трейт IntoIterator:
fn sum_squares<T: IntoIterator<Item = i32>>(items: T) -> i32 {
items.into_iter().map(|x| x * x).sum()
}
fn main() {
// Можно передать Vec напрямую
let vec = vec![1, 2, 3];
println!("{}", sum_squares(vec)); // 14
// Можно передать массив
println!("{}", sum_squares([1, 2, 3])); // 14
// Можно передать диапазон
println!("{}", sum_squares(1..=3)); // 14
}
Когда использовать Box? (динамическая диспетчеризация)
Иногда статическая диспетчеризация невозможна — например, когда тип итератора определяется во время выполнения. Тогда используйте Box<dyn Iterator>:
fn sum_squares_dyn(items: Box<dyn Iterator<Item = i32>>) -> i32 {
items.map(|x| x * x).sum()
}
// Или с более удобным синтаксисом через impl IntoIterator
fn sum_squares_dyn2(items: impl IntoIterator<Item = i32>) -> i32 {
let iter: Box<dyn Iterator<Item = i32>> = Box::new(items.into_iter());
iter.map(|x| x * x).sum()
}
Но помните: динамическая диспетчеризация имеет накладные расходы (виртуальный вызов методов) и может помешать оптимизациям компилятора (инлайнинг).
Итоговые рекомендации
- Для 90% случаев используйте
impl Iteratorили дженерики сIterator. - Если функция должна принимать и коллекции, используйте
impl IntoIterator. Box<dyn Iterator>— только когда тип итератора неизвестен на этапе компиляции.- Не забывайте про
.copied()/.cloned()для преобразования ссылок в значения.
Альтернативный подход: использование трейт-объектов с ограничениями
Иногда нужно хранить итератор как поле структуры или возвращать из функции. В таких случаях может быть удобно определить свой трейт-объект с ограничением Sized:
use std::iter::Iterator;
// Структура, которая хранит итератор
struct SquareSummer {
iter: Box<dyn Iterator<Item = i32>>,
}
impl SquareSummer {
fn new(iter: impl IntoIterator<Item = i32>) -> Self {
Self {
iter: Box::new(iter.into_iter()),
}
}
fn sum(&mut self) -> i32 {
self.iter.map(|x| x * x).sum()
}
}
fn main() {
let mut summer = SquareSummer::new(vec![1, 2, 3, 4, 5]);
println!("{}", summer.sum()); // 55
// Можно переиспользовать с другим итератором
let mut summer2 = SquareSummer::new(1..=5);
println!("{}", summer2.sum()); // 55
}
Этот подход полезен, когда нужно абстрагироваться от конкретного типа итератора, сохраняя возможность хранить его в куче. Однако помните, что Box<dyn Iterator> не реализует Sized автоматически, поэтому его нельзя использовать напрямую как поле без обёртки в Box.
Для возврата итератора из функции можно использовать impl Iterator в возвращаемом типе (стабилизировано с Rust 1.26):
fn range_squared(a: i32, b: i32) -> impl Iterator<Item = i32> {
(a..=b).map(|x| x * x)
}
fn main() {
for x in range_squared(1, 5) {
println!("{}", x); // 1, 4, 9, 16, 25
}
}
Но если вам нужно вернуть разные типы итераторов в зависимости от условия, тогда придётся использовать Box<dyn Iterator>.