iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
Formations
Rust · Intermédiaire

Rust

Apprendre Rust de zéro jusqu'à un vrai outil en ligne de commande : ownership, emprunts, enums, gestion d'erreurs, traits, génériques, durées de vie, itérateurs, tests, threads et async avec Tokio.

RustCargoédition 2024serdeclapTokio
01Introduction et installation02Variables, types, fonctions et contrôle de flux03Ownership et emprunts04Structs, enums et pattern matching05Collections et gestion d'erreurs06Traits et génériques07Durées de vie et pointeurs intelligents08Closures et itérateurs09Modules, crates et tests10Concurrence : threads, canaux et async11Projet : un gestionnaire de tâches en ligne de commande
Chapitre 2·25 min

Variables, types, fonctions et contrôle de flux

Les bases de Rust ressemblent à celles de n'importe quel langage à accolades. Trois détails changent tout le reste : les variables sont immuables par défaut, presque tout est une expression qui renvoie une valeur, et le compilateur déduit les types sans jamais les deviner à l'exécution.

Variables : immuables par défaut

fn main() {
    let x = 5;
    // x = 6;  // erreur : x est immuable
 
    let mut compteur = 0;
    compteur += 1;
    println!("x = {x}, compteur = {compteur}");
}

let crée une variable immuable. let mut la rend modifiable. Ce choix par défaut paraît contraignant, mais il dit au lecteur quelles valeurs bougent. Dans un programme Rust bien écrit, la plupart des let n'ont pas de mut.

Le masquage (shadowing)

On peut redéclarer une variable du même nom. La nouvelle masque l'ancienne, et elle peut changer de type :

fn main() {
    let saisie = "42";
    let saisie: i32 = saisie.parse().unwrap();
    let saisie = saisie * 2;
    println!("{saisie}"); // 84
}

Le masquage évite les noms comme saisie_str et saisie_int pour une même donnée qui change de forme. Ce n'est pas une mutation : chaque let crée une nouvelle variable.

Les constantes

const SECONDES_PAR_HEURE: u32 = 60 * 60;

Une const a toujours un type explicite, s'écrit en majuscules et doit être calculable à la compilation.

Les types scalaires

TypeContenuExemple
i8, i16, i32, i64, i128Entiers signéslet a: i32 = -12;
u8, u16, u32, u64, u128Entiers non signéslet octet: u8 = 255;
isize, usizeEntiers de la taille d'un pointeurIndices de tableau, longueurs
f32, f64Nombres à virgulelet pi = 3.14159; (f64 par défaut)
booltrue ou falselet actif = true;
charUn caractère Unicode, 4 octetslet c = 'é';

Sans annotation, un entier est un i32 et un flottant un f64. Les conversions ne sont jamais implicites : additionner un i32 et un i64 est une erreur de compilation. On convertit avec as ou, plus sûrement, avec try_from :

fn main() {
    let petit: i32 = 300;
    let grand = petit as i64 + 10_000_000_000;
    let octet = u8::try_from(petit); // Err : 300 ne tient pas dans un u8
    println!("{grand} {octet:?}");
}

Le _ dans 10_000_000_000 est un séparateur visuel, ignoré par le compilateur. {:?} affiche une valeur en format de débogage.

Un dépassement d'entier (255u8 + 1) fait paniquer le programme en mode dev. En release, il reboucle à 0 sans prévenir. Quand le dépassement est possible, dites ce que vous voulez : checked_add, wrapping_add ou saturating_add.

Tuples et tableaux

fn main() {
    let point: (i32, i32, &str) = (3, -1, "A");
    let (x, y, nom) = point;            // déstructuration
    println!("{nom} = ({x}, {y}), x aussi = {}", point.0);
 
    let jours = ["lun", "mar", "mer", "jeu", "ven"];
    let zeros = [0; 8];                  // huit zéros
    println!("{} jours, premier : {}, {:?}", jours.len(), jours[0], zeros);
}

Un tableau a une taille fixe connue à la compilation, et son type l'inclut : [&str; 5]. Pour une liste qui grandit, on utilise Vec, vu au chapitre 5. Un accès hors limites (jours[10]) arrête le programme au lieu de lire de la mémoire au hasard.

Fonctions

