Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Les 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.

Le code Rust utilise le snake case comme convention de style pour les noms de fonctions et de variables, dans laquelle toutes les lettres sont en minuscules et les mots sont séparés par des tirets bas (underscores). Voici un programme qui contient un exemple de définition de fonction :

Fichier : src/main.rs rust {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}}

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

    another_function();
}

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

Nous définissons une fonction en Rust en saisissant fn suivi d’un nom de fonction et d’un jeu de parenthèses. Les accolades indiquent au compilateur où le corps de la fonction commence et se terminé.

Nous pouvons appeler n’importe quelle fonction que nous avons définie en saisissant son nom suivi d’un jeu de parenthèses. Comme another_function est définie dans le programme, elle peut être appelée depuis la fonction main. Notez que nous avons défini another_function après la fonction main dans le code source ; nous aurions tout aussi bien pu la définir avant. Rust ne se soucie pas de l’endroit où vous définissez vos fonctions, seulement qu’elles soient définies quelque part dans une portée visible par l’appelant.

Créons un nouveau projet binaire nommé functions pour explorer davantage les fonctions. Placez l’exemple another_function dans src/main.rs et exécutez-le. Vous devriez voir la sortie suivante : console {{#include ../listings/ch03-common-programming-concepts/no-listing-16-functions/output.txt}}

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [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. D’abord le message “Hello, world!” s’affiche, puis another_function est appelée et son message est affiché.

Les paramètres

Nous pouvons définir des fonctions avec des paramètres, qui sont des variables spéciales faisant partie de la signature d’une fonction. Lorsqu’une fonction à des paramètres, vous pouvez lui fournir des valeurs concrètes pour ces paramètres. Techniquement, les valeurs concrètes sont appelées arguments, mais dans la conversation courante, les gens ont tendance à utiliser les mots paramètre et argument de manière interchangeable pour désigner soit les variables dans la définition d’une fonction, soit les valeurs concrètes passées lors de l’appel d’une fonction.

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

Fichier : src/main.rs rust {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}}

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 : console {{#include ../listings/ch03-common-programming-concepts/no-listing-17-functions-with-parameters/output.txt}}

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

La déclaration de another_function à un paramètre nommé x. Le type de x est spécifié comme i32. Lorsque nous passons 5 à another_function, la macro println! place 5 à l’endroit où se trouvait la paire d’accolades contenant x dans la chaîne de format.

Dans les signatures de fonctions, vous devez déclarer le type de chaque paramètre. C’est une décision délibérée dans la conception de Rust : exiger des annotations de type dans les définitions de fonctions signifie que le compilateur n’a presque jamais besoin que vous les utilisiez ailleurs dans le code pour déterminer le type que vous souhaitez. Le compilateur est également en mesure de fournir des messages d’erreur plus utiles s’il connaît les types attendus par la fonction.

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

Fichier : src/main.rs rust {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}}

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 de type i32. Le second est nommé unit_label et est de type char. La fonction affiche ensuite un texte contenant à la fois value et 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 : console {{#include ../listings/ch03-common-programming-concepts/no-listing-18-functions-with-multiple-parameters/output.txt}}

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

Comme 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.

Les instructions et les expressions

Les corps de fonctions sont constitués d’une série d’instructions se terminant éventuellement par une expression. Jusqu’à présent, les fonctions que nous avons couvertes n’incluaient pas d’expression finale, mais vous avez vu une expression en tant que partie d’une instruction. Comme Rust est un langage basé sur les expressions, c’est une distinction importante à comprendre. Les autres langages ne font pas les mêmes distinctions, alors examinons ce que sont les instructions et les expressions et comment leurs différences affectent le corps des fonctions.

  • Les instructions (statements) sont des directives qui effectuent une action et ne retournent pas de valeur.
  • Les expressions s’évaluent pour produire une valeur résultante.

Examinons quelques exemples.

Nous avons en fait 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 le listing 3-1, let y = 6; est une instruction.

Filename: src/main.rs
fn main() {
    let y = 6;
}
Listing 3-1: A main function declaration containing one statement

Les définitions de fonctions sont également des instructions ; l’exemple précédent dans son intégralité est une instruction en soi. (Comme nous le verrons bientôt, appeler une fonction n’est cependant pas une instruction.)

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

Fichier : src/main.rs rust {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}}

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

Lorsque vous exécutez ce programme, l’erreur que vous obtiendrez ressemble à ceci : console {{#include ../listings/ch03-common-programming-concepts/no-listing-19-statements-vs-expressions/output.txt}}

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found `let` statement
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^
  |
  = note: only supported directly in conditions of `if` and `while` expressions

warning: unnecessary parentheses around assigned value
 --> src/main.rs:2:13
  |
2 |     let x = (let y = 6);
  |             ^         ^
  |
  = note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
  |
2 -     let x = (let y = 6);
2 +     let x = let y = 6;
  |

warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` (bin "functions") due to 1 previous error; 1 warning emitted

L’instruction let y = 6 ne retourné pas de valeur, il n’y a donc rien à quoi lier x. Ceci est différent de ce qui se passe dans d’autres langages, comme C et Ruby, où l’affectation retourné la valeur de l’affectation. Dans ces langages, vous pouvez écrire x = y = 6 et avoir à la fois x et y avec la valeur 6 ; ce n’est pas le cas en Rust.

Les expressions s’évaluent pour produire une valeur et constituent la majeure partie du code que vous écrirez en Rust. Considérez une opération mathématique, comme 5 + 6, qui est une expression qui s’évalue à la valeur 11. Les expressions peuvent faire partie d’instructions : dans le listing 3-1, le 6 dans l’instruction let y = 6; est une expression qui s’é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 :

Fichier : src/main.rs rust {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}}

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

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

Cette expression :

{
    let x = 3;
    x + 1
}

est un bloc qui, dans ce cas, s’évalue à 4. Cette valeur est liée à y dans le cadre de l’instruction let. Notez la ligne x + 1 sans point-virgule à la fin, ce qui est différent de la plupart des lignes que vous avez vues jusqu’ici. Les expressions n’incluent pas de point-virgule final. Si vous ajoutez un point-virgule à la fin d’une expression, vous la transformez en instruction, et elle ne retournera alors pas de valeur. Gardez cela à l’esprit lorsque vous explorerez les valeurs de retour des fonctions et les expressions dans la suite.

Les fonctions avec valeurs de retour

Les fonctions peuvent retourner 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 de la fonction est synonyme de la valeur de la dernière expression dans le bloc du corps d’une fonction. Vous pouvez retourner prématurément d’une fonction en utilisant le mot-clé return et en spécifiant une valeur, mais la plupart des fonctions retournent implicitement la dernière expression. Voici un exemple de fonction qui retourné une valeur :

Fichier : src/main.rs rust {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}}

fn five() -> i32 {
    5
}

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

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

Il n’y a pas d’appels de fonctions, de macros, ni 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é, sous la forme -> i32. Essayez d’exécuter ce code ; la sortie devrait ressembler à ceci : console {{#include ../listings/ch03-common-programming-concepts/no-listing-21-function-return-values/output.txt}}

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [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. Examinons cela plus en détail. Il y a deux points importants : premièrement, la ligne let x = five(); montre que nous utilisons la valeur de retour d’une fonction pour initialiser une variable. Comme la fonction five retourné un 5, cette ligne est équivalente à la suivante :

#![allow(unused)]
fn main() {
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 simple 5 sans point-virgule car c’est une expression dont nous voulons retourner la valeur.

Examinons un autre exemple :

Fichier : src/main.rs rust {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}}

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

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

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

L’exécution de ce code affichera The value of x is: 6. Mais que se passe-t-il si nous plaçons un point-virgule à la fin de la ligne contenant x + 1, la transformant d’une expression en instruction ?

Fichier : src/main.rs rust {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}}

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 produira une erreur, comme suit : console {{#include ../listings/ch03-common-programming-concepts/no-listing-23-statements-dont-return-values/output.txt}}

$ 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 to return this value

For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` (bin "functions") due to 1 previous error

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