0% ont trouvé ce document utile (0 vote)
0 vues38 pages

Cours Rust Module1 060858

Ce cours sur Rust couvre les fondations du langage à travers quatre séances, abordant des concepts clés tels que l'environnement de développement, le modèle de mémoire, et le système d'ownership. Les participants apprendront à manipuler des références, des types, et à écrire des programmes idiomatiques en Rust. Le module inclut également des travaux pratiques pour installer l'environnement et créer un projet Cargo fonctionnel.

Transféré par

Dongmo Harold
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
0 vues38 pages

Cours Rust Module1 060858

Ce cours sur Rust couvre les fondations du langage à travers quatre séances, abordant des concepts clés tels que l'environnement de développement, le modèle de mémoire, et le système d'ownership. Les participants apprendront à manipuler des références, des types, et à écrire des programmes idiomatiques en Rust. Le module inclut également des travaux pratiques pour installer l'environnement et créer un projet Cargo fonctionnel.

Transféré par

Dongmo Harold
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

COURS COMPLET RUST


Module 1 — Fondations Rust (Séances 1–4)

Séance 1 : Introduction & Environnement


Séance 2 : Ownership & Borrowing — Partie I
Séance 3 : Ownership & Borrowing — Partie II
Séance 4 : Types Système & Structures

Niveau : Intermédiaire | Prérequis : C/C++ ou équivalent


Édition 2025

Édition 2025 Page 1


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Introduction Générale
À propos de ce cours
Ce cours couvre les fondations du langage Rust à travers 4 séances progressives. Chaque séance
allie théorie dense, exemples commentés et travaux pratiques guidés. L'objectif est de comprendre
non seulement la syntaxe, mais les raisons profondes derrière les choix de conception de Rust.

Objectifs pédagogiques du Module 1


• Maîtriser l'environnement de développement Rust (rustup, cargo, clippy, rustfmt)
• Comprendre en profondeur le modèle mémoire et le système d'ownership
• Manipuler les références, le borrow checker et les lifetimes basiques
• Modéliser des données avec des structs, enums, et le pattern matching
• Écrire des programmes Rust idiomatiques sans unsafe

Prérequis
• Maîtrise d'un langage à typage statique (C, C++, Java)
• Notions de base sur la mémoire (stack, heap, pointeurs)
• Familiarité avec la ligne de commande

Édition 2025 Page 2


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Séance 1 : Introduction & Environnement


Durée : ~3h cours + 2h TP

1.1 Pourquoi Rust ? Contexte et Philosophie

1.1.1 La crise des langages systèmes


Depuis des décennies, les développeurs sont pris entre deux options insatisfaisantes :
• Les langages de haut niveau (Python, Java) offrent sécurité et productivité, mais au prix
d'un ramasse-miettes (GC) qui introduit de la latence et de l'overhead mémoire.
• Les langages systèmes (C, C++) offrent performance maximale et contrôle fin de la
mémoire, mais au prix d'une catégorie entière de bugs : use-after-free, double-free, buffer
overflows, data races...
Selon Microsoft Security Response Center (2019), environ 70 % des CVEs critiques dans leurs
produits sont dus à des bugs de sécurité mémoire. La situation est similaire chez Google et
Mozilla.

1.1.2 La promesse Rust


Rust naît chez Mozilla Research vers 2006 (Graydon Hoare), avec une ambition : concilier sécurité
mémoire et performances sans GC, grâce à un système de types avancé analysé entièrement à la
compilation.

🎯 La Triade Rust
• Sécurité mémoire garantie à la compilation (sans GC)
• Performances comparables au C/C++
• Concurrence sans data races

Si votre programme compile en Rust, il est exempt de


undefined behavior mémoire et de data races.

1.1.3 Comparaison C / C++ / Go / Rust


Critère Comparaison
Gestion mémoire C : manuelle (malloc/free)
GC Non pour C, C++, Rust ; Oui pour Go
Sécurité mémoire C : non | C++ : partielle | Rust : totale | Go :
partielle
Abstraction zéro-coût C, C++, Rust : oui | Go : non
Concurrence sûre C++ : threads STL | Go : goroutines | Rust :
ownership

Édition 2025 Page 3


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Rust emprunte des idées à ML/Haskell (types algébriques, inférence de types) et à C++
(templates, RAII) tout en les reformulant dans un cadre cohérent centré sur la sécurité.

1.2 Installation et Outillage

1.2.1 rustup — Le gestionnaire de toolchains


rustup est le gestionnaire officiel de toolchains Rust. Il permet d'installer plusieurs versions (stable,
beta, nightly) et de gérer les cibles de compilation croisée.

# Installation (Linux / macOS)


curl --proto '=https' --tlsv1.2 -sSf [Link] | sh

# Windows : télécharger [Link] depuis [Link]

# Vérification
rustc --version # rustc 1.78.0 (9b00956e5 2024-04-29)
cargo --version # cargo 1.78.0 (54d8815d0 2024-03-26)
rustup --version # rustup 1.27.0

1.2.2 Cargo — Le build system et gestionnaire de paquets


Cargo est à Rust ce que npm est à [Link] ou Maven à Java. Il gère la compilation, les
dépendances, les tests, la documentation et la publication sur [Link].

cargo new mon_projet # Nouveau binaire


cargo new --lib ma_lib # Nouvelle bibliothèque
cargo build # Compilation debug
cargo build --release # Compilation optimisée
cargo run # Compile + exécute
cargo test # Lance les tests
cargo doc --open # Génère et ouvre la doc
cargo add serde # Ajoute une dépendance
cargo update # Met à jour les dépendances
cargo clippy # Lint avancé
cargo fmt # Formatage automatique

1.2.3 Structure d'un projet Cargo


mon_projet/
├── [Link] # Manifeste du projet
├── [Link] # Versions exactes des dépendances (à committer)
├── src/

