Cours Rust Complet 🦀

Les 12 chapitres essentiels — ownership, borrowing, pattern matching et programmation système sûre

12
Chapitres
100+
Exemples de code
2024 Ed.
Édition
A1→C2
Niveaux

CHAPITRE 01

Introduction et premier programme

🦀 Hello World
fn main()

println! est une macro (avec le !), pas une fonction. Les macros Rust sont vérifiées à la compilation (nombre d'arguments, types). = affichage normal, = debug, = debug pretty-print.

⚙️ Cargo — le gestionnaire de projet
# Installer Rust
curl –proto '=https' –tlsv1.2 -sSf https://sh.rustup.rs | sh# Créer un projet
cargo new mon_projet
cd mon_projet

# Structure :
# mon_projet/
# ├── Cargo.toml (dépendances, metadata)
# └── src/
# └── main.rs (code source)

# Compiler et exécuter
cargo run

# Compiler en mode release (optimisé)
cargo build –release

# Vérifier sans compiler
cargo check

# Ajouter une dépendance
cargo add serde

🏗️ Rust en résumé
CaractéristiqueRust
TypageStatique et fort — vérifié à la compilation
MémoireOwnership system — pas de GC, pas de malloc/free
SûretéGaranti memory-safe à la compilation (pas de null, pas de data race)
PerformanceComparable au C/C++ — zero-cost abstractions
UsageSystèmes, CLI, WebAssembly, réseau, embarqué, cloud
Conventionsnake_case (fonctions/variables), PascalCase (types/traits)

Rust vs C/C++ : Même performance, mais le compilateur empêche les bugs mémoire (use-after-free, data races, null dereference) avant l'exécution. Le C et le C++ détectent ces bugs au runtime (crash).

🧠 Quiz
Comment Rust gère-t-il la mémoire sans garbage collector ?
Avec le système d'ownership : chaque valeur a un propriétaire unique. Quand le propriétaire sort du scope, la mémoire est libérée automatiquement (drop). Le compilateur vérifie ces règles à la compilation.

CHAPITRE 02

Variables et types de données

📦 Variables et mutabilité
// Variables IMMUABLES par défaut
let x = 5;
x = 10; // ❌ Erreur de compilation !// mut = mutable
let mut y = 5;
y = 10; // ✅ OK

// Shadowing — redéclarer avec le même nom
let x = 5;
let x = x + 1; // x = 6 (nouvelle variable)
let x = « hello »; // OK — le type peut changer avec shadowing

// Constantes — MAJUSCULES, type obligatoire
const MAX_SIZE: u32 = 100_000; // _ comme séparateur visuel

Immuable par défaut est un choix de design fondamental de Rust. Cela force le développeur à être explicite sur les mutations, ce qui rend le code plus sûr et plus lisible. Bien différent de JavaScript où tout est mutable sauf avec const.

🔢 Types scalaires
TypeTaillePlage
i8 / i16 / i32 / i64 / i1281-16 octetsEntier signé
u8 / u16 / u32 / u64 / u1281-16 octetsEntier non-signé
isize / usizeTaille du pointeurPour les index
f32 / f644 / 8 octetsFlottant (f64 par défaut)
bool1 octettrue / false
char4 octetsUn caractère Unicode (pas ASCII !)
📝 String et &str
// &str — référence vers une chaîne (immuable, emprunté)
let s1: &str = « bonjour »; // Stocké dans le binaire// String — chaîne sur le heap (mutable, possédée)
let mut s2 = String::from(« bonjour »);
s2.push_str( » monde »); // « bonjour monde »
s2.push('!');

// Formatage
let s3 = format!(« Nom: , Age:  », « Alice », 25);

// Méthodes
s2.len() // Nombre d'octets
s2.is_empty() // false
s2.contains(« monde ») // true
s2.replace(« monde », « Rust »)
s2.trim()
s2.to_uppercase()
s2.split(' ') // Itérateur
s2.starts_with(« bon »)

// Conversion
let n: i32 = « 42 ».parse().unwrap(); // &str → i32
let s = 42.to_string(); // i32 → String

&str vs String : &str = emprunt en lecture seule (comme une vue). String = propriétaire des données (heap, modifiable). Les fonctions prennent généralement &str en paramètre (plus flexible) et retournent String.

📊 Tuples et Arrays
// Tuple — types hétérogènes, taille fixe
let tup: (i32, f64, bool) = (42, 3.14, true);
let (x, y, z) = tup; // Destructuring
tup.0 // 42 (accès par index)// Array — même type, taille fixe (sur la stack)
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10]; // [0, 0, 0, …, 0] (10 fois)
arr[0] // 1
arr.len() // 5
arr[10] // ❌ Panic! (index out of bounds — vérifié au runtime)

