Définir des fonctions Rust dans LabEx

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 Functions. Ce laboratoire est une partie du Rust Book. Vous pouvez pratiquer vos compétences Rust dans LabEx.

Dans ce laboratoire, vous allez apprendre à définir et à appeler des fonctions en Rust en utilisant le mot clé fn et la convention de nommage en camelCase classique.

Fonctions

Les fonctions sont omniprésentes dans le code Rust. Vous avez déjà vu l'une des fonctions les plus importantes du langage : la fonction main, qui est le point d'entrée de nombreux programmes. Vous avez également vu le mot clé fn, qui vous permet de déclarer de nouvelles fonctions.

Créez un nouveau projet appelé functions :

cargo new functions
cd functions

Le code Rust utilise le snake case comme style conventionnel pour les noms de fonctions et de variables, dans lequel toutes les lettres sont en minuscules et les tirets du bas séparent les mots. Voici un programme qui contient une définition d'une fonction exemple :

Nom de fichier : src/main.rs

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

Nous définissons une fonction en Rust en entrant fn suivi du nom de la fonction et d'un ensemble de parenthèses. Les accolades indiquent au compilateur où commence et se termine le corps de la fonction.

Nous pouvons appeler n'importe quelle fonction que nous avons définie en entrant son nom suivi d'un ensemble de parenthèses. Comme another_function est définie dans le programme, elle peut être appelée à l'intérieur de la fonction main. Notez que nous avons défini another_function après la fonction main dans le code source ; nous aurions également pu la définir avant. Rust n'a pas besoin de savoir où vous définissez vos fonctions, seulement qu'elles sont définies quelque part dans une portée visible par l'appelant.

Commencez un nouveau projet binaire nommé functions pour explorer les fonctions plus en détail. Placez l'exemple another_function dans src/main.rs et exécutez-le. Vous devriez voir la sortie suivante :

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/functions`
Hello, world!
Another function.

Les lignes s'exécutent dans l'ordre dans lequel elles apparaissent dans la fonction main. Tout d'abord, le message "Hello, world!" s'affiche, puis another_function est appelée et son message s'affiche.

Paramètres

Nous pouvons définir des fonctions qui ont des paramètres, qui sont des variables spéciales qui font partie de la signature d'une fonction. Lorsqu'une fonction a des paramètres, vous pouvez lui fournir des valeurs concrètes pour ces paramètres. Technologiquement, les valeurs concrètes sont appelées arguments, mais dans la conversation ordinaire, les gens ont tendance à utiliser les mots paramètre et argument de manière interchangeable pour les variables dans la définition d'une fonction ou les valeurs concrètes passées lorsqu'on appelle une fonction.

Dans cette version de another_function, nous ajoutons un paramètre :

Nom de fichier : src/main.rs

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

Essayez d'exécuter ce programme ; vous devriez obtenir la sortie suivante :

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 1.21s
     Running `target/debug/functions`
The value of x is: 5

La déclaration de another_function a un paramètre nommé x. Le type de x est spécifié comme étant i32. Lorsque nous passons 5 à another_function, la macro println! met 5 à l'endroit où la paire d'accolades contenant x était dans la chaîne de formatage.

Dans les signatures de fonctions, vous devez déclarer le type de chaque paramètre. Cette décision est délibérée dans la conception de Rust : exiger des annotations de type dans les définitions de fonctions signifie que le compilateur a presque jamais besoin que vous les utilisiez ailleurs dans le code pour comprendre quel type vous voulez dire. Le compilateur est également capable de donner des messages d'erreur plus utiles s'il connaît les types que la fonction attend.

Lorsque vous définissez plusieurs paramètres, séparez les déclarations de paramètres par des virgules, comme ceci :

Nom de fichier : src/main.rs

fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

Cet exemple crée une fonction nommée print_labeled_measurement avec deux paramètres. Le premier paramètre est nommé value et est un i32. Le second est nommé unit_label et est de type char. La fonction imprime ensuite le texte contenant à la fois la value et le unit_label.

Essayons d'exécuter ce code. Remplacez le programme actuellement dans le fichier src/main.rs de votre projet functions par l'exemple précédent et exécutez-le avec cargo run :

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/functions`
The measurement is: 5h

Parce que nous avons appelé la fonction avec 5 comme valeur pour value et 'h' comme valeur pour unit_label, la sortie du programme contient ces valeurs.

Instructions et expressions

Les corps de fonctions sont composés d'une série d'instructions éventuellement terminées par une expression. Jusqu'à présent, les fonctions que nous avons abordées n'ont pas inclus une expression de fin, mais vous avez vu une expression comme partie d'une instruction. Étant donné que Rust est un langage basé sur les expressions, cette distinction est importante à comprendre. D'autres langages n'ont pas les mêmes distinctions, donc examinons ce qu'est une instruction et une expression et comment leurs différences affectent les corps de fonctions.

  • Instructions : sont des instructions qui effectuent une action et ne renvoient pas de valeur.
  • Expressions : évaluent à une valeur résultante. Examnons quelques exemples.

Nous avons déjà utilisé des instructions et des expressions. Créer une variable et lui assigner une valeur avec le mot clé let est une instruction. Dans la liste 3-1, let y = 6; est une instruction.

Nom de fichier : src/main.rs

fn main() {
    let y = 6;
}