Édition 2025 Page 4


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

│ ├── [Link] # Point d'entrée (binaire)


│ └── [Link] # Point d'entrée (bibliothèque)
├── tests/ # Tests d'intégration
│ └── [Link]
├── examples/ # Exemples exécutables
│ └── [Link]
└── target/ # Artéfacts de compilation (gitignore)
├── debug/
└── release/

1.2.4 [Link] — Le manifeste


[package]
name = "mon_projet"
version = "0.1.0"
edition = "2021" # Edition Rust (2015, 2018, 2021)

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

[dev-dependencies] # Uniquement pour les tests


criterion = "0.5"

[[Link]]
opt-level = 3
lto = true

1.2.5 Outils complémentaires


Outil Rôle
rustfmt Formatage automatique du code selon le style
officiel
clippy Linter avancé : détecte les anti-patterns et
suggère des améliorations
rust-analyzer LSP : autocomplétion, goto-definition,
diagnostics en temps réel
cargo-watch Recompilation automatique à chaque
modification
cargo-flamegraph Profiling par flamegraph
miri Interpréteur pour détecter les comportements
undefined

# Installation des outils

Édition 2025 Page 5


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

rustup component add rustfmt clippy rust-analyzer


cargo install cargo-watch cargo-flamegraph

# Configuration rustfmt (.[Link])


max_width = 100
tab_spaces = 4
edition = '2021'

1.3 Types Primitifs et Fonctions

1.3.1 Types scalaires


Rust est un langage à typage statique fort avec inférence de types. Chaque valeur a un type connu
à la compilation.

Type Description
i8, i16, i32, i64, i128, isize Entiers signés (8 à 128 bits, ou taille du pointeur)
u8, u16, u32, u64, u128, usize Entiers non signés
f32, f64 Flottants IEEE 754 (simple / double précision)
bool Booléen : true ou false
char Caractère Unicode (4 octets, pas un octet ASCII)

fn main() {
// Inférence de type
let x = 42; // i32 par défaut
let y: f64 = 3.14; // annotation explicite
let z = 2u8; // suffixe de type

// Séparateur visuel
let million = 1_000_000u64;

// Hexadécimal, octal, binaire


let hex = 0xFF;
let oct = 0o77;
let bin = 0b1111_0000;

// Caractère Unicode
let c: char = 'é';

let emoji: char = '🦀';

// Immutabilité par défaut


// x = 100; // ERREUR : cannot assign twice to immutable variable

Édition 2025 Page 6


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

let mut compteur = 0;


compteur += 1; // OK
}

1.3.2 Types composés


fn main() {
// Tuple : types hétérogènes, taille fixe
let tuple: (i32, f64, bool) = (42, 3.14, true);
let (a, b, c) = tuple; // destructuring
println!("{} {} {}", a, b, c);
println!("{}", tuple.0); // accès par index

// Tableau : type homogène, taille fixe connue à la compilation


let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0u8; 1024]; // 1024 zéros
println!("{}", arr[0]); // accès indexé
println!("{}", [Link]()); // longueur

// Vec : tableau dynamique (heap)


let mut v: Vec<i32> = Vec::new();
[Link](1);
[Link](2);
let v2 = vec![10, 20, 30]; // macro vec!
}

1.3.3 Fonctions
// Déclaration de fonction
fn additionner(a: i32, b: i32) -> i32 {
a + b // Pas de point-virgule = expression de retour
}

// Retour explicite
fn diviser(a: f64, b: f64) -> f64 {
if b == 0.0 {
return f64::INFINITY; // retour anticipé
}
a / b
}

// Fonction sans valeur de retour (type unit ())


fn afficher(msg: &str) {

Édition 2025 Page 7


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

println!("[INFO] {}", msg);


}

// Fonctions imbriquées
fn exterieur() -> i32 {
fn interieur(x: i32) -> i32 { x * 2 }
interieur(21)
}

1.3.4 Contrôle de flux


fn main() {
// if est une expression
let x = 7;
let message = if x > 0 { "positif" } else { "négatif ou nul" };

// loop : boucle infinie avec valeur de retour


let mut compteur = 0;
let resultat = loop {
compteur += 1;
if compteur == 10 { break compteur * 2; }
};

// while
let mut n = 1;
while n < 100 { n *= 2; }

// for : itérateur (le plus idiomatique)


for i in 0..5 { // Range [0, 5)
print!("{} ", i);
}
for i in 0..=5 { // Range [0, 5] inclusif
print!("{} ", i);
}

// Itération sur collection


let fruits = vec!["pomme", "banane", "cerise"];
for fruit in &fruits {
println!("{}", fruit);
}
}

Édition 2025 Page 8


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

1.4 Macros Essentielles


Les macros en Rust sont des métaprogrammes qui génèrent du code à la compilation. Elles se
distinguent des fonctions par le point d'exclamation (!).

// println! / print! : affichage formaté


println!("Valeur : {}", 42);
println!("Debug : {:?}", vec![1, 2, 3]);
println!("Debug pretty : {:#?}", vec![1, 2, 3]);
println!("Hex : {:x}, Bin : {:b}, Oct : {:o}", 255, 255, 255);
println!("Padding : {:>10}", "droite"); // aligné à droite
println!("Padding : {:<10}", "gauche"); // aligné à gauche

// format! : crée une String formatée


let s = format!("{}+{}={}", 1, 2, 3);

// panic! : terminaison du thread avec message


panic!("Erreur critique : {}", "description");

// assert! / assert_eq! : vérifications (pour les tests)


assert!(2 + 2 == 4);
assert_eq!(2 + 2, 4, "L'addition est cassée !");
assert_ne!(2 + 2, 5);

// todo! / unimplemented! : placeholder


fn a_implementer() -> i32 { todo!("À faire") }

1.5 Premier Programme : Hello World