CHAPITRE 03

Fonctions et expressions

🔧 Fonctions
// Fonction avec type de retour
fn addition(a: i32, b: i32) -> i32 // Avec return explicite (possible mais pas idiomatique)
fn abs(n: i32) -> i32

// Sans retour (retourne () implicitement)
fn saluer(nom: &str) {
println!(« Bonjour ! », nom);
}

En Rust, tout est expression. La dernière expression d'un bloc (sans ;) est la valeur de retour. if/else, match, et même les blocs sont des expressions qui retournent une valeur.

⚡ Expressions partout
// if est une expression
let status = if age >= 18 else ;// Un bloc est une expression
let result = ; // result = 25

CHAPITRE 04

Ownership et Borrowing

🔑 Les 3 règles d'Ownership
// RÈGLE 1 : Chaque valeur a exactement UN propriétaire
// RÈGLE 2 : Quand le propriétaire sort du scope, la valeur est supprimée (drop)
// RÈGLE 3 : Il ne peut y avoir qu'un seul propriétaire à la foislet s1 = String::from(« hello »);
let s2 = s1; // s1 est MOVED vers s2 — s1 n'est plus utilisable !
println!(«  », s1); // ❌ Erreur : value borrowed after move
println!(«  », s2); // ✅ OK

// Clone — copie profonde explicite
let s1 = String::from(« hello »);
let s2 = s1.clone(); // Copie explicite
println!(«   », s1, s2); // ✅ Les deux fonctionnent

// Les types simples (i32, f64, bool, char) sont Copy — pas de move
let x = 5;
let y = x; // x est copié, pas moved
println!(«   », x, y); // ✅ Les deux fonctionnent

C'est LE concept qui rend Rust unique. Le compilateur vérifie à chaque instant qui possède chaque valeur. Pas de garbage collector, pas de compteur de références — tout est résolu à la compilation, sans coût au runtime.

🤝 Borrowing (Emprunts)
// Référence immuable (&) — plusieurs lecteurs simultanés
fn longueur(s: &String) -> usize let s = String::from(« hello »);
let len = longueur(&s); // Emprunte s
println!(«  = chars », s, len); // s est encore utilisable ✅

// Référence mutable (&mut) — UN seul écrivain à la fois
fn ajouter(s: &mut String)

let mut s = String::from(« hello »);
ajouter(&mut s); // Emprunt mutable

// Règles du Borrowing :
// ✅ Plusieurs &T en même temps (lecture)
// ✅ Un seul &mut T à la fois (écriture)
// ❌ JAMAIS &T et &mut T en même temps

🧠 Quiz
Pourquoi ne peut-on pas avoir &T et &mut T en même temps ?
Pour empêcher les data races à la compilation. Si quelqu'un lit une valeur pendant qu'un autre la modifie, le résultat est imprévisible. Rust garantit cette sûreté par design, pas par convention.

CHAPITRE 05

Structures conditionnelles

