Функциональное программирование в Rust: от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Функциональное программирование в Rust: от А до Я

Язык Rust, известный своей безопасной работой с памятью и высокой производительностью, также предлагает мощные инструменты для функционального программирования. В отличие от чисто функциональных языков (Haskell), Rust позволяет гибко сочетать императивный и функциональный стили. В этой статье мы разберем ключевые концепции функционального программирования в Rust: от базовых замыканий до продвинутых монадических паттернов. Материал будет полезен как новичкам, так и опытным разработчикам, желающим писать более лаконичный и надежный код.

1. Основы: Иммутабельность и переменные

Функциональное программирование начинается с иммутабельности (неизменяемости). В Rust переменные по умолчанию неизменяемы. Это ключевое отличие от многих языков.

// Иммутабельная переменная (по умолчанию)
let x = 10;
// x = 20; // Ошибка компиляции!

// Мутабельная переменная (явно)
let mut y = 5;
y = 15; // Работает

Использование let без mut гарантирует, что значение не изменится после инициализации. Это снижает количество побочных эффектов и делает код более предсказуемым. В функциональном стиле рекомендуется отдавать предпочтение неизменяемым переменным, создавая новые значения вместо изменения старых.

2. Замыкания (Closures) — анонимные функции

Замыкания — это анонимные функции, которые могут захватывать переменные из своего окружения. В Rust они реализованы через трейты Fn, FnMut и FnOnce.

2.1. Синтаксис и захват переменных

// Простое замыкание
let add_one = |x: i32| -> i32 { x + 1 };
println!("{}", add_one(5)); // 6

// Захват переменной из окружения
let base = 10;
let add_base = |x| x + base; // base захватывается по ссылке (&)
println!("{}", add_base(20)); // 30

// Захват с мутацией (FnMut)
let mut counter = 0;
let mut increment = || { counter += 1; };
increment();
increment();
println!("{}", counter); // 2

// Захват с потреблением (FnOnce)
let name = String::from("Rust");
let consume = || { drop(name); };
consume();
// println!("{}", name); // Ошибка: name перемещено

2.2. Замыкания как аргументы функций

Замыкания часто передаются в функции высшего порядка, такие как map, filter и fold.

let numbers = vec![1, 2, 3, 4, 5];

// Использование замыкания с filter
let evens: Vec<i32> = numbers
    .into_iter()
    .filter(|&x| x % 2 == 0)
    .collect();

println!("{:?}", evens); // [2, 4]

3. Итераторы и цепочки преобразований

Итераторы — сердце функционального программирования в Rust. Они ленивы (вычисления происходят только при вызове collect() или for) и позволяют строить цепочки преобразований.

3.1. Базовые адаптеры итераторов

let data = vec![1, 2, 3, 4, 5, 6];

// Цепочка: filter -> map -> take -> collect
let result: Vec<i32> = data
    .iter()
    .filter(|&x| x % 2 == 0)  // оставляем четные
    .map(|x| x * 2)           // удваиваем
    .take(2)                  // берем первые два
    .collect();

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

3.2. Свертка (fold) и редукция

fold — аналог reduce в других языках. Он аккумулирует результат, проходя по всем элементам.

let numbers = [1, 2, 3, 4, 5];

// Сумма всех чисел
let sum = numbers.iter().fold(0, |acc, &x| acc + x);
println!("Sum: {}", sum); // 15

// Факториал
let factorial = (1..=5).fold(1, |acc, x| acc * x);
println!("5! = {}", factorial); // 120

3.3. Ленивые бесконечные итераторы

// Бесконечный итератор, берем первые 10
let first_ten: Vec<i32> = (0..)
    .filter(|x| x % 2 == 0)
    .map(|x| x * x)
    .take(10)
    .collect();

println!("{:?}", first_ten); // [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

4. Сопоставление с образцом (Pattern Matching) и Option/Result

Сопоставление с образцом — мощный инструмент, который идеально сочетается с функциональным стилем. Вместо вложенных if-else вы используете match для обработки всех возможных вариантов.

4.1. Работа с Option и Result

Типы Option<T> и Result<T, E> — это монады в Rust. Они позволяют обрабатывать отсутствие значения или ошибки без исключений.

// Option
fn safe_divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 { None } else { Some(a / b) }
}

let result = safe_divide(10.0, 2.0);
match result {
    Some(value) => println!("Result: {}", value),
    None => println!("Division by zero!"),
}

// Result
fn read_file(path: &str) -> Result<String, std::io::Error> {
    std::fs::read_to_string(path)
}

// Использование map и and_then (монадический стиль)
let content = read_file("hello.txt")
    .map(|s| s.trim().to_string())
    .and_then(|s| {
        if s.is_empty() { 
            Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "empty file"))
        } else { 
            Ok(s) 
        }
    });

match content {
    Ok(text) => println!("File content: {}", text),
    Err(e) => eprintln!("Error: {}",                

Блоги

Рекомендаций по книгам