// src/[Link]
fn main() {
// Macro d'impression standard
println!("Hello, World!");

// Arguments de la ligne de commande


let args: Vec<String> = std::env::args().collect();
if [Link]() > 1 {
println!("Bonjour, {} !", args[1]);
} else {
println!("Usage: {} <nom>", args[0]);
}
}

Édition 2025 Page 9


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

# Compilation et exécution
cargo run
cargo run -- Alice
cargo run -- 'Monde Rust'

# Compilation release
cargo build --release
./target/release/mon_projet Alice

TP 1 — Setup & CLI Simple


Durée estimée : 2h

Objectifs
• Installer et configurer l'environnement Rust complet
• Créer un projet Cargo fonctionnel
• Implémenter une CLI interactive avec gestion d'arguments

Partie A — Installation et vérification (30 min)


1. Installez rustup selon votre OS (voir section 1.2.1)
2. Vérifiez les versions : rustc, cargo, rustup
3. Installez les composants : rustfmt, clippy, rust-analyzer
4. Configurez votre éditeur (VS Code + extension rust-analyzer recommandé)

Partie B — Premier projet Cargo (30 min)


cargo new calculatrice_cli
cd calculatrice_cli
cargo run

Explorez la structure générée. Modifiez src/[Link] pour afficher un message personnalisé avec
votre nom.

Partie C — Calculatrice CLI (1h)


Implémentez une calculatrice en ligne de commande qui :
• Accepte des arguments : ./calculatrice_cli <nombre1> <opérateur> <nombre2>
• Supporte : +, -, *, /
• Gère la division par zéro avec un message d'erreur clair
• Affiche le résultat formaté

Édition 2025 Page 10


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

💡 Aide — Parsing des arguments


let args: Vec<String> = std::env::args().collect();
let a: f64 = args[1].parse().expect("Nombre invalide");
let op = &args[2];
let b: f64 = args[3].parse().expect("Nombre invalide");

Partie D — Extension (30 min — Bonus)


• Ajoutez le mode interactif : l'utilisateur saisit des expressions en boucle
• Utilisez std::io::stdin() pour la lecture
• Tapez 'quitter' pour sortir

Solution de référence
use std::env;

fn calculer(a: f64, op: &str, b: f64) -> Result<f64, String> {


match op {
"+" => Ok(a + b),
"-" => Ok(a - b),
"*" => Ok(a * b),
"/" => {
if b == 0.0 {
Err(String::from("Division par zéro"))
} else {
Ok(a / b)
}
}
_ => Err(format!("Opérateur inconnu : {}", op)),
}
}

fn main() {
let args: Vec<String> = env::args().collect();

if [Link]() != 4 {
eprintln!("Usage: {} <nombre> <op> <nombre>", args[0]);
eprintln!("Opérateurs : + - * /");
std::process::exit(1);
}

let a: f64 = match args[1].parse() {


Ok(n) => n,

Édition 2025 Page 11


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Err(_) => { eprintln!("'{}' n'est pas un nombre", args[1]);


std::process::exit(1); }
};
let op = &args[2];
let b: f64 = match args[3].parse() {
Ok(n) => n,
Err(_) => { eprintln!("'{}' n'est pas un nombre", args[3]);
std::process::exit(1); }
};

match calculer(a, op, b) {


Ok(resultat) => println!("{} {} {} = {}", a, op, b, resultat),
Err(e) => { eprintln!("Erreur : {}", e); std::process::exit(1); }
}
}

Édition 2025 Page 12


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Séance 2 : Ownership & Borrowing — Partie I


Durée : ~3h cours + 2h TP

L'ownership est le concept le plus fondamental et le plus original de Rust. C'est lui qui permet la
sécurité mémoire sans garbage collector. Si vous comprenez bien l'ownership, tout le reste de
Rust devient naturel.

2.1 Modèle Mémoire : Stack vs Heap

2.1.1 La Stack (pile)


La stack est une zone mémoire organisée en LIFO (Last In, First Out). Elle stocke les données
dont la taille est connue à la compilation.

• Allocation et désallocation instantanées (simple déplacement du pointeur de pile)


• Taille connue à la compilation
• Accès très rapide (localité de cache excellente)
• Taille limitée (typiquement 1–8 MB par thread)
• Durée de vie liée à la portée (scope) de la fonction

fn exemple_stack() {
let x: i32 = 42; // stocké sur la stack
let y: f64 = 3.14; // stocké sur la stack
let arr: [i32; 4] = [1, 2, 3, 4]; // stocké sur la stack
// À la sortie de la fonction : stack frame libérée automatiquement
}

2.1.2 Le Heap (tas)


Le heap est une zone mémoire dynamique. Les données dont la taille est inconnue à la
compilation ou qui doivent vivre au-delà d'une portée sont allouées sur le heap.

• Taille déterminée à l'exécution


• Allocation via l'allocateur système (similaire à malloc en C)
• Accès légèrement plus lent (indirection de pointeur)
• Taille pratiquement illimitée
• Durée de vie à gérer explicitement (en C/C++) ou automatiquement (Rust, GC)

fn exemple_heap() {
// String alloue sur le heap
let s = String::from("Bonjour");
// s : { ptr: 0x7f..., len: 7, capacity: 7 } <- stack

Édition 2025 Page 13


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

// |
// v
// heap: [B][o][n][j][o][u][r]

// Vec<T> aussi
let v: Vec<i32> = vec![1, 2, 3, 4, 5];
// v : { ptr: ..., len: 5, capacity: 5 } <- stack
// données réelles : sur le heap
}

2.1.3 Comparaison avec C


C (manuel) Rust (automatique)
malloc() / calloc() Box::new(), Vec::new(), String::new()...
free() Automatique à la fin du scope (Drop trait)
Pas de free → fuite mémoire Impossible : le compilateur garantit que Drop est
appelé
Double free → UB Impossible : ownership unique
Use after free → UB Impossible : le borrow checker l'interdit