🔀 if / match / loop
// if est une EXPRESSION (pas besoin de ternaire)
let label = if age < 18 else ;// match — LE mécanisme de contrôle en Rust (exhaustif !)
let x = 3;
let label = match x ;

// match avec garde
match age

// if let — match simplifié pour un seul cas
if let Some(val) = option {
println!(« Valeur:  », val);
}

// let-else (Rust 1.65+)
let Some(val) = option else ;

🔁 Boucles
// loop — boucle infinie (peut retourner une valeur)
let result = loop ;// while
while n > 0

// for — itération (pas de for classique i++)
for i in 0..5 { // 0, 1, 2, 3, 4 (exclusif)
println!(«  », i);
}
for i in 0..=5
for item in &vec
for (i, val) in vec.iter().enumerate() {
println!(« Index :  », i, val);
}

CHAPITRE 06

Structs et méthodes

📦 Définir une struct
// Struct avec champs nommés
struct Person // Créer une instance
let alice = Person ;

// Shorthand (même nom variable = champ)
let name = String::from(« Bob »);
let age = 30;
let bob = Person ;

// Update syntax — copier et modifier
let charlie = Person ;

🔧 impl — méthodes et fonctions associées
impl Person {
// Fonction associée (constructeur — pas de &self)
fn new(name: &str, age: u32) -> Self {
Person {
name: name.to_string(),
age,
}
}// Méthode (&self = emprunt immuable)
fn greet(&self) -> String {
format!(« Bonjour, je suis ( ans) », self.name, self.age)
}

// Méthode mutante (&mut self)
fn birthday(&mut self) {
self.age += 1;
}
}

let mut alice = Person::new(« Alice », 25);
println!(«  », alice.greet());
alice.birthday();

// Derive — générer automatiquement des traits
#[derive(Debug, Clone, PartialEq)]
struct Point
println!(«  », Point ); // Debug print

🧠 Quiz
Différence entre &self et &mut self ?
&self = emprunte l'objet en lecture seule (ne le modifie pas). &mut self = emprunte l'objet en écriture (peut le modifier). Respecte les règles de borrowing.

CHAPITRE 07

Enums et Pattern Matching

🎭 Enums — bien plus qu'en C ou Java
// Enum simple
enum Direction // Enum avec données (tagged union / ADT)
enum Shape

impl Shape

Les enums Rust = types algébriques (ADT). Chaque variante peut contenir des données différentes. Avec match exhaustif, le compilateur garantit que tous les cas sont traités. C'est bien plus puissant que les enums de TypeScript ou C#.

⭐ Option et le zéro null
// Rust n'a PAS de null. À la place : Option
enum Option<T> fn find_name(id: u32) -> Option<String>

// Utiliser une Option
match find_name(1) {
Some(name) => println!(« Trouvé:  », name),
None => println!(« Pas trouvé »),
}

// Méthodes pratiques
let val = find_name(1).unwrap(); // Panic si None !
let val = find_name(1).unwrap_or(« ? ».into()); // Valeur par défaut
let val = find_name(1).map(|n| n.to_uppercase()); // Transformer
let val = find_name(1)?; // ? = retourne None si None (early return)

CHAPITRE 08

Gestion d'erreurs

🛡️ Result — pas d'exceptions en Rust
// Rust n'a PAS d'exceptions. À la place : Result
enum Result<T, E> use std::fs;

fn lire_fichier(path: &str) -> Result<String, std::io::Error>

// Gérer avec match
match lire_fichier(« data.txt ») {
Ok(content) => println!(« Contenu:  », content),
Err(e) => eprintln!(« Erreur:  », e),
}

// L'opérateur ? — propager l'erreur automatiquement
fn process() -> Result<(), std::io::Error> {
let content = fs::read_to_string(« data.txt »)?; // Retourne Err si erreur
let config = fs::read_to_string(« config.toml »)?;
println!(«   », content, config);
Ok(())
}

