Aller au contenu

Rust - Fonctions

Les fonctions prévalent en Rust. On a déja vu la fonction majeure de Rust, la fonction main, qqui est le point d'entrée des programmes. On a aussi vu le mot clé fn qui nous permet de déclarer des fonctions.

Rust utiliste la snake case comme style conventionel pour les noms de variable et de fonction.

Voici un programme qui définit une fonction :

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

    autre_fonction();
}

fn autre_fonction() {
    println!("Une autre fonction.");
}

On peut appeller une fonction en écrivant son nom avec des parenthèses après le nom de la fonction. Étant donné que autre_fonction est définie dans le programme, il peut être appelé dans la fonction main. On remarquera qu'on a définit la fonction autre_fonction après la fonction main. On aurait pu également la définir avant, ce qui n'aurait rien changé. Rust n'en prendra pas compte, il faut seulement que la fonction soit dans l'espace de nom adéquat pour pouvoir être appelé.*

Paramètres

Une fonction peut être définie avec des paramètres, qui sont des variables spéciales liées a la fonction.

Dans cette version de autre_fonction, on ajoute un paramètre.

Rust
fn main() {
    autre_fonction(5);
}

fn autre_fonction(x: i32) {
    println!("La valeur de x est: {x}");
}

La déclaration de autre_fonction possède un paramètre x. Le type de x est i32. Quand on passe 5 a autre_fonction, println! remplace le {x} par la valeur de x.

Dans les signatures des fonctions, on est obligé de déclarer le type de chaque paramètres. C'est une décision délibérée qui a été prise dans le design de Rust : la nécessité de l'annotation de type dans la définition des fonctions veut dire que le compileur n'a presque jamais besoin que vous utilisiez les paramètres autre part pour deviner quel type vous utilisez. Il est capable également de vous donner des messages d'erreur plus utiles s'il sait quel type on utilise.

On peut définir plusieurs paramètres en les séparant par des virgules comme dans l'exemple ci dessous

Rust
fn main() {
    afficher_mesure_avec_unitee(5, 'h');
}

fn afficher_mesure_avec_unitee(mesure: i32, unitee: char) {
    println!("La mesure est: {mesure}{unitee}");
}

Déclarations et expressions

Les corps de fonctions sont faites d'une série de déclarations et se termine optionellement par une expression. Jusqu'ici, les fonctions qu'on a couvert n'ont pas inclut une expression finale, mais on a vu des expressions a l'intérieur de déclarations. Étant donné que le Rust est un langage basé sur des expressions, c'est une distinction importante a comprendre. Les autres langages n'ont pas ces distinctions donc il est important de la comprendre.

  • Les déclarations sont des instructions qui font une actions et ne renvoient pas de valeurs.
  • Les expressions s'évaluent a un résultat.

On a déja utilisé des déclarations et des expressions. Créer une variable et lui assigner une valeur avec let est une déclaration.

La définition d'une fonction est aussi une déclaration.

Les déclarations de renvoient pas de valeurs. C'est pour cela qu'on peut pas assigner une déclaration en let a une autre variable.

Les expressions s'évaluent a un résultat et sont la plupart du reste du code qu'on écrira en Rust. Considérons une opération mathématique comme 5+6 qui est une expression qui s'évalue a la valeur 11. Les expressions peuvent faire partie de déclarations. Appeler une fonction est une expression. Appeler une macro est une expression. Un nouveau champ est une expression, avec pour exemple :

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

    println!("La valeur de y est: {y}");
}

L'expression

Rust
let x = 3;
x + 1

est un block qui, dans ce cas, s'évalue a 4. Cette valeur est liéeé a y comme partie de l'expression let

Remarquons que x+1 n'a pas de point-virgule a la fin, ce qui est différent de toutes les lignes jusqu'a présent. Les expressions n'ont pas de point-virgules finaux. Si on y ajoute un, cela va la transformer en déclaration, ce qui ne va pas retourner une valeur. Gardons cela a l'esprit pendant qu'on explore les valeurs renvoyés

Fonctions avec valeurs renvoyées

Les fonctions doivent renvoyer des valeur au code qui les appelle. On ne leur donne pas de nom, mais on doit déclarer après une flèche (->). En Rust, la valeur renvotée est synonyme de la valeur de la dernière expression dans le bloc du corps d'une fonction. On peut renvoyer une valeur plus tot en utilisant le mot-clé return avec une valeur, mais la plupart en renvoient une implicitement. En voici un exemple :

Rust
fn cinq() -> i32 {
    5
}

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

    println!("La valeur de x est: {x}");
}
Il n'y a pas d'appel de fonction, de macros ou d'expression let dans la fonction, juste le nombre 5. C'est une fonction parfaitement valide en Rust. Notons que le type de la valeur renvoyée est spécifiée également, comme -> i32

Le 5 dans la fonction est la valeur renvoyée. Cela reviendrait a écrire

Rust
let x = 5;

Prenons un autre exemple :

Rust
fn main() {
    let x = plus_un(5);

    println!("La valeur de x est: {x}");
}

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

L'exécution de ce code afichera La valeur de x est 6. Mais si on place un point-virgule a la fin de la ligne qui contient x+1, ce qui la transforme en déclaration, cela nous crée une erreur

Rust
fn main() {
    let x = plus_un(5);

    println!("La valeur de x est: {x}");
}

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

Compiler ce code nous donnera l'erreur suivante :

Text Only
$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error[E0308]: mismatched types
 --> src/main.rs:7:24
  |
7 | fn plus_un(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` due to previous error

Le message d'erreur principal, mismached types nous révèle le problème centrale avec ce code. La définition de la fonction dit qu'elle renverra un i32, mais les déclarations ne donnent pas de valeur, qui est exprimé par (), le type unité. Rien est donc retourné, ce qui est contredit par la définition de la fonction et qui donne une erreur. Dans cette sortie, Rust donne un pessage d'erreur pour aider a réparer cette erreur : il sugère de retirer le point-virgule, ce qui rectifie cette erreur.