Cours Rust Complet 🦀
Les 12 chapitres essentiels — ownership, borrowing, pattern matching et programmation système sûre
7. Enums et Pattern Matching
2. Variables et types de données
8. Gestion d'erreurs
3. Fonctions et expressions
9. Generics et Traits
4. Ownership et Borrowing
10. Collections
5. Structures conditionnelles
11. Itérateurs et Closures
6. Structs et méthodes
12. Concurrence et bonnes pratiques
🏠 Hub Programmation
🐍 Python
⚡ JavaScript
🔷 TypeScript
☁️ AWS Cloud Practitioner
CHAPITRE 01
Introduction et premier programme
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.
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
| Caractéristique | Rust |
|---|---|
| Typage | Statique et fort — vérifié à la compilation |
| Mémoire | Ownership system — pas de GC, pas de malloc/free |
| Sûreté | Garanti memory-safe à la compilation (pas de null, pas de data race) |
| Performance | Comparable au C/C++ — zero-cost abstractions |
| Usage | Systèmes, CLI, WebAssembly, réseau, embarqué, cloud |
| Convention | snake_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).
CHAPITRE 02
Variables et types de données
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.
| Type | Taille | Plage |
|---|---|---|
| i8 / i16 / i32 / i64 / i128 | 1-16 octets | Entier signé |
| u8 / u16 / u32 / u64 / u128 | 1-16 octets | Entier non-signé |
| isize / usize | Taille du pointeur | Pour les index |
| f32 / f64 | 4 / 8 octets | Flottant (f64 par défaut) |
| bool | 1 octet | true / false |
| char | 4 octets | Un caractère Unicode (pas ASCII !) |
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.
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
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.
let status = if age >= 18 else ;// Un bloc est une expression
let result = ; // result = 25
CHAPITRE 04
Ownership et Borrowing
// 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.
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
CHAPITRE 05
Structures conditionnelles
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 ;
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
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 ;
// 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
CHAPITRE 07
Enums et Pattern Matching
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#.
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
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.
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
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);
}
}
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
CHAPITRE 10
Collections
use std::collections::;
// Vec
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
let set: HashSet<i32> = [1, 2, 2, 3].into_iter().collect(); //
CHAPITRE 11
Itérateurs et Closures
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)
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.
::>() .CHAPITRE 12
Concurrence et bonnes pratiques
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.
// Cargo.toml : tokio = async fn fetch_data(url: &str) -> Result<String, reqwest::Error>
#[tokio::main]
async fn main()
✅ À 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
• Lifetime annotations inutiles (élision)
• Types imbriqués illisibles
🏠 Hub Programmation
🐍 Cours Python
⚡ Cours JavaScript
🔷 Cours TypeScript
☁️ AWS Cloud Practitioner
Cours Rust Complet — Ownership, Borrowing et programmation système sûre
Référence : The Rust Book | std docs | crates.io