L'opérateur ? est l'idiome Rust pour la gestion d'erreurs. Il remplace les try/catch de JavaScript ou Python. Si l'expression est Err, la fonction retourne l'erreur immédiatement. Si c'est Ok, elle déballe la valeur.

⚡ Méthodes utiles sur Result

let r: Result<i32, String> = Ok(42);

r.unwrap() // 42 (panic! si Err)
r.expect(« msg ») // 42 (panic! avec message si Err)
r.unwrap_or(0) // 42 (ou 0 si Err)
r.unwrap_or_default() // 42 (ou valeur par défaut du type)
r.is_ok() // true
r.is_err() // false
r.map(|v| v * 2) // Ok(84)
r.and_then(|v| Ok(v + 1)) // Ok(43) — chaînage

// Panic = erreur irrécupérable (crashe le programme)
panic!(« Erreur fatale ! »);

// unwrap est OK dans les tests et prototypes
// En production → utilisez ? ou match

CHAPITRE 09

Generics et Traits

🔄 Generics
// Fonction générique
fn largest<T: PartialOrd>(a: T, b: T) -> T // Struct générique
struct Pair<T>

// Impl générique
impl<T: std::fmt::Display> Pair<T> {
fn show(&self) {
println!(« (, ) », self.first, self.second);
}
}

📋 Traits — les interfaces de Rust
// Trait = contrat (comme une interface)
trait Summary {
fn summarize(&self) -> String;// Méthode par défaut (optionnelle)
fn preview(&self) -> String {
format!(« … », &self.summarize()[..20])
}
}

// Implémenter un trait
struct Article

impl Summary for Article {
fn summarize(&self) -> String {
format!(« :  », self.title, &self.content[..50])
}
}

// Trait comme paramètre (3 syntaxes équivalentes)
fn notify(item: &impl Summary) // Sucre syntaxique
fn notify<T: Summary>(item: &T) // Trait bound
fn notify<T>(item: &T) where T: Summary // where clause

// Traits courants à connaître
// Display — affichage avec
// Debug — affichage debug avec
// Clone — copie profonde
// Copy — copie implicite (stack only)
// PartialEq / Eq — comparaison ==
// PartialOrd / Ord — comparaison < >
// Iterator — for .. in
// From / Into — conversions

🧠 Quiz
Différence entre un trait Rust et une interface Java/C# ?
Les traits Rust peuvent avoir des implémentations par défaut, être implémentés sur des types existants (même de la bibliothèque standard), et servent de contraintes génériques. Ils sont plus flexibles qu'une interface classique.

CHAPITRE 10

Collections

📋 Vec, HashMap, HashSet

use std::collections::;

// Vec — le conteneur dynamique principal
let mut v: Vec<i32> = vec![1, 2, 3];
v.push(4);
v.pop(); // Some(4)
v.len(); // 3
v.is_empty(); // false
v[0] // 1 (panic si hors limites)
v.get(0) // Some(&1) (safe — retourne Option)
v.contains(&2) // true
v.sort();
v.dedup(); // Dédupliquer (trié d'abord)
v.retain(|&x| x > 1); // Garder si condition vraie

// HashMap
let mut ages = HashMap::new();
ages.insert(« Alice », 25);
ages.insert(« Bob », 30);
ages.get(« Alice ») // Some(&25)
ages.contains_key(« Bob ») // true
ages.remove(« Bob »);

// entry API — insérer si absent
ages.entry(« Eve »).or_insert(20);

// Itérer
for (nom, age) in &ages {
println!(« :  », nom, age);
}

// HashSet — éléments uniques
let set: HashSet<i32> = [1, 2, 2, 3].into_iter().collect(); //

CHAPITRE 11

Itérateurs et Closures

⚡ Closures
// Closure = fonction anonyme capturant son environnement
let add = |a: i32, b: i32| a + b;
add(3, 4) // 7let factor = 3;
let multiply = |x| x * factor; // Capture factor par référence
multiply(5) // 15

