Closures

Внешняя ссылка: https://habr.com/ru/articles/588917/

Статьи в разделе:

Closure

Замыкания - анонимные функции. Их можно присвоить переменным и вызывать. Это также функция, которая ссылается на свободные переменные в своей области видимости. Базовое использование:

let add_one = |x: i32| -> i32 { x + 1 };
println!("Add one to 5 = {}", add_one(5)); // Add one to 5 = 6

let add_two = |x| x + 2;
println!("Add two to 5 = {}", add_two(5)); // Add two to 5 = 7

let add = |a, b| a + b;
println!("Sum of 5 and 6 = {}", add(5, 6)); // Sum of 5 and 6 = 11

let just_number = || 42;
println!("Answer to all: {}", just_number()); // Answer to all: 42

В отличие от функций, замыкания могут использовать переменные вне своего блока:

(0..3).for_each(|x| {println!("map i = {}", x * 2);});

let factor = 2;
let multiplier = |x| x * factor;
println!("{}", multiplier(5)); // 10
println!("Factor: {factor}"); // factor ещё доступен тут 

move

Ключ move перемещает переменную в замыкание:

let greet = move || {
println!("Hello, {}!", name);
};
greet();
// println!("{name}"); даст ошибку

Это нужно для того, чтобы замыкание получило владение данным и пережило scope, в котором оно было объявлено.

Работа с итераторами

.map(<closure>) передаёт владение элементами итератора замыканию, чтобы их можно было трансформировать в другие элементы, которые далее возвращает замыкание.

.filter(<closure>) возвращает оригинальные элементы, когда предикат замыкания возвращает true. Таким образом, отдавать владение элементами замыканию нельзя, и нужно передавать по ссылке.

let x2: Vec<i32> = nums.iter().map(|x| x * 2).collect();
println!("x2 = {x2:?}"); // x2 = [2, 4, 6, 8, 10]
let evens: Vec<&i32> = nums.iter().filter(|&x| x % 2 == 0).collect();
println!("Evens = {evens:?}"); // Evens = [2, 4]
let even_sum: i32 = (0..=10).filter(|n| *n % 2 == 0).sum() 

2 в 1 = filter_map()

Можно вернуть элементы, когда предикат значения true, и сразу же их трансформировать. На вход он принимает замыкание, возвращающее Option<T>:

  • Если на выходе Some(value), значение включается в результат;
  • Если замыкание возвращает None, элемент исключается фильтром.
let numbers = vec!["1", "2", "abc", "4"];

// Раздельно: filter() и потом map()
let result: Vec<i32> = numbers
    .iter()
    .filter(|s| s.parse::<i32>().is_ok())  // фильтровать цифры
    .map(|s| s.parse::<i32>().unwrap())     // Перевести их в int
    .collect();
// Result: [1, 2, 4]

// Вместе: filter_map()
let result: Vec<i32> = numbers
    .iter()
    .filter_map(|s| s.parse::<i32>().ok())  // фильтр и перевод в 1 шаг
    .collect();
// Result: [1, 2, 4]

Вложенные замыкания map()

    let a = (0..=3).map(|x| x * 2).map(|y| y - 1);
    // первая итерация map(): 2, 4, 6
    // вторая итерация map(): 1, 3, 5
    for i in a {
        println!("{i}");
    }

All

Замыкание all возвращает True, если все элементы в замыкании соответствуют условию.

let a: Vec<i32> = vec![1, 2, 3, 4];
print!("{}\n", a.into_iter().all(|x| x > 1)); // false

Для пустого вектора замыкание all вернёт True:

let a: Vec<i32> = vec![];
print!("{}\n", a.into_iter().all(|x| x > 1)); // true

Цикл через замыкание vs for

use std::collections::HashMap;
pub fn main() {
    let num_vec = vec![1, 2, 1, 3, 5, 2, 1, 4, 6];
    let mut number_count: HashMap<i32, i32> = HashMap::new();
    for key in num_vec {
        *number_count.entry(key).or_default() += 1;
    }
    /* for (k, v) in number_count {
        print!("{} -> {}; ", k, v);
    } */
    
    number_count.iter().for_each(|(k, v)| {
        print!("{} -> {}; ", k, v);
    }); //цикл через замыкание итератора
}

Subsections of Closures

Fold

fold используется для превращения множества в 1 значение.

Базовый синтаксис

fn fold<B, F>(self, init: B, f: F) -> B
where
    F: FnMut(B, Self::Item) -> B,
  • init: исходное значение счётчика;
  • f: замыкание, которое тоже принимает 2 аргумента: счётчик и объект. Замыкание возвращает счётчик.

