Fonctions et closures avancées

RustRustBeginner
Pratiquer maintenant

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

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Bienvenue dans Advanced Functions and Closures. Ce laboratoire est une partie du Rust Book. Vous pouvez pratiquer vos compétences Rust dans LabEx.

Dans ce laboratoire, nous allons explorer les fonctionnalités avancées des fonctions et des closures, y compris les pointeurs de fonction et la restitution de closures.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL 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(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) 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{{"Fonctions et closures avancées"}} rust/integer_types -.-> lab-100450{{"Fonctions et closures avancées"}} rust/function_syntax -.-> lab-100450{{"Fonctions et closures avancées"}} rust/expressions_statements -.-> lab-100450{{"Fonctions et closures avancées"}} rust/method_syntax -.-> lab-100450{{"Fonctions et closures avancées"}} rust/operator_overloading -.-> lab-100450{{"Fonctions et closures avancées"}} end

Advanced Functions and Closures

Cette section explore certaines fonctionnalités avancées liées aux fonctions et aux closures, y compris les pointeurs de fonction et la restitution de closures.

Pointeurs de fonction

Nous avons parlé de la manière de passer des closures à des fonctions ; vous pouvez également passer des fonctions ordinaires à des fonctions! Cette technique est utile lorsque vous voulez passer une fonction que vous avez déjà définie plutôt que de définir une nouvelle closure. Les fonctions se coercitent en le type fn (avec une f en minuscules), ne pas confondre avec le trait de closure Fn. Le type fn est appelé un pointeur de fonction. Passer des fonctions avec des pointeurs de fonction vous permettra d'utiliser des fonctions comme arguments pour d'autres fonctions.

La syntaxe pour spécifier qu'un paramètre est un pointeur de fonction est similaire à celle des closures, comme montré dans la Liste 19-27, où nous avons défini une fonction add_one qui ajoute 1 à son paramètre. La fonction do_twice prend deux paramètres : un pointeur de fonction vers n'importe quelle fonction qui prend un paramètre de type i32 et renvoie un i32, et une valeur i32. La fonction do_twice appelle la fonction f deux fois, en lui passant la valeur arg, puis ajoute les deux résultats d'appel de fonction ensemble. La fonction main appelle do_twice avec les arguments add_one et 5.

Nom de fichier : 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}");
}

Liste 19-27 : Utilisation du type fn pour accepter un pointeur de fonction en tant qu'argument

Ce code affiche The answer is: 12. Nous spécifions que le paramètre f dans do_twice est un fn qui prend un paramètre de type i32 et renvoie un i32. Nous pouvons ensuite appeler f dans le corps de do_twice. Dans main, nous pouvons passer le nom de la fonction add_one comme premier argument à do_twice.

Contrairement aux closures, fn est un type plutôt qu'un trait, donc nous spécifions fn comme type de paramètre directement plutôt que de déclarer un paramètre de type générique avec l'un des traits Fn comme limite de trait.

Les pointeurs de fonction implémentent les trois traits de closure (Fn, FnMut et FnOnce), ce qui signifie que vous pouvez toujours passer un pointeur de fonction en tant qu'argument pour une fonction qui attend une closure. Il est préférable d'écrire des fonctions en utilisant un type générique et l'un des traits de closure de sorte que vos fonctions puissent accepter à la fois des fonctions et des closures.

Cela étant dit, un exemple de cas où vous voudriez seulement accepter fn et non des closures est lorsqu'il s'agit d'interagir avec du code externe qui n'a pas de closures : les fonctions C peuvent accepter des fonctions en tant qu'arguments, mais C n'a pas de closures.

En tant qu'exemple de cas où vous pourriez utiliser soit une closure définie en ligne soit une fonction nommée, regardons une utilisation de la méthode map fournie par le trait Iterator dans la bibliothèque standard. Pour utiliser la fonction map pour transformer un vecteur de nombres en un vecteur de chaînes de caractères, nous pourrions utiliser une closure, comme ceci :

let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
 .iter()
 .map(|i| i.to_string())
 .collect();

Ou nous pourrions nommer une fonction comme argument de map au lieu de la closure, comme ceci :

let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
 .iter()
 .map(ToString::to_string)
 .collect();

Notez que nous devons utiliser la syntaxe qualifiée en entier dont nous avons parlé dans "Traits avancés" car il existe plusieurs fonctions disponibles nommées to_string.

Ici, nous utilisons la fonction to_string définie dans le trait ToString, que la bibliothèque standard a implémenté pour tout type qui implémente Display.

Rappelez-vous de "Valeurs d'enumération" que le nom de chaque variant d'enumération que nous définissons devient également une fonction d'initialisation. Nous pouvons utiliser ces fonctions d'initialisation comme pointeurs de fonction qui implémentent les traits de closure, ce qui signifie que nous pouvons spécifier les fonctions d'initialisation comme arguments pour des méthodes qui prennent des closures, comme ceci :

enum Status {
    Value(u32),
    Stop,
}

let list_of_statuses: Vec<Status> = (0u32..20)
 .map(Status::Value)
 .collect();

Ici, nous créons des instances Status::Value en utilisant chaque valeur u32 dans la plage sur laquelle map est appelé en utilisant la fonction d'initialisation de Status::Value. Certaines personnes préfèrent ce style et certaines personnes préfèrent utiliser des closures. Elles se compilent en même code, donc utilisez le style qui est le plus clair pour vous.

Retourner des closures

Les closures sont représentées par des traits, ce qui signifie que vous ne pouvez pas retourner directement des closures. Dans la plupart des cas où vous voudriez retourner un trait, vous pouvez au lieu de cela utiliser le type concret qui implémente le trait comme valeur de retour de la fonction. Cependant, vous ne pouvez pas faire cela avec les closures car elles n'ont pas de type concret qui soit retournable ; vous n'êtes pas autorisé à utiliser le pointeur de fonction fn comme type de retour, par exemple.

Le code suivant essaie de retourner directement une closure, mais il ne compilera pas :

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

L'erreur du compilateur est la suivante :

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 {
  |                         ~~~~~~~~~~~~~~~~~~~

L'erreur renvoie à nouveau au trait Sized! Rust ne sait pas combien d'espace il devra utiliser pour stocker la closure. Nous avons vu une solution à ce problème plus tôt. Nous pouvons utiliser un objet de trait :

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

Ce code compilera parfaitement. Pour en savoir plus sur les objets de trait, consultez "Utiliser des objets de trait qui autorisent des valeurs de différents types".

Ensuite, regardons les macros!

Résumé

Félicitations! Vous avez terminé le laboratoire sur les fonctions et les closures avancées. Vous pouvez pratiquer d'autres laboratoires dans LabEx pour améliorer vos compétences.