2.2 Les Règles d'Ownership

2.2.1 Les trois règles fondamentales

📋 Règles d'Ownership — Gravez-les dans votre mémoire


Règle 1 : Chaque valeur en Rust a un propriétaire (owner).
Règle 2 : Il ne peut y avoir qu'un seul propriétaire à la fois.
Règle 3 : Quand le propriétaire sort du scope, la valeur est détruite.

{
let s = String::from("Bonjour"); // s devient propriétaire
// ... utilisation de s ...
} // scope de s termine → drop() appelé → mémoire libérée

2.2.2 Move Semantics


En Rust, l'affectation d'un type heap déplace (move) la propriété. L'ancienne variable devient
invalide.

fn main() {
let s1 = String::from("Bonjour");
let s2 = s1; // Move : s1 est INVALIDE après cette ligne

Édition 2025 Page 14


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

// println!("{}", s1); // ERREUR DE COMPILATION


// error[E0382]: borrow of moved value: `s1`

println!("{}", s2); // OK : s2 est le propriétaire


}

Pourquoi Rust fait-il ça ? En C++, l'affectation copie par défaut. En Rust, le move sémantique est
la copie par défaut pour les types heap, pour éviter d'avoir deux pointeurs vers la même donnée
(ce qui causerait un double-free).

2.2.3 Ownership et fonctions


fn afficher(s: String) { // s prend ownership
println!("{}", s);
} // s est droppé ici

fn dupliquer(s: String) -> String { // prend ET rend ownership


println!("{}", s);
s // retour = transfert d'ownership
}

fn main() {
let s1 = String::from("Bonjour");
afficher(s1);
// println!("{}", s1); // ERREUR : s1 a été moved

let s2 = String::from("Monde");
let s3 = dupliquer(s2); // s2 moved, s3 reçoit ownership
println!("{}", s3); // OK
}

2.3 Le Trait Copy

2.3.1 Types Copy vs Move


Les types dont la taille est fixe et dont la copie est triviale (types scalaires) implémentent le trait
Copy. Pour ces types, l'affectation copie la valeur au lieu de la déplacer.

fn main() {
// Types Copy : entiers, flottants, bool, char, tuples de Copy
let x: i32 = 42;

Édition 2025 Page 15


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

let y = x; // Copy : x ET y sont valides


println!("{} {}", x, y); // OK !

// Types NON-Copy : String, Vec, Box, etc.


let s1 = String::from("hello");
let s2 = s1; // Move : s1 invalide
// println!("{}", s1); // ERREUR
}

📌 Types Copy intégrés


• Tous les entiers : i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, isize, usize
• Flottants : f32, f64
• Booléen : bool
• Caractère : char
• Tuples si tous les éléments sont Copy : (i32, f64) est Copy
• Tableaux de Copy de taille fixe : [i32; N] est Copy
• Références immuables : &T est Copy

2.3.2 Implémenter Copy sur ses propres types


// On peut dériver Copy si tous les champs sont Copy
#[derive(Debug, Clone, Copy)]
struct Point {
x: f64,
y: f64,
}

fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1; // Copy car Point implémente Copy
println!("{:?} {:?}", p1, p2); // Les deux sont valides !
}

// IMPOSSIBLE : String ne peut pas être Copy


// #[derive(Copy)]
// struct Invalide {
// nom: String, // String n'est pas Copy !
// }

2.3.3 Clone : copie explicite et profonde


Pour les types non-Copy, on peut faire une copie explicite avec .clone(). C'est intentionnellement
verbeux pour rendre visible le coût d'allocation.

Édition 2025 Page 16


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

fn main() {
let s1 = String::from("Bonjour");
let s2 = [Link](); // Copie profonde : nouvelle allocation heap
println!("{} {}", s1, s2); // Les deux sont valides

// Performance : clone() alloue de la mémoire


// À éviter dans les hot paths ; préférer les références (séance 3)

let v1 = vec![1, 2, 3];


let v2 = [Link](); // Copie tous les éléments
println!("{:?} {:?}", v1, v2);
}

TP 2 — Gestion Mémoire Manuelle


Durée estimée : 2h

Objectifs
• Expérimenter les erreurs d'ownership pour les comprendre intuitivement
• Implémenter des transferts d'ownership entre fonctions
• Distinguer les types Copy des types Move

Partie A — Débogage d'erreurs d'ownership (45 min)


Compilez et corrigez chacun des programmes suivants. Pour chacun, expliquez l'erreur.

// Programme 1 : Pourquoi ce code ne compile-t-il pas ?


fn main() {
let v = vec![1, 2, 3];
let v2 = v;
println!("Longueur : {}", [Link]());
}

// Programme 2 : Corrigez sans utiliser clone()


fn somme(v: Vec<i32>) -> i32 {
[Link]().sum()
}

fn main() {
let nombres = vec![1, 2, 3, 4, 5];
let s = somme(nombres);
println!("Somme : {}, Vecteur : {:?}", s, nombres);

Édition 2025 Page 17


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Partie B — Implémentation (1h15)


Implémentez un gestionnaire de tâches simple (sans références) :

// Structure d'une tâche


fn creer_tache(titre: String, priorite: u8) -> (String, u8, bool) { ... }
fn afficher_tache(tache: (String, u8, bool)) { ... }
fn marquer_complete(tache: (String, u8, bool)) -> (String, u8, bool) { ... }
fn extraire_titre(tache: (String, u8, bool)) -> String { ... }

Gérez les transferts d'ownership correctement. Vous ne pouvez PAS utiliser de références (&)
dans cet exercice.

Édition 2025 Page 18


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Séance 3 : Ownership & Borrowing — Partie II


Durée : ~3h cours + 2h TP

Déplacer l'ownership à chaque appel de fonction serait trop lourd. Les références permettent
d'accéder à une valeur sans en prendre la propriété. C'est l'emprunt (borrowing).

3.1 Références et Emprunt (Borrowing)

3.1.1 Références immuables


fn calculer_longueur(s: &String) -> usize {
[Link]() // on emprunte s, on ne le possède pas
} // s sort du scope, mais la donnée n'est PAS droppée

fn main() {
let s1 = String::from("Bonjour");
let len = calculer_longueur(&s1); // on passe une référence
// s1 toujours valide ici !
println!("'{}' a {} caractères", s1, len);
}

Une référence (&T) est un pointeur garanti valide par le compilateur. Créer une référence s'appelle
emprunter (borrowing). L'emprunteur ne peut pas supprimer ni modifier la valeur (si la référence
est immuable).

