Premier programme#
Anatomie de fn main()#
Tout programme Rust exécutable commence par une fonction main. C’est le point d’entrée : le premier code que le système d’exploitation appelle lorsqu’il lance le binaire.
fn main() {
println!("Bonjour, Rust !");
}
Le mot-clé fn déclare une fonction. Les parenthèses () indiquent que main ne prend aucun paramètre. Les accolades { } délimitent le corps de la fonction, c’est-à-dire l’ensemble des instructions qu’elle exécute.
Remarque 7
Dans un noyau interactif tel que evcxr (le noyau Rust pour Jupyter), il n’est pas nécessaire d’écrire fn main(). Les expressions et instructions de premier niveau sont évaluées directement. Dans la suite de ce chapitre, tous les blocs de code exécutables utilisent cette convention.
println!("Bonjour depuis le noyau evcxr !");
Bonjour depuis le noyau evcxr !
La macro println! et le formatage de chaînes#
Définition 7 (Macro)
En Rust, une macro est un mécanisme de métaprogrammation qui génère du code à la compilation. On reconnaît l’invocation d’une macro au point d’exclamation ! qui suit son nom. println! est une macro de la bibliothèque standard qui écrit du texte sur la sortie standard, suivi d’un retour à la ligne.
Interpolation avec {}#
Le marqueur {} dans la chaîne de format est remplacé par la valeur de l’argument correspondant, en utilisant le trait Display.
let langage = "Rust";
let version = 2024;
println!("Bienvenue en {} (édition {})", langage, version);
Bienvenue en Rust (édition 2024)
Débogage avec {:?} et {:#?}#
Le marqueur {:?} utilise le trait Debug pour afficher une représentation orientée débogage. La variante {:#?} produit un affichage indenté, plus lisible pour les structures complexes.
let nombres = vec![1, 2, 3, 4, 5];
println!("Compact : {:?}", nombres);
println!("Indenté : {:#?}", nombres);
Compact : [1, 2, 3, 4, 5]
Indenté : [
1,
2,
3,
4,
5,
]
Formats numériques : {:b} et {:x}#
Rust propose des marqueurs pour afficher un entier dans différentes bases.
let n = 255;
println!("Décimal : {}", n);
println!("Binaire : {:b}", n);
println!("Hexadécimal : {:x}", n);
println!("Hexa. maj. : {:X}", n);
Décimal : 255
Binaire : 11111111
Hexadécimal : ff
Hexa. maj. : FF
Paramètres nommés#
Il est possible de nommer les arguments de format pour améliorer la lisibilité.
println!(
"{nom} a {age} ans et programme en {langage}.",
nom = "Alice",
age = 30,
langage = "Rust"
);
Alice a 30 ans et programme en Rust.
Exemple 6
Voici un récapitulatif des principaux marqueurs de format :
Marqueur |
Trait utilisé |
Description |
|---|---|---|
|
|
Affichage destiné à l’utilisateur |
|
|
Affichage de débogage (compact) |
|
|
Affichage de débogage (indenté) |
|
|
Représentation binaire |
|
|
Hexadécimal en minuscules |
|
|
Hexadécimal en majuscules |
Commentaires#
Rust distingue trois formes de commentaires.
Commentaires de ligne#
Le double barre oblique // introduit un commentaire jusqu’à la fin de la ligne.
// Ceci est un commentaire de ligne.
let x = 42; // On peut aussi commenter après une instruction.
x
Commentaires de bloc#
La paire /* ... */ délimite un commentaire qui peut s’étendre sur plusieurs lignes. Ces commentaires peuvent être imbriqués, contrairement au C.
/* Ceci est un commentaire
sur plusieurs lignes.
/* Et celui-ci est imbriqué. */
*/
let y = 7;
y
Commentaires de documentation#
Les commentaires /// (documentation externe) et //! (documentation interne) sont exploités par l’outil rustdoc pour générer automatiquement la documentation d’un projet. Ils prennent en charge la syntaxe Markdown.
/// Calcule le carré d'un entier.
///
/// # Exemples
///
/// ```
/// let résultat = carré(5);
/// assert_eq!(résultat, 25);
/// ```
fn carré(n: i32) -> i32 {
n * n
}
Le commentaire //! documente l’élément englobant (un module ou un crate) plutôt que l’élément qui suit.
//! Ce module fournit des utilitaires de calcul numérique.
Variables et liaison#
Définition 8 (Variable et liaison)
En Rust, le mot-clé let introduit une liaison (binding) : il associe un nom à une valeur. Par défaut, cette liaison est immuable : une fois la valeur affectée, elle ne peut plus être modifiée.
let x = 10;
println!("x vaut {}", x);
x vaut 10
Remarque 8
L’immuabilité par défaut est un choix de conception délibéré. Elle facilite le raisonnement sur le code, élimine toute une classe de bogues liés aux modifications involontaires, et permet au compilateur de produire des optimisations plus agressives. Lorsqu’un programmeur a réellement besoin de modifier une valeur, il l’exprime explicitement.
Mutabilité avec mut#
Pour rendre une variable modifiable, on ajoute le mot-clé mut après let.
let mut compteur = 0;
println!("Avant : {}", compteur);
compteur += 1;
println!("Après : {}", compteur);
Avant : 0
Après : 1
Sans mut, toute tentative de modification provoque une erreur de compilation. On peut l’observer dans le noyau interactif :
let x = 5;
x = 10;
[E0384] Error: cannot assign twice to immutable variable `x`
╭─[command_11:1:1]
│
1 │ let x = 5;
│ ┬
│ ╰── first assignment to `x`
│ │
│ ╰── help: consider making this binding mutable: `mut `
2 │ x = 10;
│ ───┬──
│ ╰──── cannot assign twice to immutable variable
│
│ Note: You can change an existing variable to mutable like: `let mut x = x;`
───╯
[unused_assignments] Error: value assigned to `x` is never read
╭─[command_11:1:1]
│
1 │ let x = 5;
│ ┬
│ ╰── warning: value assigned to `x` is never read
───╯
Constantes et variables statiques#
Définition 9 (Constante)
Une constante, déclarée avec const, est une valeur fixée à la compilation. Son type doit être annoté explicitement. Par convention, le nom est écrit en MAJUSCULES_AVEC_UNDERSCORES.
const PI: f64 = 3.141_592_653_589_793;
println!("Pi vaut approximativement {}", PI);
Pi vaut approximativement 3.141592653589793
Les constantes diffèrent des variables immuables sur plusieurs points : elles n’ont pas d’adresse mémoire fixe (le compilateur peut les inliner), elles ne peuvent pas être déclarées mut, et leur valeur doit être calculable à la compilation.
Une variable statique, déclarée avec static, possède quant à elle une adresse mémoire fixe et une durée de vie égale à celle du programme entier ('static).
static SALUTATION: &str = "Bonjour";
println!("{}, monde !", SALUTATION);
Bonjour, monde !
Portée des variables et blocs#
Définition 10 (Portée)
La portée (scope) d’une variable est la région du code dans laquelle cette variable est accessible. En Rust, la portée est délimitée par les accolades { } qui forment un bloc.
Lorsque l’exécution quitte un bloc, toutes les variables qui y ont été déclarées sont détruites.
let a = "externe";
{
let b = "interne";
println!("Dans le bloc : a = {}, b = {}", a, b);
}
// b n'est plus accessible ici.
println!("Hors du bloc : a = {}", a);
Dans le bloc : a = externe, b = interne
Hors du bloc : a = externe
Exemple 7
Un bloc est lui-même une expression en Rust. Il renvoie la valeur de sa dernière expression (sans point-virgule).
let valeur = {
let x = 3;
let y = 4;
x + y // pas de point-virgule : c'est la valeur du bloc
};
Ici, valeur vaut 7.
let valeur = {
let x = 3;
let y = 4;
x + y
};
println!("valeur = {}", valeur);
valeur = 7
Le masquage (shadowing)#
Définition 11 (Masquage (shadowing))
Le masquage (shadowing) consiste à redéclarer une variable avec let en utilisant le même nom. La nouvelle liaison masque la précédente dans la portée courante. Contrairement à mut, le masquage permet de changer le type de la variable.
let x = 5;
println!("x entier : {}", x);
let x = x + 1;
println!("x après ajout : {}", x);
let x = "maintenant une chaîne";
println!("x chaîne : {}", x);
x entier : 5
x après ajout : 6
x chaîne : maintenant une chaîne
Le masquage est idiomatique en Rust. On l’utilise fréquemment pour transformer une valeur tout en conservant un nom explicite, ou pour convertir un type après une opération de parsing.
Exemple 8
Un cas d’usage courant est la conversion d’une saisie utilisateur :
let entrée = "42"; // &str
let entrée: i32 = entrée.parse().unwrap(); // i32
println!("Le nombre est {}", entrée);
Sans masquage, il faudrait inventer un nom distinct (entrée_str, entrée_num), ce qui alourdit le code.
let entrée = "42";
let entrée: i32 = entrée.parse().unwrap();
println!("Le nombre est {}", entrée);
Le nombre est 42
Inférence et annotations de types#
Rust possède un système d”inférence de types : le compilateur déduit le type d’une variable à partir du contexte, sans que le programmeur ait besoin de l’écrire.
let entier = 42; // i32 par défaut
let flottant = 3.14; // f64 par défaut
let booléen = true; // bool
let caractère = 'R'; // char
println!("Types inférés : {}, {}, {}, {}", entier, flottant, booléen, caractère);
Types inférés : 42, 3.14, true, R
Lorsque l’inférence est ambiguë ou que l’on souhaite un type particulier, on utilise une annotation de type explicite, placée après le nom de la variable avec deux-points.
let x: u8 = 255;
let y: f32 = 2.718;
let z: i64 = 1_000_000_000;
println!("x = {} (u8), y = {} (f32), z = {} (i64)", x, y, z);
x = 255 (u8), y = 2.718 (f32), z = 1000000000 (i64)
On peut aussi utiliser un suffixe littéral directement sur la valeur.
let a = 42u8;
let b = 3.14f32;
println!("a = {}, b = {}", a, b);
a = 42, b = 3.14
Expressions et instructions : introduction#
Définition 12 (Expression et instruction)
Une expression (expression) produit une valeur. Une instruction (statement) effectue une action sans produire de valeur utilisable. En Rust, presque tout est expression, ce qui distingue le langage de nombreux autres.
Les appels de fonction, les opérations arithmétiques et les blocs { } sont des expressions. En revanche, une déclaration let est une instruction : elle ne renvoie pas de valeur.
// Ceci est interdit : let n'est pas une expression.
let x = (let y = 6);
Error: expected expression, found `let` statement
╭─[command_21:1:1]
│
2 │ let x = (let y = 6);
│ ─┬─
│ ╰─── error: expected expression, found `let` statement
───╯
[unused_parens] Error: unnecessary parentheses around assigned value
// Un bloc est une expression.
let résultat = {
let a = 2;
let b = 3;
a * b
};
println!("résultat = {}", résultat);
résultat = 6
Remarque 9
La distinction expression/instruction a des conséquences profondes. En Rust, un if est une expression, un match est une expression, un bloc est une expression. Cette propriété permet d’écrire du code concis et lisible, où le résultat d’un branchement peut être directement affecté à une variable. Nous approfondirons ce sujet dans le chapitre sur les structures de contrôle.
let condition = true;
let message = if condition { "vrai" } else { "faux" };
println!("Le résultat est : {}", message);
Le résultat est : vrai