Введение
В этом практическом занятии мы узнаем, что при написании функций на Rust, которые принимают замыкание в качестве входного параметра, полный тип замыкания должен быть аннотирован с использованием одного из трейтов: Fn, FnMut или FnOnce, которые определяют, как замыкание использует захваченное значение, по ссылке, по изменяемой ссылке или по значению. Компилятор захватывает переменные в наименее ограничивающем порядке, исходя из выбранного для замыкания трейта.
Примечание: Если практическое занятие не задает имя файла, вы можете использовать любое имя файла, которое хотите. Например, вы можете использовать
main.rs, скомпилировать и запустить его с помощьюrustc main.rs &&./main.
В качестве входных параметров
Хотя Rust обычно выбирает, как захватывать переменные, практически без явной аннотации типа, такая неоднозначность не допускается при написании функций. Когда замыкание передается в качестве входного параметра, его полный тип должен быть аннотирован с использованием одного из нескольких трейтов, которые определяются тем, как замыкание обрабатывает захваченное значение. В порядке убывания степени ограничения они следующие:
Fn: замыкание использует захваченное значение по ссылке (&T)FnMut: замыкание использует захваченное значение по изменяемой ссылке (&mut T)FnOnce: замыкание использует захваченное значение по значению (T)
По отдельности для каждой переменной компилятор будет захватывать переменные в наименее ограничивающем порядке.
Например, рассмотрим параметр, аннотированный как FnOnce. Это означает, что замыкание может захватывать по &T, &mut T или T, но компилятор в конечном итоге будет выбирать в зависимости от того, как захваченные переменные используются в замыкании.
Это происходит потому, что если возможно совершение move, то и любая другая форма ссылочного доступа должна быть возможной. Обратите внимание, что обратное не верно. Если параметр аннотирован как Fn, то захват переменных по &mut T или T не допускается. Однако, захват по &T допустим.
В следующем примере попробуйте поменять местами использование Fn, FnMut и FnOnce, чтобы увидеть, что произойдет:
// Функция, которая принимает замыкание в качестве аргумента и вызывает его.
// <F> обозначает, что F - "обобщенный тип параметра"
fn apply<F>(f: F) where
// Замыкание не имеет входных параметров и не возвращает ничего.
F: FnOnce() {
// ^ TODO: Попробуйте изменить это на `Fn` или `FnMut`.
f();
}
// Функция, которая принимает замыкание и возвращает `i32`.
fn apply_to_3<F>(f: F) -> i32 where
// Замыкание принимает `i32` и возвращает `i32`.
F: Fn(i32) -> i32 {
f(3)
}
fn main() {
use std::mem;
let greeting = "hello";
// Тип, не поддерживающий копирование.
// `to_owned` создает собственное владение над данными из ссылочного доступа
let mut farewell = "goodbye".to_owned();
// Захват двух переменных: `greeting` по ссылке и
// `farewell` по значению.
let diary = || {
// `greeting` по ссылке: требует `Fn`.
println!("I said {}.", greeting);
// Мутация заставляет `farewell` быть захваченным по
// изменяемой ссылке. Теперь требует `FnMut`.
farewell.push_str("!!!");
println!("Then I screamed {}.", farewell);
println!("Now I can sleep. zzzzz");
// Вручную вызывая drop, заставляем `farewell` быть захваченным по значению. Теперь требует `FnOnce`.
mem::drop(farewell);
};
// Вызываем функцию, которая применяет замыкание.
apply(diary);
// `double` удовлетворяет ограничениям `apply_to_3`
let double = |x| 2 * x;
println!("3 doubled: {}", apply_to_3(double));
}
Резюме
Поздравляем! Вы завершили практическое занятие "В качестве входных параметров". Вы можете выполнить больше практических занятий в LabEx, чтобы улучшить свои навыки.