Указатели на функции
Мы говорили о том, как передавать замыкания в функции; вы также можете передавать обычные функции в функции! Эта техника полезна, когда вы хотите передать функцию, которую уже определили, вместо определения нового замыкания. Функции преобразуются в тип fn
(с маленькой буквы f), не путать с треитом замыкания Fn
. Тип fn
называется указателем на функцию. Передача функций с указателями на функции позволит вам использовать функции в качестве аргументов для других функций.
Синтаксис для указания того, что параметр является указателем на функцию, похож на синтаксис замыканий, как показано в Листинге 19-27, где мы определили функцию add_one
, которая прибавляет 1 к своему параметру. Функция do_twice
принимает два параметра: указатель на функцию на любую функцию, которая принимает параметр типа i32
и возвращает i32
, и одно значение типа i32
. Функция do_twice
вызывает функцию f
дважды, передав ей значение arg
, а затем складывает результаты двух вызовов функций. Функция main
вызывает do_twice
с аргументами add_one
и 5
.
Имя файла: src/main.rs
fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is: {answer}");
}
Листинг 19-27: Использование типа fn
для приема указателя на функцию в качестве аргумента
Этот код выводит The answer is: 12
. Мы указываем, что параметр f
в do_twice
является fn
, который принимает один параметр типа i32
и возвращает i32
. Затем мы можем вызвать f
в теле do_twice
. В main
мы можем передать имя функции add_one
в качестве первого аргумента для do_twice
.
В отличие от замыканий, fn
является типом, а не треитом, поэтому мы напрямую указываем fn
в качестве типа параметра, а не объявляем обобщенный тип параметра с одним из треитов Fn
в качестве ограничения треита.
Указатели на функции реализуют все три треита замыканий (Fn
, FnMut
и FnOnce
), что означает, что вы всегда можете передать указатель на функцию в качестве аргумента для функции, которая ожидает замыкание. Лучше всего писать функции с использованием обобщенного типа и одного из треитов замыканий, чтобы ваши функции могли принимать как функции, так и замыкания.
Тем не менее, один пример того, когда вы захотите принимать только fn
и не замыкания, — это когда взаимодействуете с внешним кодом, который не имеет замыканий: C-функции могут принимать функции в качестве аргументов, но у C нет замыканий.
В качестве примера того, где можно использовать либо инлайн-определенное замыкание, либо именованную функцию, рассмотрим использование метода map
, предоставляемого треитом Iterator
в стандартной библиотеке. Чтобы использовать функцию map
для преобразования вектора чисел в вектор строк, мы могли бы использовать замыкание, вот так:
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(|i| i.to_string())
.collect();
Или мы могли бы назвать функцию в качестве аргумента для map
вместо замыкания, вот так:
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(ToString::to_string)
.collect();
Обратите внимание, что мы должны использовать полностью квалифицированный синтаксис, о котором мы говорили в разделе "Расширенные треиты", потому что есть несколько функций с именем to_string
.
Здесь мы используем функцию to_string
, определенную в треите ToString
, который стандартная библиотека реализовала для любого типа, который реализует Display
.
Помните из раздела "Значения перечислений", что имя каждой варианта перечисления, которое мы определяем, также становится инициализаторной функцией. Мы можем использовать эти инициализаторные функции в качестве указателей на функции, которые реализуют треиты замыканий, что означает, что мы можем указать инициализаторные функции в качестве аргументов для методов, которые принимают замыкания, вот так:
enum Status {
Value(u32),
Stop,
}
let list_of_statuses: Vec<Status> = (0u32..20)
.map(Status::Value)
.collect();
Здесь мы создаем экземпляры Status::Value
с использованием каждого значения u32
в диапазоне, для которого вызывается map
, с использованием инициализаторной функции Status::Value
. Некоторые предпочитают этот стиль, а некоторые предпочитают использовать замыкания. Они компилируются в один и тот же код, поэтому используйте тот стиль, который вам кажется более понятным.