Liste 3-1 : Une déclaration de fonction main contenant une instruction

Les définitions de fonctions sont également des instructions ; l'exemple précédent dans son ensemble est une instruction en elle-même.

Les instructions ne renvoient pas de valeurs. Par conséquent, vous ne pouvez pas assigner une instruction let à une autre variable, comme le tente de le faire le code suivant ; vous obtiendrez une erreur :

Nom de fichier : src/main.rs

fn main() {
    let x = (let y = 6);
}

Lorsque vous exécutez ce programme, l'erreur que vous obtiendrez ressemble à ceci :

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found statement (`let`)
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^^^^^^^
  |
  = note: variable declaration using `let` is a statement

error[E0658]: `let` expressions in this position are unstable
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^^^^^^^
  |
  = note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for
more information

L'instruction let y = 6 ne renvoie pas de valeur, donc il n'y a rien à quoi x puisse être lié. Cela est différent de ce qui se passe dans d'autres langages, tels que C et Ruby, où l'affectation renvoie la valeur de l'affectation. Dans ces langages, vous pouvez écrire x = y = 6 et que x et y aient tous les deux la valeur 6 ; ce n'est pas le cas en Rust.

Les expressions évaluent à une valeur et constituent la majeure partie du reste du code que vous écrirez en Rust. Considérez une opération mathématique, telle que 5 + 6, qui est une expression qui évalue à la valeur 11. Les expressions peuvent faire partie d'instructions : dans la liste 3-1, le 6 dans l'instruction let y = 6; est une expression qui évalue à la valeur 6. Appeler une fonction est une expression. Appeler une macro est une expression. Un nouveau bloc de portée créé avec des accolades est une expression, par exemple :

Nom de fichier : src/main.rs

fn main() {
  1 let y = {2
        let x = 3;
      3 x + 1
    };

    println!("The value of y is: {y}");
}

L'expression [2] est un bloc qui, dans ce cas, évalue à 4. Cette valeur est liée à y comme partie de l'instruction let [1]. Notez la ligne sans point-virgule à la fin [3], qui est différente de la plupart des lignes que vous avez vu jusqu'à présent. Les expressions ne comportent pas de point-virgule de fin. Si vous ajoutez un point-virgule à la fin d'une expression, vous la transformez en une instruction, et elle ne renverra alors pas de valeur. Gardez cela à l'esprit lorsque vous explorerez les valeurs de retour de fonctions et les expressions dans la suite.

Fonctions avec valeurs de retour

Les fonctions peuvent renvoyer des valeurs au code qui les appelle. Nous ne nommons pas les valeurs de retour, mais nous devons déclarer leur type après une flèche (->). En Rust, la valeur de retour d'une fonction est synonyme de la valeur de la dernière expression dans le bloc du corps d'une fonction. Vous pouvez retourner rapidement d'une fonction en utilisant le mot clé return et en spécifiant une valeur, mais la plupart des fonctions renvoient implicitement la dernière expression. Voici un exemple d'une fonction qui renvoie une valeur :

Nom de fichier : src/main.rs

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}

Il n'y a pas d'appels de fonction, de macros ou même d'instructions let dans la fonction five - juste le nombre 5 tout seul. C'est une fonction parfaitement valide en Rust. Notez que le type de retour de la fonction est également spécifié, comme -> i32. Essayez d'exécuter ce code ; la sortie devrait ressembler à ceci :

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/functions`
The value of x is: 5

Le 5 dans five est la valeur de retour de la fonction, c'est pourquoi le type de retour est i32. Examnons cela plus en détail. Il y a deux points importants : tout d'abord, la ligne let x = five(); montre que nous utilisons la valeur de retour d'une fonction pour initialiser une variable. Comme la fonction five renvoie un 5, cette ligne est la même que la suivante :

let x = 5;

Deuxièmement, la fonction five n'a pas de paramètres et définit le type de la valeur de retour, mais le corps de la fonction est un seul 5 sans point-virgule car c'est une expression dont nous voulons renvoyer la valeur.

Examnons un autre exemple :

Nom de fichier : src/main.rs

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {x}");
}

fn plus_one(x: i32) -> i32 {
    x + 1
}

Exécuter ce code imprimera The value of x is: 6. Mais si nous plaçons un point-virgule à la fin de la ligne contenant x + 1, en la changeant d'une expression en une instruction, nous obtiendrons une erreur :

Nom de fichier : src/main.rs

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {x}");
}

fn plus_one(x: i32) -> i32 {
    x + 1;
}

La compilation de ce code produit une erreur, comme suit :

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error[E0308]: mismatched types
 --> src/main.rs:7:24
  |
7 | fn plus_one(x: i32) -> i32 {
  |    --------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
8 |     x + 1;
  |          - help: remove this semicolon

Le principal message d'erreur, mismatched types, révèle le problème central de ce code. La définition de la fonction plus_one indique qu'elle renverra un i32, mais les instructions ne s'évaluent pas à une valeur, ce qui est exprimé par (), le type unité. Par conséquent, rien n'est renvoyé, ce qui contredit la définition de la fonction et entraîne une erreur. Dans cette sortie, Rust fournit un message pour peut-être aider à corriger ce problème : il suggère de supprimer le point-virgule, ce qui corrigerait l'erreur.

Sommaire

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