Анонимные функции в отличие от обычных функций не имеют имени и могут сохраняться в переменную.
Формальное определение анонимной функции:
let переменная = | параметры | {
// выполняемые действия
};
Анонимная функция определяется без оператора fn. Ее определение фактически начинается с определения параметров,
которые помещаются между двумя вертикальными чертами: | параметры |. Затем в фигурных скобках идут выполняемые
действия. Причем после закрывающей фигурной скобки ставится точка с запятой. При этом анонимная функция сохраняется в переменной.
Вызов анонимной функции похож на вызов обычной функции, только для вызова применяется имя переменной:
название_замыкания(значения_для_параметров);
Рассмотрим простейший пример:
fn main(){
let square = |n: i32|{
let result = n * n;
println!("Квадрат числа {} равен {}", n, result);
};
square(4);
square(5);
square(6);
}
Здесь определена переменная square, которая хранит анонимную функцию. Эта функция принимает один параметр типа i32 и
выводит на консоль его квадрат.
Консольный вывод программы:
Квадрат числа 4 равен 16 Квадрат числа 5 равен 25 Квадрат числа 6 равен 36
Анонимные функции также могут возвращать значение:
fn main(){
let sum = |a: i32, b: i32| -> i32{
a + b
};
let sum_of_5_and_4 = sum(5, 4);
println!("Сумма 5 и 4 равна: {}", sum_of_5_and_4);
}
Здесь определена переменная sum, которая представляет анонимную функцию, принимающую два параметра - два числа и возвращающую их сумму.
И по имени переменной мы можем вызвать эту анонимную функцию и получить ее результат.
Если анонимная функция не принимает никаких параметров, то просто указываются две вертикальных черты:
let hello =||{ // определение анонимной функции
println!("hello");
};
hello(); // вызов анонимной функции
В отличие от обычной функции в анонимной можно не указывать типы параметров и возвращаемого результата. Для анонимной функции компилятор сам
способен определить типы параметров и результата. Например, мы могли бы переписать вышеопределенную анонимную функцию sum следующим образом:
fn main(){
let sum = |a, b|{ // типы параметров и результата не указаны
a + b
};
let sum_of_5_and_4 = sum(5, 4);
println!("Сумма 5 и 4 равна: {}", sum_of_5_and_4);
}
Более того, так как анонимная функция содержит лишь одно выполняемое действие, то мы можем и фигурные скобки убрать:
fn main(){
let sum = |a, b| a + b;
let sum_of_5_and_4 = sum(5, 4);
println!("Сумма 5 и 4 равна: {}", sum_of_5_and_4);
}
Однако стоит учитывать, что типы устанавливаются при первом вызове анонимной функции. И при последующих вызовах типы параметров и результатов анонимной функции должны соответствовать первому вызову. Например, в следующем случае мы получим ошибку при компиляции:
fn main(){
let sum = |a, b| a + b;
let sum_of_int = sum(5, 4);
let sum_of_float = sum(5.3, 4.2);
println!("Сумма целых чисел: {}", sum_of_int);
println!("Сумма дробных чисел: {}", sum_of_float);
}
Потому что после первого вызова анонимной функции sum() компилятор ожидает, что далее она будет принимать два целых числа. Однако во втором вызове
в данном случае передаются два числа с плавающей точкой.
От анонимных функций следуюет отличать блоки кода, которые присваиваются функциям. Так, рассмотрим следующий пример:
fn main(){
let block = {
println!("block starts");
let n = 5;
println!("n={n}");
println!("block ends");
6
};
println!("block={block}");
}
Здесь переменной block присваивается блок кода. В этом блоке кода могут идти разнообразные инструкции и выражения. Для примера я здесь вывожу некоторые диагностические сообщения и определяю переменную n. Блок кода, как и тело функции, заканчивается значением, которое возвращается из этого блока и именно это значение в реальности будет присваиваться переменной block. Посмотрим на консольный вывод:
block starts n=5 block ends block=6
Здесь мы видим, что блок кода выполняется сразу же при определении. А его возвращаемое значение - число 6 присваивается переменной block.
Посмотрим чуть более сложный пример с затенением:
fn main(){
let n = {
println!("block starts");
let n = 5;
println!("n in block={n}");
println!("block ends");
};
println!("n in main={:?}", n);
}
Здесь блок кода присваивается переменной n, а внутри блока кода переопределяется переменная n. Какое значение в конечном счете после завершения блока получит переменная n? Посмотрим на консольный вывод:
block starts n in block=5 block ends n in main=()
Вне зависимости, как внутри блока определяется вложенная переменная n, внешняя переменная n получит то значение, которое возвращается блоком кода.
А в данном случае блок коа явно не возвращает никакого значения, поэтому фактически это будет значение unit или ()