Что такое замыкание?
Замыкание, также известное как анонимная функция или лямбда-выражение, является функцией, определенной в контексте его использования. Они позволяют определять функции в пределах области действия другой функции и могут захватывать переменные из их окружающей области действия.
В Rust замыкания определяются с помощью пары вертикальных черт (| |
), которые действуют как скобки функции. Между этими символами задаются аргументы функции, за которыми следует блок кода, составляющий тело функции. Рассмотрим основной пример:
let add_one = |x| x + 1;
println!("{}", add_one(5)); // вывод: 6
Здесь add_one
является замыканием, которое принимает один аргумент, x
, и возвращает x + 1
.
Захват переменных
Сильной особенностью замыканий является их способность захватывать переменные из окружающей среды, обычно называемая upvar capturing. Переменные могут быть зафиксированы тремя способами:
- By reference:
&T
- По изменяемой ссылке:
&mut T
- По значению:
Т
Rust попытается сделать вывод о наименее ограничительном выборе на основе того, как вы используете переменные в замыкании. Давайте продемонстрируем следующее:
let x = 7;
let borrow = || println!("borrow: {}", x);
let borrow_mut = || println!("borrow_mut: {}", x);
let move = move || println!("move: {}", x);
borrow();
borrow_mut();
move();
В этом примере заимствование захватывает x по ссылке (&T
), borrow_mut
захватывает x
по изменяемой ссылке (&mut T
) и перемещает захваченную переменную x по значению (T
). Ключевое слово move
используется для принудительной передачи в замыкание во владение значения, которые оно использует.
Вывод типа и дженерики
Реализация замыкания в Rust блещет, когда речь заходит о выводе типа и дженериках. В то время как определения функций требуют явных типов для их аргументов, замыкания Rust не требуют. Кроме того, в отличие от функций, замыкания могут быть дженериками для своих типов ввода. Вот пример:
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5);
В этом коде example_closure
можете использовать строку или i32
, поскольку он является универсальным.
Fn, FnMut, и FnOnce
В Rust мы имеем три трейта для представления трех видов замыканий: Fn
, FnMut
и FnOnce
, которые соответствуют трем способам захвата переменных:
Fn
принимает переменные по ссылке (&T
)FnMut
принимает переменные по изменяемой ссылке (&mut T
)FnOnce
принимает переменные по значению (T
)
Каждое замыкание реализует один или несколько из этих трейтов. Если замыкание реализует Fn
, то оно также реализует FnMut
и FnOnce
. Если оно реализует FnMut
, оно также реализует FnOnce
.
let x = 7;
let fn_closure = || println!("{}", x); // Реализует Fn
let mut fnmut_closure = || println!("{}", x); // Реализует FnMut
let fnonce_closure = move || println!("{}", x); // Реализует FnOnce
Практическое использование замыканий
Существует множество способов использования замыканий в Rust для написания краткого и гибкого кода. Давайте обсудим некоторые из них:
В качестве аргументов функций
Функции в Rust могут принимать замыкания в качестве аргументов. Это часто используется в функциях более высокого порядка, которые принимают другие функции в качестве аргументов или возвращают их в качестве результатов.
Вот пример, где мы используем замыкание для реализации функции map
, подобной функции map
на итераторах:
fn map<F>(value: i32, func: F) -> i32
where
F: Fn(i32) -> i32,
{
func(value)
}
let double = |x| x * 2;
println!("{}", map(5, double)); // Выводит: 10
В этом примере map
принимает i32
, а замыкание в качестве аргументов применяет замыкание к значению и возвращает результат.
Замыкания в структурах данных
Хранение замыкания в структуре для последующего выполнения является типичным шаблоном в Rust, особенно в конструкциях, управляемых событиями или обратными вызовами.
struct Button<F> {
callback: F,
}
impl<F: Fn()> Button<F> {
fn new(callback: F) -> Self {
Self { callback }
}
fn click(&self) {
(self.callback)();
}
}
let button = Button::new(|| println!("Кнопка нажата!"));
button.click(); // Выводит: Кнопка нажата!
В этом примере создается структура Button
, хранящая замыкание как обратный вызов. При нажатии кнопки вызывается обратный вызов.
Замыкания с итераторами
Замыкания широко используются с итераторами в Rust. Они обеспечивают краткий способ выполнения операций над каждым элементом сбора. Например:
let numbers = vec![1, 2, 3, 4, 5];
let squares: Vec<_> = numbers.iter().map(|x| x * x).collect();
println!("{:?}", squares); // Выводит: [1, 4, 9, 16, 25]
В этом примере замыкание | x | x * x
передается методу map
итератора, который применяет замыкание к каждому элементу.
В заключение
Замыкание Rust - это захватывающая параметры функция, обеспечивающая гибкость и лаконичность кода. Оно гибко захватывают в свою среду, может использоваться аналогично функциям и приносит такие преимущества, как определение охвата видимости и типа. Оно может использоваться в качестве функциональных параметров, храниться в структурах данных для последующего использования и в паре с итераторами для эффективной обработки данных.