// move — force la capture par valeur (ownership)
let name = String::from(« Alice »);
let greet = move || println!(« Hello  », name);
// name n'est plus utilisable ici (moved dans la closure)

🌊 Chaîner les itérateurs

let nums = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Chaîner filter → map → collect
let result: Vec<i32> = nums.iter()
.filter(|&&n| n % 2 == 0) // Garder les pairs
.map(|&n| n * 2) // Doubler
.collect(); // Matérialiser en Vec
// [4, 8, 12, 16, 20]

// Autres méthodes
nums.iter().sum::<i32>() // 55
nums.iter().min() // Some(&1)
nums.iter().max() // Some(&10)
nums.iter().any(|&n| n > 5) // true
nums.iter().all(|&n| n > 0) // true
nums.iter().count() // 10
nums.iter().enumerate() // (0, &1), (1, &2), …
nums.iter().take(3) // Les 3 premiers
nums.iter().skip(2) // Sauter les 2 premiers
nums.iter().zip(&other) // Combiner deux itérateurs
nums.iter().fold(0, |acc, &n| acc + n) // Reduce

// collect est très flexible
let s: String = vec![« a », « b », « c »].join(« , « );
let map: HashMap<&str, i32> = vec![(« a »,1),(« b »,2)].into_iter().collect();

Les itérateurs Rust sont zero-cost. Le compilateur optimise les chaînes d'itérateurs en une seule boucle — aussi rapide qu'un for écrit à la main, mais bien plus lisible.

🧠 Quiz
Que fait .collect() ?
collect() matérialise un itérateur (lazy) en une collection concrète (Vec, HashMap, String…). Le type de sortie est déduit du contexte ou spécifié avec le turbofish ::>().

CHAPITRE 12

Concurrence et bonnes pratiques

🔀 Concurrence fearless
use std::thread;
use std::sync::;// Spawn un thread
let handle = thread::spawn(|| );
handle.join().unwrap(); // Attendre la fin

// Partager des données avec Arc + Mutex
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10

for h in handles
println!(« Total:  », *counter.lock().unwrap()); // 10

Fearless concurrency : Le système d'ownership empêche les data races à la compilation. Si vous essayez de partager des données mutables entre threads sans Mutex, le code ne compile simplement pas.

⚡ async/await
// async/await avec tokio (runtime async le plus populaire)
// Cargo.toml : tokio = async fn fetch_data(url: &str) -> Result<String, reqwest::Error>

#[tokio::main]
async fn main()

✅ Bonnes pratiques

✅ À FAIRE
clippy (cargo clippy) pour les linting
rustfmt (cargo fmt) pour le formatage
? pour la propagation d'erreurs
&str en paramètre, String en retour
#[derive(…)] pour les traits courants
match exhaustif
Option/Result au lieu de panic
• Itérateurs au lieu de boucles manuelles
• Documentation avec ///

❌ À ÉVITER
.unwrap() en production
.clone() partout (souvent un signe de mauvais design)
unsafe sauf nécessité absolue
• Ignorer les warnings du compilateur
• Variables mutables non nécessaires
Box quand un generic suffit
• Lifetime annotations inutiles (élision)
• Types imbriqués illisibles

🧠 Quiz
Comment Rust empêche les data races ?
Le système d'ownership et de borrowing interdit de partager des données mutables entre threads sans synchronisation explicite (Mutex, Arc). Si les règles ne sont pas respectées, le programme ne compile pas.
Qu'est-ce que l'opérateur ? en Rust ?
Il sert à propager les erreurs. Sur un Result, si c'est Ok il déballe la valeur, si c'est Err il retourne l'erreur. Sur un Option, si c'est Some il déballe, si c'est None il retourne None.

Cours Rust Complet — Ownership, Borrowing et programmation système sûre

Référence : The Rust Book | std docs | crates.io