fn aire_rectangle(largeur: f64, hauteur: f64) -> f64 {
    largeur * hauteur
}
 
fn main() {
    let a = aire_rectangle(3.0, 4.5);
    println!("{a}");
}

Les types des paramètres sont obligatoires, celui du retour aussi s'il y en a un. Remarquez l'absence de return et de point-virgule sur la dernière ligne : c'est la clé du point suivant.

Expressions et instructions

En Rust, un bloc { ... } est une expression : il vaut sa dernière ligne, si celle-ci n'a pas de point-virgule. Ajouter le ; transforme l'expression en instruction, qui ne vaut rien (techniquement, elle vaut (), le type unité).

fn carre(x: i32) -> i32 {
    x * x      // renvoie x * x
}
 
fn carre_casse(x: i32) -> i32 {
    x * x;     // erreur : la fonction renvoie (), pas i32
}

L'erreur du compilateur sur la seconde fonction propose d'ailleurs de retirer le point-virgule. return existe, mais on le réserve aux sorties anticipées.

Conditions

if est une expression, ce qui remplace l'opérateur ternaire :

fn main() {
    let temperature = 23;
    let tenue = if temperature > 20 { "t-shirt" } else { "pull" };
    println!("{tenue}");
}

Les deux branches doivent avoir le même type. La condition doit être un bool : pas de if 1, pas de if chaine comme en JavaScript.

Boucles

Rust a trois boucles. loop tourne jusqu'à un break, qui peut renvoyer une valeur :

fn main() {
    let mut n = 1;
    let premier_au_dessus_de_100 = loop {
        n *= 2;
        if n > 100 {
            break n;
        }
    };
    println!("{premier_au_dessus_de_100}"); // 128
}

while tourne tant qu'une condition est vraie. Et for parcourt tout ce qui est itérable :

fn main() {
    for i in 0..3 {
        println!("tour {i}");          // 0, 1, 2
    }
    for i in (1..=3).rev() {
        println!("{i}...");            // 3, 2, 1
    }
    let fruits = ["pomme", "kiwi"];
    for (index, fruit) in fruits.iter().enumerate() {
        println!("{index} : {fruit}");
    }
}

0..3 exclut la borne haute, 1..=3 l'inclut. Dans du Rust idiomatique, for sur une plage ou un itérateur remplace presque toutes les boucles à compteur manuel. Le chapitre 8 montre comment aller plus loin avec les itérateurs.

Pour sortir d'une boucle imbriquée, on étiquette la boucle extérieure :

fn main() {
    'externe: for x in 0..10 {
        for y in 0..10 {
            if x * y == 42 {
                println!("trouvé : {x} x {y}");
                break 'externe;
            }
        }
    }
}

Exercice

Écrivez fizzbuzz(n: u32) -> String qui renvoie "Fizz" pour un multiple de 3, "Buzz" pour un multiple de 5, "FizzBuzz" pour les deux, et le nombre sinon. Affichez le résultat de 1 à 15.

fn fizzbuzz(n: u32) -> String {
    if n % 15 == 0 {
        "FizzBuzz".to_string()
    } else if n % 3 == 0 {
        "Fizz".to_string()
    } else if n % 5 == 0 {
        "Buzz".to_string()
    } else {
        n.to_string()
    }
}
 
fn main() {
    for i in 1..=15 {
        println!("{}", fizzbuzz(i));
    }
}

Pourquoi "Fizz".to_string() et pas "Fizz" tout court ? Parce que "Fizz" est un &str et la fonction promet une String. La différence entre les deux est le cœur du chapitre suivant.


Immuabilité par défaut, types explicites aux frontières des fonctions, blocs qui renvoient une valeur : ces trois réflexes suffisent pour écrire du Rust simple. Le chapitre suivant aborde ce qui rend Rust unique, et ce qui bloque tous les débutants : l'ownership.

Précédent
Introduction et installation
Suivant
Ownership et emprunts

Développeur fullstack passionné. J'apprends en construisant et je documente tout — front, back, outils. Le code s'apprend mieux en public.

Naviguer

IndexTous les articlesFormationsProfilOutilsBibliothech

Ailleurs

GitHub RSS

Newsletter

Les articles, libs et découvertes. Une fois par semaine, pas plus.

© 2026 William LoreeConçu & codé à la main