Avatar ·

Как применить функцию к каждому элементу вектора в Rust (аналог map)?

Привет, коллеги! Я только начал изучать Rust, перешел с Python, и у меня возникла проблема с обработкой векторов. В Python я привык делать так: ```python numbers = [1, 2, 3, 4] squared = list(map(lambda x: x**2, numbers)) # [1, 4, 9, 16] ``` Или с помощью list comprehension, что ещё удобнее. В Rust я написал примерно такой код: ```rust fn main() { let numbers = vec![1, 2, 3, 4]; // Я хочу получить новый вектор с квадратами чисел let squared: Vec = numbers.map(|x| x * x).collect(); println!("{:?}", squared); } ``` Компилятор выдаёт ошибку: ``` error[E0599]: no method named `map` found for struct `Vec` in the current scope ``` Я понимаю, что Rust — это не Python, и тут всё строже с типами. Но как же тогда обработать каждый элемент вектора? Я слышал про какие-то итераторы, но не до конца понимаю, как их использовать. Подскажите, пожалуйста, как правильно применить функцию к каждому элементу, чтобы получить новый вектор? Мне нужно именно преобразование, а не изменение исходного массива (иммутабельность). Заранее спасибо за помощь! <<>>

Правильный путь: Итераторы и метод .iter()

Ваша ошибка типична для новичков. Дело в том, что Vec сам по себе не имеет метода map. В Rust функциональная обработка коллекций построена на концепции итераторов (типаж Iterator). Чтобы применить map, нужно сначала создать итератор из вектора.

Вот исправленный код, который делает то, что вы хотите:

fn main() {
    let numbers = vec![1, 2, 3, 4];

    // Шаг 1: .iter() создаёт итератор, который заимствует элементы (&i32)
    // Шаг 2: .map() применяет замыкание к каждому элементу
    // Шаг 3: .collect() собирает результат обратно в Vec
    let squared: Vec<i32> = numbers.iter().map(|x| x * x).collect();

    println!("{:?}", squared); // Выведет: [1, 4, 9, 16]
}

Три способа создания итератора

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

  • .iter() — заимствует элементы. x в замыкании будет &i32 (ссылку). Если вам нужно вернуть новый вектор, вы можете разыменовать x через *x или просто выполнить арифметику, так как Rust сам разыменовывает для операторов.
  • .into_iter() — потребляет вектор (забирает владение). x будет i32 (значение). После этого исходный вектор использовать нельзя.
  • .iter_mut() — заимствует с возможностью изменения. x будет &mut i32. Используется, если вы хотите изменить исходный вектор на месте, а не создавать новый.

Пример с into_iter() (ваш случай из Python)

Если вам не нужен старый вектор после преобразования, используйте into_iter(). Это будет ближе всего к вашему Python-коду:

fn main() {
    let numbers = vec![1, 2, 3, 4];
    let squared: Vec<i32> = numbers.into_iter().map(|x| x * x).collect();
    // numbers больше не доступен, так как мы передали владение
    println!("{:?}", squared);
}

Важные нюансы для новичков

  • Ленивость: map в Rust — ленивый. Он не выполнит замыкание, пока вы не вызовете .collect(), .for_each() или не проитерируетесь в цикле for.
  • Тип замыкания: Замыкание |x| x * x должно возвращать значение того же типа, который ожидает итоговый вектор. В нашем случае i32.
  • Производительность: Компилятор Rust (LLVM) генерирует очень эффективный код. Цепочки итераторов (iter().map().filter().collect()) часто работают так же быстро, как ручные циклы, а иногда и быстрее.

Полный пример с фильтрацией

Для полноты картины покажу, как совместить map и filter (как в Python list comprehension):

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6];
    // Возвести в квадрат только чётные числа
    let result: Vec<i32> = numbers
        .iter()
        .filter(|&&x| x % 2 == 0) // Фильтр: оставляем только чётные
        .map(|x| x * x)            // Преобразование
        .collect();

    println!("{:?}", result); // [4, 16, 36]
}

Обратите внимание на двойное && в фильтре: numbers.iter() даёт нам &i32, а замыкание фильтра получает ссылку на элемент итератора, то есть &&i32. Это нормально, компилятор подскажет.

<<>>

Альтернативный подход: Использование циклов for

Если функциональный стиль с map и замыканиями кажется вам слишком сложным на первых порах, вы можете использовать обычный цикл for и изменяемый вектор. Это более императивный стиль, который может быть понятнее для начинающих.

Вот как можно решить ту же задачу без итераторов:

fn main() {
    let numbers = vec![1, 2, 3, 4];
    let mut squared = Vec::new(); // Создаём пустой изменяемый вектор

    for &num in numbers.iter() {  // Итерируемся по ссылкам
        squared.push(num * num); // Добавляем квадрат в новый вектор
    }

    println!("{:?}", squared); // [1, 4, 9, 16]
}

Когда это удобно? Если логика преобразования сложная (много условий, вложенные циклы, мутация внешних переменных), то цикл for может быть читаемее, чем цепочка итераторов с сложными замыканиями.

Недостаток: Вам нужно вручную объявлять mut и следить за типом коллекции. Компилятор Rust всё равно оптимизирует цикл, но код получается более многословным по сравнению с map.

Совет: Начните с циклов для понимания логики, а затем постепенно переходите на итераторы — это позволит писать более декларативный и безопасный код.