Formation  ·  Le langage Rust 🦀

Apprendre
Rust en codant

Un parcours pour développeurs : la possession, l'emprunt, les traits, la gestion d'erreurs sans null… Chaque notion s'accompagne de code exécutable directement depuis la page, et un dojo de katas à faire en local pour ancrer les réflexes.

Le compilateur n'est pas votre adversaire — c'est votre relecteur le plus exigeant.

Démarrer

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.
🦀
Comment exécuter le code

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.

🎯
L'état d'esprit

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.

Démarrer · 01

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 :

Le cœur · 02

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 :

let s2 = s1; → la valeur est déplacée s1 ✗ invalidé s2 (propriétaire) "salut" move
Une seule « étiquette de propriété » à la fois : la donnée n'est jamais libérée deux fois.
🪙
Types « Copy »

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.

Le cœur · 03

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.

Plusieurs & partagés donnée &&& lecture seule → OK en parallèle Un seul &mut donnée &mut &mut ✗ écriture → exclusif
« Partage XOR mutation » : on lit à plusieurs, ou on écrit à un seul, jamais en même temps.
🔪
Tranches (slices)

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.

Modéliser · 04

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

#[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.

Modéliser · 05

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.

Pourquoi c'est précieux

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.

Modéliser · 06

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.

Idiomatique · 07

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.

Idiomatique · 08

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.

🧩
Des traits partout

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.

Idiomatique · 09

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.

Et l'async ?

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.

Pratiquer · 10

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.

📦
Modules & crates

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.

Pratiquer · 🥋

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)

🥋
Règle du dojo

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.

Kata 1

FizzBuzz

Renvoie "Fizz" si divisible par 3, "Buzz" par 5, "FizzBuzz" par 15, sinon le nombre en texte. Échauffement : match et tuples.

Kata 2

Inverser l'ordre des mots

« le chat dort » → « dort chat le ». Travaille les slices et les itérateurs (split_whitespace, rev, collect, join).

Kata 3

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

Kata 4

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.

Kata 5

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

🏅
Pour aller plus loin

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.

Pour finir

Antisèche & lectures

Les réflexes Rust en une page, le vocabulaire, et par où continuer.

Mémo syntaxe

let / let mutLiaison immuable / mutable. Immuable par défaut.
fn f(x: T) -> UFonction typée ; la dernière expression (sans ;) est retournée.
&T / &mut TEmprunt partagé / mutable. Un seul &mut XOR plusieurs &.
String / &strChaî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.
matchFiltrage exhaustif par motifs ; if let pour un seul cas.
struct / enumType produit (ET) / type somme (OU, variantes à données).
trait / implComportement 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

PossessionChaque valeur a un unique propriétaire ; libérée à la fin de sa portée.
MoveTransfert de possession ; la source devient inutilisable.
EmpruntAccès temporaire via une référence, sans prendre possession.
Borrow checkerLe vérificateur qui fait respecter les règles d'emprunt à la compilation.
LifetimeDurée de validité d'une référence ; souvent inférée.
Crate / cargoUnité de compilation/paquet / l'outil qui gère tout.
TraitContrat 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.
🦀
Le mot de la fin

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 ! 🦀