hyperion/20-dev/00-rust/rustlings/Untitled.md
2025-11-27 23:37:40 +03:00

7.2 KiB
Raw Blame History

В функциональном стиле, когда тебе нужно свести последовательность значений к одному числу (произведению в данном случае), используют свертку (fold) или, в более простых случаях, готовые методы агрегации вроде product.

В Rust у итераторов есть метод product(), который делает ровно то, что нужно для факториала.

Решение 1: Самое идиоматичное (Iterators + product)

Мы создаем диапазон от 1 до num (включительно) и перемножаем все числа.

fn factorial(num: u64) -> u64 {
    (1..=num).product()
}

Почему это работает:

  1. (1..=num) создает итератор, генерирующий числа 1, 2, 3, ..., num.
    • Если num равно 0, диапазон 1..=0 пуст.
  2. .product() проходит по итератору, умножая аккумулятор на каждый элемент.
    • Для пустого итератора (факториал 0) product() возвращает мультипликативную единицу (1), что математически верно: 0! = 1.

Решение 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:
    1. acc = 1 (начальное), x = 1 -> 1 * 1 = 1
    2. acc = 1, x = 2 -> 1 * 2 = 2
    3. acc = 2, x = 3 -> 2 * 3 = 6
    4. acc = 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 — прагматичный язык. Иногда императивный код лучше:

  1. Сложная мутация состояния: Если тебе нужно обновлять 5 разных переменных в зависимости от сложной логики на каждом шаге, fold превратится в ад с кортежем из 5 элементов. Обычный for будет чище.
  2. Ранний выход из вложенных циклов: Выйти из тройного вложенного цикла через break 'label проще, чем писать цепочку итераторов, которая умеет прерываться.
  3. Графы и произвольный доступ: Итераторы хороши для последовательностей. Если ты прыгаешь по индексам массива туда-сюда (i, i+5, i/2), функциональный стиль будет выглядеть как натягивание совы на глобус.

Совет: Используй итераторы для трансформации потоков данных (фильтрация, маппинг, поиск, агрегация). Используй циклы для сложного управления потоком выполнения или когда состояние слишком запутанное. В Rust нормально сочетать оба подхода.