Расширенные функции и замыкания

RustRustBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

Добро пожаловать в Advanced Functions and Closures. Этот лаба является частью Rust Book. Вы можете практиковать свои навыки Rust в LabEx.

В этом лабе мы будем изучать продвинутые функции и замыкания, включая указатели на функции и возврат замыканий.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/AdvancedTopicsGroup(["Advanced Topics"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/DataTypesGroup -.-> rust/integer_types("Integer Types") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/AdvancedTopicsGroup -.-> rust/operator_overloading("Traits for Operator Overloading") subgraph Lab Skills rust/variable_declarations -.-> lab-100450{{"Расширенные функции и замыкания"}} rust/integer_types -.-> lab-100450{{"Расширенные функции и замыкания"}} rust/function_syntax -.-> lab-100450{{"Расширенные функции и замыкания"}} rust/expressions_statements -.-> lab-100450{{"Расширенные функции и замыкания"}} rust/method_syntax -.-> lab-100450{{"Расширенные функции и замыкания"}} rust/operator_overloading -.-> lab-100450{{"Расширенные функции и замыкания"}} end

Advanced Functions and Closures

В этом разделе исследуются некоторые продвинутые функции, связанные с функциями и замыканиями, включая указатели на функции и возврат замыканий.

Указатели на функции

Мы говорили о том, как передавать замыкания в функции; вы также можете передавать обычные функции в функции! Эта техника полезна, когда вы хотите передать функцию, которую уже определили, вместо определения нового замыкания. Функции преобразуются в тип 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. Некоторые предпочитают этот стиль, а некоторые предпочитают использовать замыкания. Они компилируются в один и тот же код, поэтому используйте тот стиль, который вам кажется более понятным.

Возвращение замыканий

Замыкания представляются трейтами, что означает, что вы не можете напрямую возвращать замыкания. В большинстве случаев, когда вы хотите вернуть трейт, вы можете вместо этого использовать конкретный тип, который реализует трейт, в качестве возвращаемого значения функции. Однако вы не можете сделать этого с замыканиями, потому что у них нет конкретного типа, который можно вернуть; например, вы не можете использовать указатель на функцию fn в качестве возвращаемого типа.

Следующий код пытается напрямую вернуть замыкание, но он не скомпилируется:

fn returns_closure() -> dyn Fn(i32) -> i32 {
    |x| x + 1
}

Ошибка компилятора выглядит так:

error[E0746]: return type cannot have an unboxed trait object
 --> src/lib.rs:1:25
  |
1 | fn returns_closure() -> dyn Fn(i32) -> i32 {
  |                         ^^^^^^^^^^^^^^^^^^ doesn't have a size known at
compile-time
  |
  = note: for information on `impl Trait`, see
<https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-
implement-traits>
help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of
type `[closure@src/lib.rs:2:5: 2:14]`, which implements `Fn(i32) -> i32`
  |
1 | fn returns_closure() -> impl Fn(i32) -> i32 {
  |                         ~~~~~~~~~~~~~~~~~~~

Ошибка снова ссылается на трейт Sized! Rust не знает, сколько места ему потребуется для хранения замыкания. Мы видели решение этой проблемы ранее. Мы можем использовать объект трейта:

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

Этот код скомпилируется без ошибок. Для получения дополнительной информации о объектах трейтов, обратитесь к разделу "Использование объектов трейтов, которые допускают значения разных типов".

Далее давайте посмотрим на макросы!

Резюме

Поздравляем! Вы завершили лабораторную работу по продвинутым функциям и замыканиям. Вы можете выполнить больше лабораторных работ в LabEx, чтобы улучшить свои навыки.