Как работает

fold() преобразовывает каждый элемент по цепочке :

  1. Начинает со значения  init в счётчике;
  2. Для каждого элемента применяет замыкание f(accumulator, element);
  3. Возвращает финальное значение счётчика после обработки всех элементов. Наглядно по шагам:
fold(init, |acc, x| f(acc, x))
// Шаг 1: acc = init
// Шаг 2: acc = f(init, item1)
// Шаг 3: acc = f(acc, item2)
// Шаг 4: acc = f(acc, item3)
// ...
// Возврат финального acc

Сумма чисел:

let numbers = vec![1, 2, 3, 4, 5];  
let sum = numbers.iter().fold(0, |acc, &x| acc + x);
println!("Sum: {}", sum); // 15    
// По шагам:
// acc = 0
// acc = 0 + 1 = 1
// acc = 1 + 2 = 3
// acc = 3 + 3 = 6
// acc = 6 + 4 = 10
// acc = 10 + 5 = 15
// код выше можно заменить на sum()

Произведение чисел:

let numbers = vec![1, 2, 3, 4, 5];
let product = numbers.iter().fold(1, |acc, &x| acc * x);
println!("Product: {}", product); // 120
// код выше можно заменить на product()

Нахождение максимального значения

let numbers = vec![3, 1, 4, 1, 5, 9, 2];
let max = numbers.iter().fold(i32::MIN, |acc, &x| acc.max(x));
println!("Max: {}", max); // 9

Соединение (конкатенация) строк

Счётчик у fold необязательно числовой, можно использовать, например, String:

fn hello_world_concat() {
	let words = vec!["hello", "world", "rust"];    
	let sentence = words.iter().fold(String::new(), |acc, &word| {
	if acc.is_empty() {
	    word.to_string()
	    } else {
	    acc + " " + word
	    }});
	println!("Sentence: '{}'", sentence); // "hello world rust"
}

fn giant_grunts(initial: char) -> String {
    ["Bee", "Fee", "Gee", "Fi", "Hi", "Fo", "Mo", "Fum", "Tum"].iter().fold(
        String::new(),
        |acc, grunt| if grunt.starts_with(initial) { acc + grunt } else { acc },
    )}

fn main() {
    let song = giant_grunts('F');
    println!("{song:?}"); // "FeeFiFoFum" 
}

Разделение на чётные и нечётные

    let numbers = vec![1, 2, 3, 4, 5];
    let result = numbers.iter().fold(
        (Vec::new(), Vec::new()), // (evens, odds)
        |(mut evens, mut odds), &x| {
            if x % 2 == 0 {
                evens.push(x);
            } else {
                odds.push(x);
            }
            (evens, odds)
        }
    );
    
    println!("Evens: {:?}, Odds: {:?}", result.0, result.1);
    // Evens: [2, 4], Odds: [1, 3, 5]

Получение частоты символов в тексте:

use std::collections::HashMap;
pub fn first_non_repeating(s: &str) -> Option<HashMap<char, i32>> {
    let char_frequency = s.chars().fold(HashMap::new(), |mut acc, char| {
        *acc.entry(char).or_insert(0) += 1;
        acc
    });
    Some(char_frequency)
}

fn main() {
    println!("Letter frequency: {:?}", first_non_repeating("stress"));
} // Letter frequency: Some({'e': 1, 'r': 1, 's': 3, 't': 1})

Реализация других методов

Реализация map()

let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().fold(Vec::new(), |mut acc, &x| {
    acc.push(x * 2);
    acc });
    println!("Doubled: {:?}", doubled); // [2, 4, 6, 8, 10]

Реализация filter()

let numbers = vec![1, 2, 3, 4, 5, 6];    
    let evens: Vec<i32> = numbers.iter().fold(Vec::new(), |mut acc, &x| {
        if x % 2 == 0 {
            acc.push(x); }
        acc });
println!("Evens: {:?}", evens); // [2, 4, 6]

Вариация try_fold()

От fold() отличается тем, что прерывает выполнение и возвращает Result(Err):

    let strings = ["1", "2", "3", "4", "5"];
    // перевести строки в числа и суммировать их
    let sum: Result<i32, _> = strings
        .iter()
        .try_fold(0, |acc, &s| match s.parse::<i32>() {
            Ok(num) => Ok(acc + num),
            Err(e) => Err(e),
        });

    match sum {
        Ok(total) => println!("Total: {}", total), // 15
        Err(e) => println!("Parse error: {}", e), }