Pourquoi Rust
Rust tient une promesse longtemps jugée impossible : la sûreté mémoire d'un langage managé, avec la performance du C, et sans ramasse-miettes. Le secret : un compilateur qui vérifie, à la compilation, ce que les autres laissent exploser à l'exécution.
Là où d'autres langages détectent les fuites, accès concurrents et déréférencements nuls au runtime (ou jamais), Rust les rend impossibles à compiler. Le prix à payer : apprendre quelques règles nouvelles — la possession et l'emprunt — qui forment le cœur de ce cours.
- Sûreté — pas de
null, pas de double libération, pas d'accès hors limites silencieux, pas de data race. - Performance — pas de GC, contrôle fin de la mémoire, abstractions « à coût zéro ».
- Concurrence sereine — le compilateur refuse les partages de données dangereux entre threads.
- Ergonomie moderne — types algébriques, pattern matching,
cargo, des messages d'erreur qui expliquent pourquoi.
Sous chaque exemple, ▶ Exécuter tente de compiler dans la page. Si l'aperçu bloque le réseau, un bouton Playground ↗ ouvre le même code dans le Rust Playground officiel (qui tourne dans votre navigateur). Les exercices sont éditables : modifiez, relancez.
Au début, le « borrow checker » vous dira souvent non. Ce n'est pas de l'obstruction : il vous montre un bug que vous auriez écrit. Apprendre Rust, c'est apprendre à penser la propriété des données — et ça change durablement votre façon de coder, même ailleurs.
Les bases
Quelques repères de syntaxe avant d'attaquer le cœur. Deux idées surprennent souvent : tout est immuable par défaut, et presque tout est une expression.
On déclare avec let (immuable) ou let mut (mutable). Les types sont statiques mais largement inférés. Le dernier élément d'un bloc, sans point-virgule, en est la valeur — d'où des fonctions sans return.
L'interpolation {nom} dans println! (une macro, d'où le !) lit directement les variables. À vous de jouer :
La possession
La règle d'or — Chaque valeur a un seul propriétaire. Quand le propriétaire sort de portée, la valeur est libérée. Affecter ou passer la valeur la déplace (move) : l'ancien nom devient invalide.
C'est ainsi que Rust gère la mémoire sans ramasse-miettes et sans free() manuel : la libération est déterminée à la compilation, par la portée. Conséquence directe et déroutante au début — un déplacement invalide la source :
Les types simples de taille fixe (entiers, booléens, char…) sont copiés, pas déplacés : let a = 5; let b = a; laisse a bien vivant. Le « move » concerne les valeurs qui possèdent des données sur le tas, comme String ou Vec.
Les emprunts
La règle d'or — Plutôt que déplacer, on peut emprunter une valeur via une référence &. Et à tout instant : soit une seule référence mutable &mut, soit plusieurs références partagées & — jamais les deux.
Cette règle, vérifiée par le borrow checker, élimine à elle seule les data races et la plupart des bugs d'aliasing : on ne peut pas modifier une donnée pendant que quelqu'un d'autre la lit.
Une slice est un emprunt sur une portion contiguë : &str (vue sur une chaîne), &[T] (vue sur un tableau/Vec). On passe des slices partout : c'est léger (ni copie ni possession) et ça marche pour plusieurs types sources. Les durées de vie (lifetimes) garantissent qu'une slice ne survit jamais à la donnée qu'elle référence — le compilateur les infère presque toujours pour vous.
Structs & enums
L'idée — Rust modélise les données avec des structs (un ET : un point a un x ET un y) et des enums (un OU : une forme est un cercle OU un rectangle). Ces « types algébriques » rendent les états impossibles… impossibles à représenter.
Les enums de Rust ne sont pas de simples constantes : chaque variante peut porter des données. Couplés au pattern matching, ils remplacent avantageusement héritage et valeurs sentinelles.
#[derive(Debug, Clone, PartialEq)] au-dessus d'un type demande au compilateur d'implémenter automatiquement des comportements courants : afficher avec {:?}, cloner, comparer. Un gain de temps constant, et lisible.
Pattern matching
L'idée — match compare une valeur à des motifs et exécute la branche correspondante. Sa force : il est exhaustif — le compilateur exige que vous traitiez tous les cas. Plus d'oubli silencieux.
Ajoutez une variante à un enum, et tous les match qui ne la gèrent pas cessent de compiler. Le compilateur vous emmène exactement aux endroits à mettre à jour : le refactoring devient sûr.
Option & Result
L'idée — Rust n'a pas de null ni d'exceptions pour le contrôle de flux courant. L'absence se modélise avec Option<T> (Some/None), l'échec avec Result<T, E> (Ok/Err). Les erreurs sont des valeurs qu'on doit traiter.
Conséquence : impossible d'oublier le cas « ça n'a pas marché » — il est dans le type. Et l'opérateur ? propage l'erreur en une touche, gardant le code lisible.
Collections & itérateurs
L'idée — Vec<T> (liste), String (texte), HashMap<K,V> (dictionnaire) couvrent l'essentiel. Mais le vrai style Rust, ce sont les itérateurs : on décrit une transformation (filtrer, transformer, réduire) plutôt qu'une boucle.
Les chaînes d'itérateurs sont paresseuses et compilées en code aussi rapide qu'une boucle manuelle (abstraction à coût zéro). Elles se lisent comme une phrase.
Traits & génériques
L'idée — Un trait définit un comportement partagé (≈ une interface) ; les génériques écrivent du code qui marche pour tout type respectant un trait. C'est le polymorphisme de Rust — sans héritage.
La bibliothèque standard est bâtie sur des traits : Iterator (boucler), Display/Debug (afficher), From/Into (convertir), Clone, Ord… Implémenter le bon trait, et votre type s'intègre au reste de l'écosystème comme s'il était natif.
Concurrence sans peur
L'idée — Les règles de possession s'appliquent aussi entre threads. Résultat : le compilateur refuse de compiler un partage de données qui provoquerait une data race. C'est la « fearless concurrency ».
Pour communiquer entre threads, le style idiomatique privilégie les canaux (on s'échange des messages) plutôt que la mémoire partagée. Le mot-clé move transfère la possession des données au thread.
Pour des milliers de tâches d'E/S (réseau, fichiers), Rust propose async/await avec un runtime comme Tokio : énormément de tâches concurrentes sur peu de threads. Mêmes garanties de sûreté, autre modèle — à explorer une fois les bases solides.
Cargo & l'outillage
L'idée — cargo est le couteau suisse de Rust : il crée, compile, teste, lint et gère les dépendances (les crates). C'est lui qui rend l'écosystème si confortable.
Les tests vivent dans le code, marqués #[test], et cargo test les exécute. C'est la base de notre dojo : on écrit une fonction pour faire passer des tests.
Le code s'organise en modules (mod, use) au sein d'une crate (l'unité de compilation). Les dépendances externes sont des crates publiées sur crates.io et déclarées dans Cargo.toml — versionnées et résolues par cargo.
Katas / Dojo
La théorie ne suffit pas : Rust s'apprend dans les doigts. Voici un dojo à faire en local — des katas progressifs guidés par des tests. Le principe : la fonction est à écrire, les #[test] disent quand c'est gagné.
Mise en place (en local)
Implémentez jusqu'à ce que tous les tests passent au vert, puis lancez cargo clippy et essayez de simplifier. Bonus : un kata sans fn main mais avec des #[test] se lance aussi via le bouton Playground ↗ (il exécute cargo test). Les solutions sont masquées : cherchez d'abord.
FizzBuzz
Renvoie "Fizz" si divisible par 3, "Buzz" par 5, "FizzBuzz" par 15, sinon le nombre en texte. Échauffement : match et tuples.
Inverser l'ordre des mots
« le chat dort » → « dort chat le ». Travaille les slices et les itérateurs (split_whitespace, rev, collect, join).
Mot le plus fréquent
Renvoie le mot le plus fréquent d'une phrase, ou None si elle est vide. Au menu : HashMap, Option et itérateurs (max_by_key).
Une pile générique
Implémente une Pile<T> (LIFO). pop renvoie Option<T> (None si vide). Au menu : struct générique, Vec, Option.
Mini-évaluateur
Évalue "3 + 4" ou "10 - 7" et renvoie un Result (Err si l'entrée est invalide). Au menu : parsing, Result, l'opérateur ?.
Une fois ces katas digérés, enchaînez sur Rustlings (des centaines de petits exercices à corriger localement) et les défis d'Exercism ou d'Advent of Code en Rust. La répétition espacée fait le reste.
Antisèche & lectures
Les réflexes Rust en une page, le vocabulaire, et par où continuer.
Mémo syntaxe
| let / let mut | Liaison immuable / mutable. Immuable par défaut. |
| fn f(x: T) -> U | Fonction typée ; la dernière expression (sans ;) est retournée. |
| &T / &mut T | Emprunt partagé / mutable. Un seul &mut XOR plusieurs &. |
| String / &str | Chaîne possédée / vue empruntée sur une chaîne. |
| Vec<T> / &[T] | Liste possédée / slice (vue sur une portion). |
| Option<T> | Some(v) ou None — l'absence, sans null. |
| Result<T,E> | Ok(v) ou Err(e) — l'échec comme valeur. ? propage. |
| match | Filtrage exhaustif par motifs ; if let pour un seul cas. |
| struct / enum | Type produit (ET) / type somme (OU, variantes à données). |
| trait / impl | Comportement partagé / son implémentation pour un type. |
| #[derive(...)] | Génère Debug, Clone, PartialEq… automatiquement. |
| .iter().map().filter() | Pipelines d'itérateurs, paresseux et rapides. |
Vocabulaire
| Possession | Chaque valeur a un unique propriétaire ; libérée à la fin de sa portée. |
| Move | Transfert de possession ; la source devient inutilisable. |
| Emprunt | Accès temporaire via une référence, sans prendre possession. |
| Borrow checker | Le vérificateur qui fait respecter les règles d'emprunt à la compilation. |
| Lifetime | Durée de validité d'une référence ; souvent inférée. |
| Crate / cargo | Unité de compilation/paquet / l'outil qui gère tout. |
| Trait | Contrat de comportement partagé entre types (≈ interface). |
Pour aller plus loin
- The Rust Book — le manuel officiel, gratuit et excellent (doc.rust-lang.org/book).
- Rust by Example — apprendre par des exemples exécutables.
- Rustlings — exercices guidés à corriger en local, parfait après ce dojo.
- Rust Playground & std docs — expérimenter et chercher dans la bibliothèque standard.
Rust se mérite les premières semaines, puis se savoure : une fois la possession intégrée, on écrit du code rapide et sûr avec une tranquillité rare. Laissez le compilateur vous guider, pratiquez le dojo, et le déclic viendra. Bon code ! 🦀