Продвинутые функции и замыкания
В этом разделе рассматриваются некоторые продвинутые возможности, относящиеся к функциям и замыканиям, такие как указатели функций и возвращаемые замыкания.
Указатели функций
Мы уже обсуждали, как передавать замыкания в функции; но также можно передавать обычные функции в функции! Эта техника полезна, когда вы хотите передать ранее созданную функцию, а не определять новое замыкание. Функции соответствуют типу fn
(со строчной буквой f), не путать с трейтом замыкания Fn
. Тип fn
называется указателем функции. Передача функций с помощью указателей функций позволяет использовать функции в качестве аргументов других функций.
Для указания того, что параметр является указателем на функцию, используется синтаксис, такой же, как и для замыканий, что демонстрируется в листинге 19-27, где мы определили функцию add_one
, которая добавляет единицу к переданному ей параметру. Функция do_twice
принимает два параметра: указатель на любую функцию, принимающую параметр i32
и возвращающую i32
, и число типа i32
. Функция do_twice
дважды вызывает функцию f
, передавая ей значение arg
, а затем складывает полученные результаты. Функция main
вызывает функцию do_twice
с аргументами add_one
и 5
.
Файл: src/main.rs
#![allow(unused)] fn main() { {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-27/src/main.rs}} }
Этот код выводит Ответ: 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
для преобразования вектора чисел в вектор строк, мы можем использовать замыкание, например, так:
#![allow(unused)] fn main() { {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-15-map-closure/src/main.rs:here}} }
Или мы можем использовать функцию в качестве аргумента map
вместо замыкания, например, так:
#![allow(unused)] fn main() { {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-16-map-function/src/main.rs:here}} }
Обратите внимание, что мы должны использовать полный синтаксис, о котором мы говорили ранее в разделе "Продвинутые трейты", потому что доступно несколько функций с именем to_string
. Здесь мы используем функцию to_string
определённую в типаже ToString
, который реализован в стандартной библиотеке для любого типа реализующего типаж Display
.
Вспомните из раздела "Значения перечислений" главы 6, что имя каждого определённого нами варианта перечисления также становится функцией-инициализатором. Мы можем использовать эти инициализаторы в качестве указателей на функции, реализующих трейты замыканий, что означает, что мы можем использовать инициализаторы в качестве аргументов для методов, принимающих замыкания, например, так:
#![allow(unused)] fn main() { {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-17-map-initializer/src/main.rs:here}} }
Здесь мы создаём экземпляры Status::Value
, используя каждое значение u32
в диапазоне (0..20), с которым вызывается map
с помощью функции инициализатора Status::Value
. Некоторые люди предпочитают этот стиль, а некоторые предпочитают использовать замыкания. Оба варианта компилируется в один и тот же код, поэтому используйте любой стиль, который вам понятнее.
Возврат замыканий
Замыкания представлены трейтами, что означает, что вы не можете возвращать замыкания из функций. В большинстве случаев, когда вам захочется вернуть трейт, вы можете использовать конкретный тип, реализующий этот трейт, в качестве возвращаемого значения функции. Однако вы не можете сделать подобного с замыканиями, поскольку у них не может быть конкретного типа, который можно было бы вернуть; например, вы не можете использовать указатель на функцию fn
в качестве возвращаемого типа.
Следующий код пытается напрямую вернуть замыкание, но он не компилируется:
{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-18-returns-closure/src/lib.rs}}
Ошибка компилятора выглядит следующим образом:
{{#include ../listings/ch19-advanced-features/no-listing-18-returns-closure/output.txt}}
Ошибка снова ссылается на типаж Sized
! Rust не знает, сколько памяти нужно будет выделить для замыкания. Мы видели решение этой проблемы ранее. Мы можем использовать типаж-объект:
{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-19-returns-closure-trait-object/src/lib.rs}}
Этот код просто отлично компилируется. Для получения дополнительной информации об типаж-объектах обратитесь к разделу "Использование типаж-объектов которые допускают значения разных типов" главы 17.
Далее давайте посмотрим на макросы!