3.1.2 Références mutables


fn ajouter_monde(s: &mut String) {
s.push_str(", Monde");
}

fn main() {
let mut s = String::from("Bonjour"); // doit être mut
ajouter_monde(&mut s); // référence mutable
println!("{}", s); // "Bonjour, Monde"
}

3.2 Les Règles du Borrow Checker

3.2.1 Les règles d'emprunt

Édition 2025 Page 19


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

📋 Règles d'emprunt — Non-négociables


Règle 1 : Vous pouvez avoir autant de références immuables (&T) que vous voulez.
Règle 2 : OU vous pouvez avoir exactement UNE référence mutable (&mut T).
Règle 3 : Ces deux cas sont mutuellement exclusifs.
Règle 4 : Une référence doit toujours être valide (pas de dangling references).

fn main() {
let mut s = String::from("Bonjour");

// OK : plusieurs références immuables simultanées


let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2); // r1 et r2 sont utilisées ici
// Après leur dernière utilisation, r1 et r2 sont libérées

// OK : maintenant on peut emprunter mutuellement


let r3 = &mut s;
r3.push_str(", Monde");
println!("{}", r3);
}

fn main() {
let mut s = String::from("Bonjour");

let r1 = &s; // OK
let r2 = &mut s; // ERREUR !
// error[E0502]: cannot borrow `s` as mutable because
// it is also borrowed as immutable

println!("{} {}", r1, r2);


}

3.2.2 Pourquoi ces règles ? Prévention des data races


Ces règles éliminent statiquement les data races — une condition de course sur les données. Une
data race nécessite :
5. Deux pointeurs ou plus vers les mêmes données
6. Au moins un pointeur est utilisé pour écrire
7. Pas de synchronisation

La règle 'une seule référence mutable OU plusieurs immuables' rend les data races impossibles.
Le compilateur fait le travail à votre place.

Édition 2025 Page 20


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

3.2.3 Dangling references — Impossibles en Rust


// Ce code ne compile PAS en Rust
// fn creer_reference() -> &String {
// let s = String::from("hello");
// &s // ERREUR : s est droppé à la fin de cette fonction !
// } // Le compilateur détecte que la référence sera invalide

// Solution : retourner la valeur (ownership transfer)


fn creer_string() -> String {
let s = String::from("hello");
s // Transfer d'ownership : pas de drop
}

3.3 Lifetimes

3.3.1 Qu'est-ce qu'une lifetime ?


Une lifetime est la portée pendant laquelle une référence est valide. Le compilateur vérifie que
toutes les références sont valides pour toute leur durée de vie. Dans la majorité des cas, le
compilateur infère les lifetimes automatiquement.

3.3.2 Annotations de lifetimes


// Sans annotation : le compilateur ne peut pas inférer
// fn plus_long(s1: &str, s2: &str) -> &str { // ERREUR

// Avec annotation : on dit que la sortie vit au moins aussi


// longtemps que l'entrée la plus courte
fn plus_long<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if [Link]() > [Link]() { s1 } else { s2 }
}

fn main() {
let s1 = String::from("longue chaîne");
let resultat;
{
let s2 = String::from("xyz");
resultat = plus_long(s1.as_str(), s2.as_str());
println!("La plus longue : {}", resultat);
// resultat utilisé avant la fin du scope de s2 : OK
}

Édition 2025 Page 21


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

3.3.3 Élision des lifetimes


Le compilateur applique des règles d'élision qui permettent d'omettre les annotations dans les cas
courants :
• Règle 1 : Chaque paramètre de référence reçoit sa propre lifetime
• Règle 2 : S'il y a exactement un paramètre de référence, sa lifetime est assignée à la sortie
• Règle 3 : S'il y a &self, sa lifetime est assignée à la sortie

// Ces deux signatures sont équivalentes :


fn premiere_lettre(s: &str) -> &str { &s[..1] }
fn premiere_lettre_explicit<'a>(s: &'a str) -> &'a str { &s[..1] }

// Struct avec lifetime


struct Extrait<'a> {
partie: &'a str, // doit vivre au moins aussi longtemps que la struct
}

impl<'a> Extrait<'a> {
fn afficher(&self) -> &str { // élision : lifetime de self
[Link]
}
}

3.4 Slices

3.4.1 Slices de tableaux


fn somme_slice(s: &[i32]) -> i32 {
[Link]().sum()
}

fn main() {
let arr = [1, 2, 3, 4, 5];
let v = vec![10, 20, 30, 40, 50];

// Slice d'un tableau


let s1 = &arr[1..4]; // &[i32] : [2, 3, 4]
let s2 = &v[..3]; // [10, 20, 30]
let s3 = &arr[2..]; // [3, 4, 5]
let s4 = &arr[..]; // tout le tableau

Édition 2025 Page 22


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

println!("{}", somme_slice(&arr)); // fonctionne avec tableau


println!("{}", somme_slice(&v)); // fonctionne avec Vec
println!("{}", somme_slice(s1)); // et avec une slice

// Méthodes utiles sur les slices


println!("Longueur : {}", [Link]());
println!("Vide : {}", s1.is_empty());
println!("Premier : {:?}", [Link]());
println!("Dernier : {:?}", [Link]());
}

