7.2 KiB
В функциональном стиле, когда тебе нужно свести последовательность значений к одному числу (произведению в данном случае), используют свертку (fold) или, в более простых случаях, готовые методы агрегации вроде product.
В Rust у итераторов есть метод product(), который делает ровно то, что нужно для факториала.
Решение 1: Самое идиоматичное (Iterators + product)
Мы создаем диапазон от 1 до num (включительно) и перемножаем все числа.
fn factorial(num: u64) -> u64 {
(1..=num).product()
}
Почему это работает:
(1..=num)создает итератор, генерирующий числа1, 2, 3, ..., num.- Если
numравно0, диапазон1..=0пуст.
- Если
.product()проходит по итератору, умножая аккумулятор на каждый элемент.- Для пустого итератора (факториал 0)
product()возвращает мультипликативную единицу (1), что математически верно:0! = 1.
- Для пустого итератора (факториал 0)
Решение 2: Более явное (Fold)
Если бы метода product() не существовало, мы бы использовали fold. Это классическая "reduce" операция из функционального программирования.
fn factorial(num: u64) -> u64 {
(1..=num).fold(1, |acc, x| acc * x)
}
Разбор:
1(первый аргумент) — начальное значение аккумулятора.|acc, x| acc * x— замыкание, которое берет текущий аккумулятор (acc) и следующий элемент (x), и возвращает новое значение аккумулятора.- Для
num = 4:acc = 1(начальное),x = 1->1 * 1 = 1acc = 1,x = 2->1 * 2 = 2acc = 2,x = 3->2 * 3 = 6acc = 6,x = 4->6 * 4 = 24
Итог:
Используй (1..=num).product() — это самый "ржавый" (Rustacean) и читаемый способ. Он выполняет все твои условия:
- Нет явного
return(это выражение). - Нет циклов
for/while(спрятаны внутри итератора). - Нет мутабельных переменных.
- Нет рекурсии.
1. "Джентльменский набор" итераторов
Помимо уже знакомых тебе map, filter, fold и flat_map, вот что ты будешь использовать в 90% случаев:
collect()— ты уже знаешь, это "материализатор". Превращает поток обратно в коллекцию (Vec,HashMap,String...).find(|x| ...)— ищет первый элемент, удовлетворяющий условию. ВозвращаетOption<T>.any(|x| ...)/all(|x| ...)— возвращаютbool.anyпроверяет, есть ли хотя бы один,all— что все соответствуют.enumerate()— добавляет индекс к элементу. Вместоxполучаешь(index, x).// Вывести строки с номерами lines.iter().enumerate().for_each(|(i, line)| println!("{}: {}", i, line));zip(other_iter)— "сшивает" два итератора в один поток пар(a, b).take(n)/skip(n)— берет первыеnили пропускает первыеnэлементов.chain(other_iter)— склеивает два итератора: "сначала всё из первого, потом всё из второго".inspect(|x| ...)— бесценно для отладки! Позволяет "подсмотреть" значение в середине цепочки, не меняя его (обычно там делаютprintln!).
2. Вся ли функциональщина в Rust — это итераторы?
И да, и нет.
- Да, итераторы — это главный локомотив функционального стиля в Rust. Это самый мощный и оптимизированный инструмент для обработки данных.
- Нет, ФП в Rust шире. Оно включает:
- Enum + Pattern Matching (алгебраические типы данных) — это основа логики, замена классам и полиморфизму.
- Immutability (неизменяемость по умолчанию).
- Closures (замыкания) — функции высшего порядка, которые можно передавать и возвращать.
Но именно цепочки итераторов делают код "визуально функциональным".
3. Каждую ли задачу можно решить функционально?
Теоретически — да. Любой цикл можно переписать через рекурсию или fold.
Практически — не стоит.
Rust — прагматичный язык. Иногда императивный код лучше:
- Сложная мутация состояния: Если тебе нужно обновлять 5 разных переменных в зависимости от сложной логики на каждом шаге,
foldпревратится в ад с кортежем из 5 элементов. Обычныйforбудет чище. - Ранний выход из вложенных циклов: Выйти из тройного вложенного цикла через
break 'labelпроще, чем писать цепочку итераторов, которая умеет прерываться. - Графы и произвольный доступ: Итераторы хороши для последовательностей. Если ты прыгаешь по индексам массива туда-сюда (
i,i+5,i/2), функциональный стиль будет выглядеть как натягивание совы на глобус.
Совет: Используй итераторы для трансформации потоков данных (фильтрация, маппинг, поиск, агрегация). Используй циклы для сложного управления потоком выполнения или когда состояние слишком запутанное. В Rust нормально сочетать оба подхода.