Затварянията са мощна функция на Rust, която позволява на функциите да улавят заобикалящата ги среда. Разбирането на затварянията като професионален разработчик е от решаващо значение за писането на ефективен, модулен и кратък код. В тази статия ще разгледаме концепцията за затваряния и как те могат да улавят променливи, да взаимодействат с входни и изходни параметри и др. Ще използваме илюстрации и множество примери, за да осигурим задълбочено разбиране на затварянията на Rust. Нека да започнем нашето пътуване през света на затварянията!

Затваряния: Функциите за улавяне на околната среда

Затварянията са функции, които могат да улавят заобикалящата ги среда и да използват променливи извън своя обхват. |…| синтаксисът понякога се използва за дефиниране на затваряне.

Считайте затворите за камера, която може да заснеме моментна снимка на заобикалящата ги среда. Те могат да „запомнят“ стойностите на променливите по време на моментната снимка и да ги използват по-късно, когато бъдат извикани.

let x = 3;
let closure = |num| num * x;
let result = closure(2);

Затварянето в този пример улавя променливата „x“ от нейната среда и я умножава по входа „num“.

Заснемане: гъвкавата природа на затварянията

Затварянията могат да улавят променливи по три начина, в зависимост от необходимата функционалност:

  1. По справка: &T
  2. Чрез променлива препратка: &mut T
  3. По стойност: Т

Представете си затваряния, улавящи променливи като различни типове контейнери:

  1. Чрез справка: Прозрачен контейнер, който ви позволява да виждате и използвате променливата, като същевременно не ви позволява да я променяте или премествате.
  2. Чрез променлива препратка: Контейнер, който позволява променливата да бъде използвана, модифицирана и пренаредена, но не и премахната.
  3. По стойност: Контейнер, който ви позволява да извлечете променливата и да я използвате, както сметнете за добре, включително да я преместите на друго място.
let x = 3;
let by_ref = || println!("{}", x); // Captures x by reference
let mut y = 5;
let by_mut_ref = || { y += 1; println!("{}", y); }; // Captures y by mutable reference
let by_value = move || println!("{}", x); // Captures x by value

Затварянията като входни параметри: Изкуството на анотирането на черти

Когато затваряне се използва като входен параметър, неговият пълен тип трябва да бъде анотиран с една от следните характеристики:

1. Fn: Затварянето се отнася до уловената стойност. (&T)
2. FnMut: Затварянето използва уловената стойност чрез променливата препратка. (&mut T)
3. FnOnce: Затварянето използва уловената стойност по стойност. (T)

Считайте тези характеристики за различни видове нива на достъп, предоставени на посетител във вашия дом:

  1. Fn: Посетителят може да инспектира вашите вещи, но не може да ги докосва или променя.
  2. FnMut: Гостът може да докосне и промени вашите вещи, но те не могат да бъдат отнети.
  3. FnOnce: Гостът има възможност да вземе вашите вещи със себе си, когато си тръгне.
fn apply<F: Fn(i32) -> i32>(f: F, num: i32) -> i32 {
    f(num)
}

let double = |x| x * 2;
let result = apply(double, 4);

Анонимност на типа: генерични продукти в света на затварянията

Затварянията имат анонимни типове, които трябва да се използват с генерични, когато се използват като функционални параметри.

Считайте, че затварянията са хора, облечени в маски на маскен бал. Не знаете кои са те, но все пак можете да взаимодействате с тях въз основа на тяхното поведение и характеристики.

fn call_twice<F>(closure: F, value: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    closure(value) + closure(value)
}

let add_five = |x| x + 5;
let result = call_twice(add_five, 10);

Ние използваме генерични с клаузата `where` в този пример, за да ограничим типа на затваряне въз основа на неговото поведение, което ни позволява да взаимодействаме с него, без да знаем точния му тип.

Входни функции: Предаване на функции като параметри

Затварянията и функциите могат да се използват като аргументи. Всяка функция, която удовлетворява границата на чертата на това затваряне, може да бъде предадена като параметър при деклариране на функция, която приема затваряне като параметър.

Вземете пъзел с разнообразни фигури. Затварянията са части с неправилна форма, докато функциите са правилни форми като квадрати или кръгове. Ако слот за пъзел приема уникална форма, той може да приеме и стандартна форма, която се вписва в границите на уникалната форма.

fn square(x: i32) -> i32 {
    x * x
}

let result = apply(square, 4); // Passing a function instead of a closure

Затваряния като изходни параметри: Връщане на неизвестното

Възможно е да се върнат затваряния като изходни параметри, но тъй като анонимните типове затваряния са неизвестни по дефиниция, трябва да използваме impl Trait, за да го направим.

Да приемем, че затварянията са опаковани подаръци. Давате на някого подарък, без да разкривате какво има вътре, когато връщате затваряне от функция. Синтаксисът на impl Trait е подобен на опаковъчната хартия, тъй като прикрива съдържанието.

Следните са валидни характеристики за връщане на затваряне:

  1. Fn
  2. FnMut
  3. FnOnce
fn create_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

let add_five = create_adder(5);
let result = add_five(10);

Функцията create_adder в този пример връща затваряне, което добавя x към своя вход. Затварянето улавя x по стойност с ключовата дума `move` и функцията връща затваряне с impl Fn(i32) -› i32.

Затварянията са мощна и адаптивна функция в Rust, която ви позволява да улавяте и използвате променливи от средата. Ще бъдете по-добре подготвени да пишете ефективен, модулен и кратък код на Rust, ако разбирате как затварянията могат да улавят променливи, да взаимодействат с входни и изходни параметри и да работят с анонимност на типа.