3.4.2 Slices de chaînes (&str vs String)


fn main() {
// &str : slice de chaîne (référence vers des données UTF-8)
let s_lit: &str = "Bonjour"; // données statiques (programme)

// String : chaîne possédée sur le heap


let mut s_own: String = String::from("Monde");
s_own.push('!');

// Slice d'une String


let s_slice: &str = &s_own[..5]; // "Monde"

// Bonne pratique : prendre &str (plus flexible)


fn premiere_lettre(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &b) in [Link]().enumerate() {
if b == b' ' { return &s[..i]; }
}
&s[..]
}

// Fonctionne avec &str ET &String (déréférencement auto)


println!("{}", premiere_lettre(s_lit));
println!("{}", premiere_lettre(&s_own));
}

&str String
&str Référence vers des données UTF-8 existantes
String Données possédées sur le heap
Taille connue à la compilation

Édition 2025 Page 23


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Taille dynamique
Immuable
Peut être mutée
Pas d'allocation
Alloue sur le heap

TP 3 — Analyseur de Chaînes
Durée estimée : 2h

Objectifs
• Manipuler des références et des slices
• Appliquer les règles du borrow checker dans un cas réel
• Travailler avec &str et String

Spécification
Implémentez un analyseur de texte avec les fonctions suivantes (toutes basées sur des
références) :

// Compte les mots dans un texte


fn compter_mots(texte: &str) -> usize { ... }

// Retourne le mot le plus long


fn mot_le_plus_long<'a>(texte: &'a str) -> &'a str { ... }

// Vérifie si le texte est un palindrome


fn est_palindrome(texte: &str) -> bool { ... }

// Retourne les N premiers mots


fn premiers_mots(texte: &str, n: usize) -> Vec<&str> { ... }

// Remplace les occurrences (retourne String)


fn remplacer(texte: &str, de: &str, vers: &str) -> String { ... }

// Statistiques globales
fn analyser(texte: &str) -> Statistiques { ... }

struct Statistiques {
nb_mots: usize,

Édition 2025 Page 24


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

nb_caracteres: usize,
nb_phrases: usize,
mot_le_plus_long: String,
frequence_chars: [(char, usize); 5], // top 5
}

Tests
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_compter_mots() {
assert_eq!(compter_mots("hello world"), 2);
assert_eq!(compter_mots(""), 0);
assert_eq!(compter_mots(" espaces "), 1);
}

#[test]
fn test_palindrome() {
assert!(est_palindrome("kayak"));
assert!(est_palindrome("A man a plan a canal Panama"));
assert!(!est_palindrome("Rust"));
}
}

Édition 2025 Page 25


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Séance 4 : Types Système & Structures


Durée : ~3h cours + 2h TP

Rust dispose d'un système de types algébriques expressif. Les structs et les enums, combinés au
pattern matching, permettent de modéliser n'importe quel domaine avec précision et sécurité.

4.1 Structs

4.1.1 Structs nommées


// Déclaration
#[derive(Debug, Clone)]
struct Utilisateur {
nom: String,
email: String,
age: u32,
actif: bool,
}

fn main() {
// Instanciation
let user1 = Utilisateur {
nom: String::from("Alice"),
email: String::from("alice@[Link]"),
age: 30,
actif: true,
};

// Accès aux champs


println!("Nom : {}", [Link]);

// Mise à jour avec spread (struct update syntax)


let user2 = Utilisateur {
email: String::from("bob@[Link]"),
nom: String::from("Bob"),
..user1 // les autres champs de user1
// Note : [Link] et [Link] sont moved !
};
println!("{:?}", user2);
}

Édition 2025 Page 26


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

4.1.2 Tuple structs et Unit structs


// Tuple struct : champs sans nom
#[derive(Debug)]
struct Couleur(u8, u8, u8); // RGB

#[derive(Debug)]
struct Point(f64, f64);

// Unit struct : pas de données, pour les traits


struct MarkerType;

fn main() {
let rouge = Couleur(255, 0, 0);
let origine = Point(0.0, 0.0);

// Accès par index


println!("R={} G={} B={}", rouge.0, rouge.1, rouge.2);
println!("x={} y={}", origine.0, origine.1);
}

4.2 Blocs impl — Méthodes et Méthodes Associées

4.2.1 Méthodes d'instance


#[derive(Debug)]
struct Rectangle {
largeur: f64,
hauteur: f64,
}

