let x = 5; chasse fixe, gras : code à taper tel quelnom italique : valeur à remplacer// → 42 commentaire : résultat obtenuE0382 code d’erreur du compilateur| rustup | installateur officiel et gestionnaire de versions (rustup.rs) |
| rustup update | mettre à jour compilateur et outils |
| cargo new projet | créer un projet (--lib : une bibliothèque) |
| cargo run | compiler et lancer |
| cargo check | vérifier sans produire d’exécutable (rapide) |
| cargo build --release | compilation optimisée → target/release/ |
| cargo test | lancer les tests |
| cargo fmt | mettre le code en forme |
| cargo clippy | conseils de qualité (analyseur) |
| rustup doc --std | documentation de la bibliothèque standard, hors ligne |
Windows : rustup-init.exe ; il propose d’installer les outils Visual Studio (éditeur de liens MSVC) requis.
Une version stable toutes les 6 semaines ; au 29 septembre 2026 : 1.98. Édition courante : 2024 (depuis Rust 1.85).
// src/main.rs
fn main() {
let nom = "monde";
println!("Bonjour, {nom} !");
}fn main() : point d’entrée du programme.println! : le ! signale une macro.; ; blocs entre { }.// ligne · /* */ bloc · /// documentation de l’élément qui suit.snake_case variables, fonctions · CamelCase types, traits · MAJUSCULES constantes.| let x = 5; | liaison immuable (par défaut) |
| let mut n = 0; | liaison modifiable |
| let x: i64 = 5; | type explicite (sinon déduit) |
| let x = x * 2; | masquage : nouvelle variable de même nom, type libre |
| const MAX: u32 = 100; | constante : type obligatoire, calculée à la compilation |
| static NOM: &str = "a"; | valeur globale unique |
| let (a, b) = (1, 2); | déstructuration |
| let _x = 1; | _ en tête : pas d’avertissement si inutilisée |
! Réaffecter une liaison sans mut : erreur E0384.
| i8 i16 i32 i64 i128 | entiers signés ; i32 par défaut |
| u8 u16 u32 u64 u128 | entiers non signés ; u8 = octet |
| isize usize | taille d’une adresse (64 bits sur un PC actuel) ; index et longueurs : usize |
| f32 f64 | flottants IEEE 754 ; f64 par défaut |
| bool | true · false |
| char | un caractère Unicode, 4 octets : 'é' |
| () | « unité » : absence de valeur |
| 1_000_000 | _ : séparateur visuel |
| 0xff 0o77 0b1010 | hexadécimal · octal · binaire |
| 255u8 2.5f32 | suffixe de type |
| b'A' b"abc" | octet u8 · chaîne d’octets |
| '\n' '\t' '\\' '\u{e9}' | caractères échappés (\u{e9} = é) |
| r"C:\d" · r#"a "b""# | chaînes brutes : \ et " pris à la lettre |
Bornes : i32::MAX, u8::MIN… ! Dépassement d’entier : panique en mode debug, résultat « rebouclé » en --release. Choisir : checked_add (→ Option), wrapping_add, saturating_add.
| (i32, f64, char) | tuple : types mêlés, taille fixe ; t.0, t.1 |
| [i32; 5] | tableau : taille fixe ; [0; 5] = cinq zéros |
| &[i32] | tranche (slice) : vue sur une suite contiguë ; &a[1..3] |
| &str | tranche de texte UTF-8 ; littéraux "…" |
| String | texte possédé, modifiable (tas) |
| Vec<T> | tableau dynamique possédé (tas) |
a[i] hors limites : panique, jamais de lecture hasardeuse. a.get(i) renvoie une Option.
| x as f64 | entre types primitifs, sans contrôle |
| f64::from(x) · x.into() | sans perte possible (From / Into) |
| u8::try_from(x) | faillible → Result |
| "42".parse::<i32>() | texte → nombre : Result |
| n.to_string() | nombre → String |
| s.as_str() · &s | String → &str |
| s.to_string() | &str → String (ou String::from(s)) |
! as tronque : 300_i32 as u8 → 44 ; -1_i32 as u32 → 4294967295 ; 2.9_f64 as i32 → 2 ; 300.0_f64 as u8 → 255.
| {} | affichage « utilisateur » (Display) |
| {:?} · {:#?} | débogage (Debug) · indenté |
| {x} · {0} · {nom} | variable · position · argument nommé |
| {:>8} {:<8} {:^8} | à droite · à gauche · centré sur 8 |
| {:08.2} | 8 caractères, zéros devant, 2 décimales |
| {:+} | signe toujours affiché |
| {:x} {:X} {:o} {:b} | hexadécimal · majuscules · octal · binaire |
| {:#x} · {:#b} | avec préfixe 0x · 0b |
| {:e} | notation scientifique |
| {:>w$} · {:.p$} | largeur · précision prises dans w · p |
| {{ · }} | accolade littérale |
| print! · println! | sortie standard |
| eprint! · eprintln! | sortie d’erreur |
| format!(…) | même syntaxe → String |
| dbg!(expr) | affiche fichier, ligne, expression et valeur (sur stderr), puis la renvoie |
| write! · writeln! | vers un fichier ou un tampon |
| vec![1, 2, 3] | construire un Vec |
| assert!(c) · assert_eq!(a, b) | vérifier, sinon paniquer |
| todo!() | code à écrire : panique si atteint |
| unreachable!() | branche jugée impossible |
E0382 → rustc --explain E0382 : explication et exemples.help: : le correctif y est souvent proposé.cargo check pour itérer vite ; cargo clippy pour les tournures idiomatiques.| E0308 | types incompatibles | conversion ? ; final en trop ? |
| E0277 | trait non implémenté | derive, impl, autre type |
| E0599 | méthode introuvable | type exact ? trait importé ? |
| E0425 | nom inconnu | orthographe, portée |
| E0432 · E0433 | import · chemin non résolu | use, mod, dépendance |
Erreurs d’emprunt : page 3. Hors blocs unsafe, le compilateur écarte des classes entières d’erreurs mémoire : pointeur pendant, double libération, accès concurrent non synchronisé.
| + - * / % | arithmétique ; entre entiers, / division entière : 7 / 2 → 3, % reste |
| == != < > <= >= | comparaison |
| && || ! | logique (évaluation paresseuse) |
| & | ^ << >> | bit à bit ; !n : complément |
| += -= *= /= %= | affectation composée |
| a..b · a..=b | intervalle exclusif · inclusif |
| &x · &mut x · *r | emprunter · en modification · déréférencer |
| ? | propager une erreur (page 5) |
| a::b | chemin : std::fs::read, String::new |
Ni ++ ni -- : n += 1. Puissance : 2_i32.pow(10), x.powf(0.5), x.sqrt(). Arrondis : round, floor, ceil · bornes : min, max, clamp · reste positif : rem_euclid (-7 % 3 → -1). ! Flottants : comparer (a - b).abs() < 1e-9, pas a == b après un calcul.
let signe = if n < 0 { "négatif" } else { "positif" };
let carre = {
let a = 3;
a * a // pas de ; : valeur du bloc
}; // carre = 9Un bloc vaut sa dernière expression sans ; ; avec ;, il vaut (). Les branches d’un if doivent avoir le même type.
fn aire(l: f64, h: f64) -> f64 {
l * h // valeur renvoyée
}
fn diviser(a: i32, b: i32) -> Option<i32> {
if b == 0 {
return None; // sortie anticipée
}
Some(a / b)
}
fn saluer(nom: &str) { // renvoie ()
println!("Bonjour {nom}");
}->.Option, noms distincts, constructeurs.fn f() -> i32 { 5; } : le ; fait renvoyer () → E0308.-> ! : la fonction ne revient jamais (panic!, std::process::exit).let double = |x| x * 2;
let somme = |a: i32, b: i32| -> i32 { a + b };
let seuil = 10;
let grand = |x: &i32| *x > seuil; // capture seuil
let f = move || println!("{seuil}");// prend possession| Fn | lit ce qu’elle capture ; appelable à volonté |
| FnMut | modifie ce qu’elle capture |
| FnOnce | consomme ce qu’elle capture : un seul appel |
| fn(i32) -> i32 | pointeur de fonction (aucune capture) |
move : indispensable quand la fermeture survit à la portée courante (thread, valeur renvoyée). Accepter une fermeture : fn appliquer(f: impl Fn(i32) -> i32).
for i in 0..5 { } // 0, 1, 2, 3, 4
for i in 1..=5 { } // 1 à 5 inclus
for i in (0..10).rev() { } // 9 à 0
for i in (0..10).step_by(3) { } // 0, 3, 6, 9
for (i, x) in v.iter().enumerate() { }
while n > 0 { n -= 1; }
let mut k = 0;
let r = loop { // boucle sans fin…
k += 1;
if k == 10 { break k * 2; } // …qui renvoie 20
};
'externe: for x in 0..5 { // étiquette
for y in 0..5 {
if x + y > 6 { break 'externe; }
if y > x { continue 'externe; }
}
}for x in &v parcourt sans consommer ; for x in v consomme v (inutilisable ensuite) ; for x in &mut v permet de modifier : *x += 1.
match n {
0 => println!("zéro"),
1 | 2 => println!("un ou deux"),
3..=9 => println!("chiffre"),
x if x < 0 => println!("négatif : {x}"),
grand @ 100.. => println!("grand : {grand}"),
_ => println!("autre"),
}
let pays = match code { // une expression
'F' => "France",
'D' => "Allemagne",
_ => "inconnu",
};| _ | n’importe quoi, sans lier |
| x | lie la valeur à x |
| a | b | l’un ou l’autre |
| 1..=5 · 'a'..='z' | intervalle inclusif |
| x if condition | garde : condition supplémentaire |
| n @ 1..=9 | tester et lier à la fois |
| (a, _, c) | tuple |
| [premier, .., dernier] | tableau ou tranche ; .. : le reste |
| Point { x, y: 0, .. } | structure : champs lus, champs testés |
| Some(v) · Err(e) | variante d’une énumération |
| &x | déstructurer une référence |
! Un match doit être exhaustif : tout cas possible couvert, sinon E0004. _ en dernier recueille le reste.
if let Some(x) = option { // un seul motif
println!("{x}");
} else {
println!("rien");
}
let Some(x) = option else { // let … else
return; // doit sortir
}; // x reste utilisable
while let Some(sommet) = pile.pop() {
println!("{sommet}");
}
if let Some(x) = option && x > 10 { } // édition 2024
let voyelle = matches!(c, 'a' | 'e' | 'i' | 'o' | 'u');Le bloc else d’un let … else doit diverger : return, break, continue, panic!. Chaînes if let … && : édition 2024, Rust 1.88 ou plus.
free.Les types Copy (entiers, flottants, bool, char, &T, tuples et tableaux de types Copy) sont copiés au lieu d’être déplacés. Les autres (String, Vec, Box…) se dupliquent explicitement avec .clone() : une copie coûteuse reste visible.
let s1 = String::from("salut");
let s2 = s1; // déplacement : s1 n'est plus utilisable
s1 (invalidée) s2 tas
+-----+-------+ +-----+-------+ +---+---+---+---+---+
| ptr | x | | ptr | o---+---> | s | a | l | u | t |
| len | 5 | | len | 5 | +---+---+---+---+---+
| cap | 5 | | cap | 5 |
+-----+-------+ +-----+-------+
pile (stack)let s1 = String::from("salut");
let s2 = s1; // déplacement
// println!("{s1}"); // E0382 : valeur déplacée
let s3 = s2.clone(); // copie profonde
println!("{s2} {s3}"); // les deux sont valides
let a = 5;
let b = a; // i32 est Copy
println!("{a} {b}"); // a reste valide
fn prendre(s: String) { } // prend possession
prendre(s3); // s3 inutilisable ensuitefn longueur(s: &String) -> usize { s.len() } // lit
fn crier(s: &mut String) { s.push('!'); } // modifie
let mut s = String::from("salut");
let n = longueur(&s); // emprunt partagé
crier(&mut s); // emprunt exclusif&T qu’on veut, soit une seule référence exclusive &mut T. Et une référence ne vit jamais plus longtemps que la valeur qu’elle désigne.let mut v = vec![1, 2, 3]; let premier = &v[0]; // emprunt partagé… v.push(4); // E0502 : exclusif println!("{premier}"); // …encore utilisé ici
Pourquoi ? push peut réallouer le vecteur : premier désignerait une mémoire libérée. Correctifs : utiliser premier avant le push, ou copier la valeur (let premier = v[0];).
Un emprunt dure jusqu’à sa dernière utilisation, pas jusqu’à la fin du bloc.
'a)fn plus_long<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() >= y.len() { x } else { y }
}
struct Extrait<'a> {
texte: &'a str, // la structure emprunte
}'a ne change aucune durée : il décrit le lien entre les références (ici : le résultat vit autant que la plus courte des deux).&self → annotation inutile.'static : valable pendant tout le programme (littéraux "…").{
let a = String::from("a");
let b = vec![1, 2, 3];
// … a et b utilisables ici
} // fin de portée : b, puis a, libérés
let v = vec![0; 1000];
drop(v); // libération anticipéeOrdre inverse de la déclaration. impl Drop : agir à la libération (fermer un fichier, relâcher un verrou).
| &str | lire un texte (accepte &String et les littéraux) |
| &[T] | lire une suite (accepte &Vec<T> et les tableaux) |
| &mut T | modifier sur place |
| T | garder la valeur (la stocker) ; petites valeurs Copy |
Renvoyer une valeur possédée (String, Vec<T>) plutôt qu’une référence vers une variable locale.
| Box<T> | valeur sur le tas, un seul propriétaire | type récursif, dyn Trait |
| Rc<T> | plusieurs propriétaires (compteur de références) | partage dans un thread |
| Arc<T> | idem, compteur atomique | partage entre threads |
| Cell<T> RefCell<T> | modifier à travers & (mutabilité intérieure) | contrôle à l’exécution |
| Mutex<T> RwLock<T> | mutabilité intérieure verrouillée | entre threads |
| Weak<T> | référence non possédante vers un Rc / Arc | casser les cycles |
| Cow<'a, T> | emprunté ou possédé selon le besoin | éviter une copie |
Associations classiques : Rc<RefCell<T>> (un thread) · Arc<Mutex<T>> (plusieurs threads).
! RefCell : deux borrow_mut() simultanés → panique. Deux Rc qui se désignent mutuellement ne sont jamais libérés : fuite mémoire (utiliser Weak).
| &v[i] | emprunter au lieu de prendre |
| v[i].clone() | copier l’élément (coût d’une copie) |
| mem::take(&mut v[i]) | prendre, en laissant la valeur par défaut ("", 0, vide) |
| option.take() | prendre le contenu, en laissant None |
| v.into_iter() · v.drain(..) | parcourir en prenant possession · en vidant |
| E0382 | valeur utilisée après déplacement | emprunter &, cloner, réordonner |
| E0499 | deux emprunts &mut simultanés | raccourcir le premier |
| E0502 | &mut pendant un emprunt & | finir de lire avant d’écrire |
| E0505 | déplacement d’une valeur empruntée | attendre la fin de l’emprunt |
| E0507 | sortie d’une valeur depuis une référence | &, .clone(), mem::take |
| E0596 | &mut sur une variable non mut | let mut |
| E0597 | valeur libérée trop tôt | allonger sa portée |
| E0515 | référence vers une variable locale renvoyée | renvoyer la valeur possédée |
| E0106 | durée de vie manquante | annoter 'a ou posséder |
Détail et exemples : rustc --explain code ou doc.rust-lang.org/error_codes.
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn new(x: f64, y: f64) -> Self { // fonction associée
Self { x, y } // raccourci de x: x
}
fn norme(&self) -> f64 { self.x.hypot(self.y) }
fn deplacer(&mut self, dx: f64) { self.x += dx; }
fn eclater(self) -> (f64, f64) { (self.x, self.y) }
}
let mut p = Point::new(3.0, 4.0);
println!("{}", p.norme()); // 5
p.deplacer(1.0);
let q = Point { y: 0.0, ..p.clone() }; // autres champs de p
let Point { x, y } = q; // déstructurer| &self | la méthode lit l’instance |
| &mut self | la méthode la modifie |
| self | la méthode la consomme |
| Self | le type lui-même, dans un impl |
| struct Metres(f64); | structure-tuple : m.0 ; « newtype » |
| struct Vide; | structure unité, sans champ |
| type Km = f64; | alias : autre nom du même type |
use std::f64::consts::PI;
enum Forme {
Cercle { rayon: f64 },
Rectangle(f64, f64),
Point,
}
impl Forme {
fn aire(&self) -> f64 {
match self {
Forme::Cercle { rayon } => PI * rayon * rayon,
Forme::Rectangle(l, h) => l * h,
Forme::Point => 0.0,
}
}
}Une valeur d’énumération est une de ses variantes, chacune avec ses propres données : idéal pour modéliser des états.
Dans le prélude, sans use : enum Option<T> { None, Some(T) } et enum Result<T, E> { Ok(T), Err(E) }. Rust n’a pas de null.
trait Decrire {
fn nom(&self) -> String; // à fournir
fn decrire(&self) -> String { // fournie par défaut
format!("Je suis {}", self.nom())
}
}
impl Decrire for Point {
fn nom(&self) -> String { "un point".to_string() }
}
use std::fmt;
impl fmt::Display for Point { // active {} et to_string
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}Implémenter un trait pour un type : permis si le trait ou le type est défini dans votre crate (règle de l’orphelin).
fn plus_grand<T: PartialOrd>(v: &[T]) -> &T {
let mut max = &v[0];
for x in v {
if x > max { max = x; }
}
max
}
fn afficher(x: impl fmt::Display) { println!("{x}"); }
fn resumer<T>(x: &T) -> String
where
T: Decrire + Clone,
{
x.decrire()
}
struct Pile<T> { elements: Vec<T> }
impl<T> Pile<T> {
fn empiler(&mut self, x: T) { self.elements.push(x); }
}| T: Trait | contrainte : T doit implémenter Trait |
| T: A + B | plusieurs contraintes |
| where … | contraintes nombreuses, écrites à part |
| x: impl Trait | paramètre : raccourci d’un générique |
| -> impl Trait | renvoie « un type qui implémente Trait » (itérateur…) |
| &dyn Trait · Box<dyn Trait> | objet-trait : type choisi à l’exécution |
Générique : une copie du code par type, à la compilation (monomorphisation) — rapide. dyn : un seul code, appel indirect ; permet les collections hétérogènes : Vec<Box<dyn Decrire>>.
| Debug | {:?} | derive |
| Display | {}, .to_string() | à écrire |
| Clone · Copy | .clone() · copie implicite | derive |
| PartialEq · Eq | ==, != | derive |
| PartialOrd · Ord | <, >, .sort(), .max() | derive |
| Hash | clé de HashMap (avec Eq) | derive |
| Default | T::default() | derive |
| From · Into | conversions ; From fournit Into | à écrire |
| FromStr | "…".parse() | à écrire |
| Iterator | boucle for, adaptateurs : fournir next() | à écrire |
| Drop | code exécuté à la libération | à écrire |
| Deref | *x ; méthodes transmises (Box, String) | à écrire |
| Add, Mul, Neg… | surcharge des opérateurs (std::ops) | à écrire |
| Error | type d’erreur (avec Debug + Display) | à écrire |
| Send · Sync | transfert · partage entre threads | automatique |
! f64 n’implémente ni Eq ni Ord (à cause de NaN) : v.sort_by(|a, b| a.total_cmp(b)).
| (rien) | privé : visible dans le module et ses sous-modules |
| pub | public |
| pub(crate) | visible dans toute la crate |
| pub(super) | visible dans le module parent |
! Un pub struct garde ses champs privés : mettre pub devant chaque champ voulu.
| panic! | irrécupérable : bogue, invariant violé ; le thread s’arrête |
| Result<T, E> | récupérable : l’appelant décide (fichier absent, saisie invalide, réseau) |
| Option<T> | absence de valeur, sans erreur (recherche vide, champ facultatif) |
Ni exceptions ni null : les échecs possibles se lisent dans la signature des fonctions.
expect sur une hypothèse démontrée.unwrap sur None / Err, tranche de str coupant un caractère.RUST_BACKTRACE=1.use std::error::Error;
use std::fs;
fn lire_nombre(chemin: &str) -> Result<i64, Box<dyn Error>> {
let texte = fs::read_to_string(chemin)?; // io::Error
let n: i64 = texte.trim().parse()?; // ParseIntError
Ok(n * 2)
}
fn main() -> Result<(), Box<dyn Error>> {
let n = lire_nombre("nombre.txt")?;
println!("{n}");
Ok(())
}x? : si Ok(v), donne v ; si Err(e), renvoie aussitôt Err(e.into()).Option dans une fonction qui renvoie Option.Result ou Option (sinon E0277).main peut renvoyer Result : une erreur y est affichée (format Debug) et le programme se termine avec un code d’échec.| match · if let · let else | traiter chaque cas explicitement |
| ? | propager à l’appelant |
| .unwrap() | ! la valeur, ou panique (prototypes, tests) |
| .expect("raison") | idem, avec l’hypothèse expliquée |
| .unwrap_or(v) | la valeur, sinon v |
| .unwrap_or_else(|| …) | sinon calculée (seulement si besoin) |
| .unwrap_or_default() | sinon 0, "", vide… |
| .is_some() · .is_ok() | tests (et is_none, is_err) |
| .is_some_and(|x| …) | présente et conforme |
| .map(|x| …) | transformer la valeur si présente |
| .and_then(|x| …) | enchaîner une opération faillible |
| .or(b) · .or_else(|| …) | solution de repli |
| .filter(|x| …) | Option : garder si la condition est vraie |
| .map_err(|e| …) | Result : transformer l’erreur |
| .ok_or(e) · .ok_or_else(|| e) | Option → Result |
| .ok() · .err() | Result → Option |
| .as_deref() | &Option<String> → Option<&str> |
| .take() | extraire, en laissant None |
| .copied() · .cloned() | Option<&T> → Option<T> |
Une suite de Result → un seul : .collect::<Result<Vec<_>, _>>() (arrêt à la première erreur).
use std::{fmt, io};
#[derive(Debug)]
enum ErreurConfig {
Absente(String),
Syntaxe(usize),
Io(io::Error),
}
impl fmt::Display for ErreurConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Absente(c) => write!(f, "clé {c} absente"),
Self::Syntaxe(n) => write!(f, "erreur ligne {n}"),
Self::Io(e) => write!(f, "entrée-sortie : {e}"),
}
}
}
impl std::error::Error for ErreurConfig {}
impl From<io::Error> for ErreurConfig { // pour ?
fn from(e: io::Error) -> Self { Self::Io(e) }
}enum), pour que l’appelant distingue les cas.Box<dyn Error> suffit souvent.thiserror (écrit ce code) et anyhow (erreurs d’application, contexte) — à évaluer comme tout code externe.| fs::read_to_string(p)? | tout le fichier → String |
| fs::write(p, contenu)? | créer ou écraser |
| fs::read(p)? | octets bruts → Vec<u8> |
| File::open(p)? · File::create(p)? | ouvrir en lecture · créer (écrase) |
| OpenOptions::new() | ajouter à la fin |
| fs::create_dir_all(p)? | créer les dossiers manquants |
| fs::read_dir(p)? | lister un dossier |
| Path::new(p).exists() | chemins portables : Path, PathBuf |
| std::env::args() | arguments ; le premier : le programme |
| std::env::var("NOM") | variable d’environnement → Result |
| std::process::exit(1) | quitter avec un code de retour |
use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
for ligne in BufReader::new(File::open("a.txt")?).lines() {
let ligne = ligne?; // chaque ligne : Result
println!("{ligne}");
}
let mut sortie = File::create("rapport.txt")?;
writeln!(sortie, "total : {}", 42)?;print!("Votre âge : ");
io::stdout().flush()?; // afficher aussitôt
let mut saisie = String::new();
io::stdin().read_line(&mut saisie)?;
let age: u32 = saisie.trim().parse()?; // trim : retire \nwrite!, writeln! et flush exigent le trait Write : use std::io::Write.
| Vec::new() · vec![1, 2] · vec![0; n] | créer |
| Vec::with_capacity(n) | créer en réservant la place |
| v.push(x) · v.pop() | ajouter à la fin · retirer (→ Option) |
| v[i] · v.get(i) | accès : panique hors limites · → Option<&T> |
| v.first() · v.last() | premier · dernier → Option |
| v.len() · v.is_empty() | taille · vide ? |
| v.insert(i, x) · v.remove(i) | au rang i (décale la suite) |
| v.swap_remove(i) | retirer sans décaler (le dernier prend la place) |
| v.contains(&x) | présence |
| v.sort() · v.sort_unstable() | trier (éléments Ord) |
| v.sort_by_key(|p| p.age) | trier selon une clé |
| v.sort_by(|a, b| b.cmp(a)) | ordre libre (ici décroissant) |
| v.dedup() | retirer les doublons consécutifs |
| v.retain(|x| *x > 0) | ne garder que ce qui vérifie la condition |
| v.extend(autre) | ajouter tous les éléments |
| v.truncate(n) · v.clear() | raccourcir · vider |
| v.reverse() · v.swap(i, j) | inverser · échanger, sur place |
| v.binary_search(&x) | vecteur trié → Ok(rang) ou Err(rang d’insertion) |
| v.chunks(3) · v.windows(2) | paquets de 3 · fenêtres glissantes |
| v.split_at(i) | deux tranches |
| v.join(", ") | Vec de textes → un texte |
| String::new() · String::from("a") | créer ; ou "a".to_string() |
| s.push('c') · s.push_str("abc") | ajouter |
| s1 + &s2 · format!("{s1}{s2}") | concaténer (+ consomme s1) |
| s.len() | ! longueur en octets : "é".len() → 2 |
| s.chars().count() | nombre de caractères |
| s.chars() · s.bytes() | parcourir caractères · octets |
| s.trim() | sans espaces ni \n aux extrémités |
| s.split(',') · s.lines() | découper (itérateurs) |
| s.split_whitespace() | mots séparés par des blancs |
| s.split_once('=') | → Option<(&str, &str)> |
| s.contains("ab") | aussi starts_with, ends_with |
| s.find("ab") | position en octets → Option<usize> |
| s.replace("a", "b") | remplacer → nouvelle String |
| s.to_uppercase() | casse Unicode (to_lowercase) |
| s.repeat(3) | répéter |
| s.parse::<f64>() | convertir → Result |
| &s[0..3] | ! par octets : panique si un caractère est coupé |
! s[0] est refusé : en UTF-8, un caractère occupe de 1 à 4 octets. Premiers caractères : s.chars().take(n).collect::<String>().
| VecDeque<T> | file à deux bouts : push_front, pop_front |
| HashMap<K, V> | dictionnaire, ordre quelconque |
| BTreeMap<K, V> | dictionnaire trié par clé |
| HashSet<T> · BTreeSet<T> | ensemble sans doublon : union, intersection |
| BinaryHeap<T> | file de priorité, plus grand en tête ; Reverse(x) : plus petit |
Clés Hash… : Eq + Hash ; clés BTree… : Ord.
use std::collections::HashMap;
let mut notes: HashMap<String, u32> = HashMap::new();
notes.insert("Anne".to_string(), 15);
if let Some(n) = notes.get("Anne") { println!("{n}"); }
let mut compte = HashMap::new(); // compter les mots
for mot in texte.split_whitespace() {
*compte.entry(mot).or_insert(0) += 1;
}
for (cle, val) in ¬es { println!("{cle} : {val}"); }| m.insert(k, v) | ajoute ou remplace → ancienne valeur (Option) |
| m.get(&k) · m.get_mut(&k) | → Option |
| m.contains_key(&k) · m.remove(&k) | présence · retrait |
| m.entry(k).or_insert(v) | accès, en créant si absent |
| m.entry(k).or_default() | idem, valeur par défaut |
| m.keys() · m.values() | itérateurs |
! Ordre de parcours imprévisible, variable d’une exécution à l’autre : BTreeMap, ou trier, si l’ordre compte.
struct Compte { n: u32 }
impl Iterator for Compte {
type Item = u32; // type produit
fn next(&mut self) -> Option<u32> {
self.n += 1;
if self.n <= 3 { Some(self.n) } else { None }
}
}
// Compte { n: 0 }.collect::<Vec<_>>() → [1, 2, 3]Seul next est à écrire : map, filter, sum… sont fournis par le trait.
let v = vec![3, 1, 4, 1, 5, 9, 2, 6];
let doubles: Vec<i32> = v.iter()
.filter(|x| *x % 2 == 0) // 4, 2, 6
.map(|x| x * 2)
.collect(); // [8, 4, 12]
let total: i32 = v.iter().sum(); // 31| Créer | ||||||||
| v.iter() · v.iter_mut() | éléments &T · &mut T | |||||||
| v.into_iter() | consomme la collection → T | |||||||
| Adapter (paresseux) | ||||||||
| map(f) · filter(p) | transformer · garder si | |||||||
| filter_map(f) | les deux ; f renvoie une Option | |||||||
| enumerate() · zip(b) | → (rang, x) · paires (a, b) | |||||||
| chain(b) · rev() | à la suite · à l’envers | |||||||
| skip(n) · take(n) | sauter · prendre n éléments | |||||||
| skip_while(p) · take_while(p) | tant que la condition est vraie | |||||||
| step_by(n) | un élément sur n | |||||||
| flat_map(f) · flatten() | aplatir | |||||||
| copied() · cloned() | &T → T | |||||||
| Consommer | ||||||||
| collect() | → Vec, String, HashMap… (type à préciser) | |||||||
| sum() · product() · count() | somme · produit · nombre | |||||||
| min() · max() | → Option ; aussi min_by_key(f), max_by_key(f) | |||||||
| fold(init, f) · reduce(f) | accumuler | |||||||
| any(p) · all(p) | au moins un · tous | |||||||
| find(p) · position(p) | premier trouvé · son rang | |||||||
| partition(p) | → deux collections | |||||||
| for_each(f) | action sur chaque élément | |||||||
! Paresseux : sans consommateur (collect, sum, for…), rien n’est calculé.
mon_projet/ +-- Cargo.toml manifeste : nom, dépendances +-- Cargo.lock versions exactes retenues (généré) +-- src/ | +-- main.rs exécutable : fn main | +-- lib.rs bibliothèque (facultatif) | +-- config.rs module config | +-- reseau.rs module reseau… | +-- reseau/ | +-- client.rs sous-module reseau::client +-- tests/ tests d'intégration +-- examples/ cargo run --example nom +-- benches/ mesures de performance +-- target/ produits de compilation (hors Git)
[package] name = "mon_projet" version = "0.1.0" edition = "2024" [dependencies] regex = "1" # >= 1.0.0 et < 2.0.0 serde = { version = "1", features = ["derive"] } outil = { path = "../outil" } # dépendance locale [dev-dependencies] # pour les tests seulement proptest = "1" [profile.release] lto = true # optimisation plus poussée
Versions : "1.2" accepte toute version compatible (≥ 1.2.0 et < 2.0.0) ; "=1.2.3" : cette version exacte.
| cargo new nom · cargo init | créer un projet · dans le dossier courant |
| cargo add regex · cargo remove regex | ajouter · retirer une dépendance |
| cargo add serde -F derive | avec une fonctionnalité (--features) |
| cargo build · --release | compiler : debug · optimisé |
| cargo run -- arg1 arg2 | lancer avec des arguments |
| cargo test motif | tests dont le nom contient le motif |
| cargo test -- --nocapture | montrer les println! des tests |
| cargo doc --open | documentation du projet et des dépendances |
| cargo update | actualiser Cargo.lock (dans les limites de Cargo.toml) |
| cargo tree | arbre des dépendances |
| cargo install outil | installer un programme depuis crates.io |
| cargo publish | ! publie sur crates.io ; une version publiée ne se remplace pas |
! Une dépendance est du code tiers, exécuté dès la compilation (build.rs, macros) : vérifier auteur, licence, activité, avis de sécurité (rustsec.org ; outil tiers cargo audit).
| #[derive(Debug, Clone)] | implémentations automatiques |
| #[allow(dead_code)] | taire un avertissement |
| #[cfg(test)] | compilation conditionnelle (aussi target_os = "windows"…) |
| #[must_use] | avertir si le résultat est ignoré |
| #![…] | en tête de fichier : vaut pour tout le module |
// src/main.rs mod geometrie; // charge src/geometrie.rs use geometrie::{distance, Point}; use std::io::{self, Write}; // io et io::Write // src/geometrie.rs pub struct Point { pub x: f64, pub y: f64 } pub fn distance(a: &Point, b: &Point) -> f64 { (a.x - b.x).hypot(a.y - b.y) }
| mod m; | déclarer un module : fichier m.rs (ou m/mod.rs) |
| mod m { … } | module écrit sur place |
| use a::b::C; | raccourcir un chemin |
| use a::b::C as D; | importer sous un autre nom |
| use a::b::*; | tout importer (à éviter, sauf tests) |
| crate:: · super:: · self:: | racine de la crate · module parent · module courant |
| pub use a::C; | réexporter : façade publique |
Crate : unité de compilation (un exécutable ou une bibliothèque). Paquet (package) : un Cargo.toml et ses crates.
pub fn diviser(a: i32, b: i32) -> i32 {
if b == 0 { panic!("division par zéro"); }
a / b
}
#[cfg(test)] // compilé pour cargo test
mod tests {
use super::*;
#[test]
fn quotient() {
assert_eq!(diviser(7, 2), 3);
assert!(diviser(-6, 3) < 0, "négatif attendu");
}
#[test]
#[should_panic(expected = "division par zéro")]
fn par_zero() { diviser(1, 0); }
#[test]
fn lecture() -> Result<(), std::num::ParseIntError> {
assert_eq!("42".parse::<i32>()?, 42);
Ok(())
}
}| assert_eq!(a, b) · assert_ne! | égalité · différence ; message facultatif en plus |
| #[should_panic] | le test doit paniquer |
| #[ignore] | ignoré, sauf cargo test -- --ignored |
| tests/*.rs | tests d’intégration : API publique seulement |
/// Moyenne d'une tranche ; `None` si elle est vide. /// /// ``` /// assert_eq!(mon_projet::moyenne(&[2.0, 4.0]), Some(3.0)); /// ``` pub fn moyenne(v: &[f64]) -> Option<f64> { if v.is_empty() { return None; } Some(v.iter().sum::<f64>() / v.len() as f64) }
/// documente l’élément suivant (Markdown) ; //! le module entier. Les blocs de code de la documentation sont exécutés par cargo test (bibliothèques).
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
let h = thread::spawn(|| 6 * 7);
let r = h.join().unwrap(); // 42
let compteur = Arc::new(Mutex::new(0));
let mut poignees = vec![];
for _ in 0..4 {
let c = Arc::clone(&compteur);
poignees.push(thread::spawn(move || {
*c.lock().unwrap() += 1;
}));
}
for p in poignees { p.join().unwrap(); }
// *compteur.lock().unwrap() vaut 4
let (tx, rx) = mpsc::channel(); // canal
thread::spawn(move || tx.send("message").unwrap());
println!("{}", rx.recv().unwrap());
let v = vec![1, 2, 3];
thread::scope(|s| { // threads bornés :
s.spawn(|| println!("{v:?}")); // emprunt permis
});Send : transférable à un autre thread · Sync : partageable par référence. Rc ou RefCell entre threads : refusé (E0277).lock() renvoie Err si un thread a paniqué en tenant le verrou (verrou « empoisonné »).async fn renvoie un futur : rien ne s’exécute avant .await.tokio.#[tokio::main]
async fn main() {
let (a, b) = tokio::join!(tache_a(), tache_b());
}macro_rules! carre {
($x:expr) => { $x * $x };
}
let n = carre!(3 + 1); // 16 : $x reste groupéUn bloc unsafe { } autorise cinq opérations invérifiables : déréférencer un pointeur brut, appeler une fonction unsafe (dont le code C), lire ou modifier un static mut, implémenter un trait unsafe, lire un champ d’union. Les autres règles restent actives. Un programme d’application n’en a presque jamais besoin.
| mem::swap(&mut a, &mut b) | échanger deux valeurs |
| mem::take(&mut v) | extraire, en laissant la valeur par défaut |
| mem::replace(&mut v, n) | remplacer, en récupérant l’ancienne |
| a.cmp(&b) | → Ordering::Less, Equal ou Greater |
| Instant::now() · t.elapsed() | mesurer une durée (std::time) |
| Command::new("git") | lancer un programme (std::process) |
| c.is_alphabetic() · c.is_ascii_digit() | nature d’un caractère |
| c.to_digit(10) · char::from_digit(7, 10) | chiffre ↔ caractère |
new() et impl Default ; impl From<X> for Y pour convertir (utilisé par ?).&str et &[T] ; valeurs renvoyées possédées.struct Euros(f64); : ne plus confondre deux unités.enum plutôt que des booléens combinés.Option pour l’absence, jamais de valeur sentinelle (-1, "")..clone() pour faire taire le compilateur | revoir qui possède quoi ; emprunter |
.unwrap() partout | ?, expect, traitement explicite |
s.len() pris pour des caractères | s.chars().count() |
&s[..n] sur un texte accentué | panique : s.chars().take(n) |
dépassement d’entier silencieux en --release | checked_add… ; tester en debug |
v.sort() sur des f64 | refusé : trier avec total_cmp (page 4) |
| itérateur sans consommateur | rien n’est calculé |
| modifier une collection en la parcourant | refusé : retain, ou collecter d’abord |
| performances mesurées en debug | toujours --release |
cargo fmt et cargo clippy avant chaque commit.cargo clippy --fix : appliquer les corrections proposées.| Python | Rust |
|---|---|
None | Option<T> : None, Some(x) |
try / except | Result<T, E>, ?, match |
list · dict · set | Vec · HashMap · HashSet |
for i in range(n) | for i in 0..n |
len(s) | s.chars().count() (s.len() : octets) |
f"{x}" | format!("{x}") |
class · héritage | struct + impl · traits et composition |
lambda x: x + 1 | |x| x + 1 |
pip · pytest | cargo add · cargo test |