impl Rectangle {
// Méthode : premier paramètre est &self (ou self, &mut self)
fn aire(&self) -> f64 {
[Link] * [Link]
}

fn perimetre(&self) -> f64 {


2.0 * ([Link] + [Link])
}

fn est_carre(&self) -> bool {


([Link] - [Link]).abs() < f64::EPSILON

Édition 2025 Page 27


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

// Méthode mutable
fn mettre_a_l_echelle(&mut self, facteur: f64) {
[Link] *= facteur;
[Link] *= facteur;
}

// Méthode qui consomme la struct


fn detruire(self) -> String {
format!("Rect {}x{} détruit", [Link], [Link])
}
}

4.2.2 Méthodes associées (fonctions statiques)


impl Rectangle {
// Méthode associée : pas de self → comme une fonction statique
fn nouveau(largeur: f64, hauteur: f64) -> Self {
Rectangle { largeur, hauteur } // shorthand si nom = variable
}

fn carre(cote: f64) -> Self {


Rectangle { largeur: cote, hauteur: cote }
}
}

fn main() {
let r = Rectangle::nouveau(10.0, 5.0); // :: pour les associées
let c = Rectangle::carre(4.0);

println!("Aire : {}", [Link]()); // . pour les méthodes


println!("Périmètre : {}", [Link]());
println!("Carré ? {}", c.est_carre());
}

4.3 Enums et Pattern Matching

4.3.1 Enums de base


#[derive(Debug)]
enum Direction {

Édition 2025 Page 28


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Nord,
Sud,
Est,
Ouest,
}

#[derive(Debug)]
enum StatutConnexion {
Connecté,
Déconnecté,
EnCours { depuis: u64 }, // variante avec données nommées
Erreur(String), // variante avec données tuple
}

4.3.2 Enums avec données — L'enum comme union sûre


// Exemple avancé : représenter un message réseau
#[derive(Debug)]
enum Message {
Quitter, // pas de données
Déplacer { x: i32, y: i32 }, // struct interne
Écrire(String), // tuple-like
ChangerCouleur(u8, u8, u8), // tuple de 3
}

impl Message {
fn traiter(&self) {
match self {
Message::Quitter => println!("Quit reçu"),
Message::Déplacer { x, y } => println!("Move ({},{})", x, y),
Message::Écrire(texte) => println!("Texte : {}", texte),
Message::ChangerCouleur(r, g, b) => {
println!("Couleur RGB({},{},{})", r, g, b)
}
}
}
}

4.3.3 Pattern Matching exhaustif


fn analyser_direction(dir: Direction) -> &'static str {
match dir {
Direction::Nord => "vers le nord",

Édition 2025 Page 29


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Direction::Sud => "vers le sud",


Direction::Est => "vers l'est",
Direction::Ouest => "vers l'ouest",
// Le compilateur GARANTIT l'exhaustivité !
// Si on oublie un cas → erreur de compilation
}
}

fn analyser_statut(statut: &StatutConnexion) {
match statut {
StatutConnexion::Connecté => println!("Connecté"),
StatutConnexion::Déconnecté => println!("Déconnecté"),
StatutConnexion::EnCours { depuis } => {
println!("En cours depuis {}s", depuis)
}
StatutConnexion::Erreur(msg) => {
eprintln!("Erreur : {}", msg)
}
}
}

4.3.4 Patterns avancés


fn categoriser(n: i32) -> &'static str {
match n {
0 => "zéro",
1..=9 => "un chiffre", // range pattern
10 | 20 | 30 => "dizaine ronde", // or pattern
x if x < 0 => "négatif", // guard condition
_ => "autre", // wildcard
}
}

// if let : matching sur une seule variante


let valeur = Some(42);
if let Some(v) = valeur {
println!("On a : {}", v);
}

// while let : boucle tant que le pattern matche


let mut pile = vec![1, 2, 3];
while let Some(sommet) = [Link]() {
println!("Dépilé : {}", sommet);

Édition 2025 Page 30


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

4.4 Option<T> — L'absence de valeur sûre

4.4.1 Problème : null en C/C++


En C et C++, null est une source majeure de bugs (null pointer dereference). Tony Hoare,
l'inventeur de null, l'a qualifié de 'milliard de dollars de dommages'.

Rust n'a PAS de null. À la place, il a Option<T>.

4.4.2 Option<T>
// Défini dans la bibliothèque standard
// enum Option<T> {
// Some(T), // il y a une valeur
// None, // pas de valeur
// }

fn diviser(a: f64, b: f64) -> Option<f64> {


if b == 0.0 {
None
} else {
Some(a / b)
}
}

fn main() {
// match : gestion explicite
match diviser(10.0, 2.0) {
Some(resultat) => println!("Résultat : {}", resultat),
None => println!("Division par zéro"),
}

// Méthodes sur Option


let opt: Option<i32> = Some(42);
let valeur = [Link](); // panic si None !
let valeur = opt.unwrap_or(0); // 0 si None
let valeur = opt.unwrap_or_else(|| calcul_coûteux());
let doublé = [Link](|v| v * 2); // Some(84)
let filtre = [Link](|v| *v > 10); // Some(42)
let est_some = opt.is_some(); // true

Édition 2025 Page 31


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

let est_none = opt.is_none(); // false


}

4.5 Result<T, E> — Gestion d'erreurs

4.5.1 Philosophie de la gestion d'erreurs en Rust


Rust distingue deux catégories d'erreurs :
• Erreurs récupérables : fichier non trouvé, entrée invalide → Result<T, E>
• Erreurs irrécupérables : bug de programmation → panic!

4.5.2 Result<T, E>


// Défini dans la bibliothèque standard
// enum Result<T, E> {
// Ok(T), // succès avec valeur
// Err(E), // échec avec erreur
// }

use std::num::ParseIntError;

fn parser_entier(s: &str) -> Result<i32, ParseIntError> {


[Link]::<i32>() // retourne déjà un Result
}

fn main() {
match parser_entier("42") {
Ok(n) => println!("Parsé : {}", n),
Err(e) => println!("Erreur : {}", e),
}

// Méthodes sur Result


let r: Result<i32, &str> = Ok(10);
let n = [Link](); // panic si Err
let n = r.unwrap_or(0); // 0 si Err
let n = [Link]("Échec"); // panic avec message personnalisé
let doublé = [Link](|v| v * 2); // Ok(20)
let est_ok = r.is_ok(); // true
}

4.5.3 L'opérateur ? — Propagation d'erreurs


use std::fs;

Édition 2025 Page 32


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

use std::io;

// Sans ? : verbeux
fn lire_fichier_v1(chemin: &str) -> Result<String, io::Error> {
let contenu = match fs::read_to_string(chemin) {
Ok(c) => c,
Err(e) => return Err(e),
};
Ok(contenu)
}

// Avec ? : élégant
fn lire_fichier_v2(chemin: &str) -> Result<String, io::Error> {
let contenu = fs::read_to_string(chemin)?; // Retourne si Err
Ok(contenu)
}

// ? peut chaîner plusieurs opérations


fn lire_et_parser(chemin: &str) -> Result<i32, Box<dyn std::error::Error>> {
let contenu = fs::read_to_string(chemin)?;
let nombre: i32 = [Link]().parse()?;
Ok(nombre * 2)
}

TP 4 — Modélisation de Processus OS
Durée estimée : 2h

Contexte
Vous allez modéliser un gestionnaire de processus simplifié inspiré des systèmes Unix. Ce TP met
en pratique structs, enums, pattern matching, Option et Result.

Spécification
#[derive(Debug, Clone)]
enum EtatProcessus {
Prêt,
EnExécution { cpu_id: u8 },
Bloqué { raison: String },
Terminé { code_retour: i32 },
Zombie,

Édition 2025 Page 33


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

#[derive(Debug, Clone)]
enum Priorité {
TrèsFaible,
Faible,
Normale,
Haute,
TrèsHaute,
Temps_Réel(u8), // niveau 0-99
}

#[derive(Debug)]
struct Processus {
pid: u32,
nom: String,
état: EtatProcessus,
priorité: Priorité,
mémoire_ko: u64,
pid_parent: Option<u32>,
}

#[derive(Debug)]
struct GestionnaireProcessus {
processus: Vec<Processus>,
prochain_pid: u32,
}

Méthodes à implémenter
impl GestionnaireProcessus {
fn nouveau() -> Self { ... }

fn créer_processus(
&mut self,
nom: String,
priorité: Priorité,
mémoire_ko: u64,
pid_parent: Option<u32>,
) -> u32 { ... } // Retourne le PID

fn trouver(&self, pid: u32) -> Option<&Processus> { ... }

Édition 2025 Page 34


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

fn changer_état(
&mut self,
pid: u32,
nouvel_état: EtatProcessus,
) -> Result<(), String> { ... }

fn mémoire_totale_utilisée(&self) -> u64 { ... }

fn processus_par_état(&self, état: &EtatProcessus) -> Vec<&Processus> { ... }

fn tuer_processus(&mut self, pid: u32) -> Result<i32, String> {


// Change l'état en Terminé { code_retour: 0 }
// Retourne le code de retour ou une erreur si PID introuvable
}

fn afficher_résumé(&self) { ... }
}

Programme principal
fn main() {
let mut gp = GestionnaireProcessus::nouveau();

// Créer init (PID 1)


let init = [Link]éer_processus(
String::from("init"),
Priorité::Haute,
1024,
None, // pas de parent
);

// Créer des processus fils


let bash = [Link]éer_processus(
String::from("bash"),
Priorité::Normale,
4096,
Some(init),
);

// Changer les états


gp.changer_état(bash, EtatProcessus::EnExécution { cpu_id: 0 }).unwrap();

Édition 2025 Page 35


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

// Afficher le résumé
gp.afficher_résumé();

// Tuer un processus
match gp.tuer_processus(bash) {
Ok(code) => println!("bash terminé avec code {}", code),
Err(e) => eprintln!("Erreur : {}", e),
}
}

Édition 2025 Page 36


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

Annexes
A. Récapitulatif des Règles Fondamentales

📋 Ownership
1. Chaque valeur a un seul propriétaire
2. Quand le propriétaire sort du scope, la valeur est détruite
3. L'affectation déplace (move) pour les types heap
4. Les types Copy sont copiés automatiquement

📋 Borrowing
1. Plusieurs &T (références immuables) simultanées sont OK
2. Une seule &mut T (référence mutable) à la fois
3. Les deux types ne peuvent pas coexister
4. Toute référence doit être valide pendant toute sa durée de vie

B. Cheatsheet des Types


Type Description
i32, u64, f64, bool, char Types scalaires (Copy)
String Chaîne possédée (heap, mutable)
&str Slice de chaîne (référence)
Vec<T> Tableau dynamique (heap)
&[T] Slice de tableau (référence)
(T1, T2, T3) Tuple (Copy si tous les T le sont)
[T; N] Tableau de taille fixe (stack/Copy si T Copy)
Box<T> Pointeur intelligent (heap)
Option<T> Valeur optionnelle
Result<T, E> Résultat ou erreur

C. Commandes Cargo Essentielles


cargo new nom # Nouveau projet binaire
cargo new --lib nom # Nouveau projet bibliothèque
cargo build # Compile en mode debug
cargo build --release # Compile en mode release (optimisé)
cargo run # Compile et exécute
cargo run -- arg1 arg2 # Avec arguments
cargo test # Lance tous les tests
cargo test nom_du_test # Lance un test spécifique

Édition 2025 Page 37


🦀 Cours Rust — Module 1 : Fondations | Séances 1–4

cargo clippy # Analyse statique avancée


cargo fmt # Formate le code
cargo doc --open # Génère et ouvre la documentation
cargo add crate_name # Ajoute une dépendance
cargo update # Met à jour les dépendances
cargo check # Vérifie la compilation sans produire de binaire

D. Ressources Complémentaires
• The Rust Book (officiel) : [Link]
• Rust by Example : [Link]
• Rustlings (exercices interactifs) : [Link]
• [Link] (bibliothèques) : [Link]
• [Link] (documentation) : [Link]
• Playground Rust en ligne : [Link]
• Rust Reference (exhaustif) : [Link]

E. Erreurs de Compilation Fréquentes


Code Erreur Cause et Solution
E0382 : borrow of moved value Vous utilisez une variable après l'avoir déplacée
(moved)
E0502 : cannot borrow as mutable because Vous créez une &mut en même temps qu'une &
immutable borrow exists
E0505 : cannot move out of ... because it is Vous essayez de déplacer une valeur empruntée
borrowed
E0308 : mismatched types Types incompatibles : vérifiez les conversions
E0369 : operator cannot be applied to type Le type n'implémente pas le trait requis (Display,
Add, etc.)

Édition 2025 Page 38

Vous aimerez peut-être aussi