Rust : Les bases pour développeurs expérimentés en 2026
Apprenez Rust rapidement en partant de vos acquis. Ownership, borrowing, lifetimes et patterns essentiels expliqués pour développeurs venant de C++, Java ou Python.

Rust gagne en popularité chaque année, et pour cause : sécurité mémoire garantie à la compilation, performances équivalentes au C++, et un écosystème moderne. Pour un développeur expérimenté venant de C++, Java ou Python, l'apprentissage de Rust peut sembler déroutant au début, mais les concepts fondamentaux deviennent rapidement intuitifs une fois compris.
Rust est le langage le plus apprécié sur Stack Overflow depuis 8 ans consécutifs. Adopté par Microsoft, Google, Amazon et Meta pour des composants critiques, il offre la sécurité mémoire sans garbage collector.
Configuration de l'environnement Rust
Avant de coder, il faut installer Rust via rustup, l'outil officiel de gestion des versions.
# install.sh
# Installation de Rust via rustup (macOS, Linux)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Vérification de l'installation
rustc --version
cargo --versionCargo est le gestionnaire de paquets et outil de build de Rust. Il combine les fonctionnalités de npm, Maven et Make en un seul outil cohérent.
# project-setup.sh
# Création d'un nouveau projet
cargo new mon_projet
cd mon_projet
# Structure générée :
# mon_projet/
# ├── Cargo.toml # Manifeste (équivalent package.json)
# └── src/
# └── main.rs # Point d'entrée
# Commandes essentielles
cargo build # Compile le projet
cargo run # Compile et exécute
cargo test # Lance les tests
cargo check # Vérifie sans compiler (plus rapide)Variables et immutabilité par défaut
Rust inverse la convention habituelle : les variables sont immutables par défaut. Cette approche force à réfléchir explicitement à la mutabilité et prévient de nombreux bugs.
fn main() {
// Variable immutable par défaut
let x = 5;
// x = 6; // Erreur de compilation !
// Variable mutable explicite
let mut y = 5;
y = 6; // OK
// Shadowing : redéclaration dans le même scope
let x = x + 1; // Crée une nouvelle variable x
let x = x * 2; // x vaut maintenant 12
// Le shadowing permet aussi de changer le type
let spaces = " "; // &str
let spaces = spaces.len(); // usize
}Le shadowing crée une nouvelle variable, contrairement à mut qui modifie la valeur existante. Le shadowing permet de transformer une valeur tout en gardant un nom clair.
Types de données fondamentaux
Rust est statiquement typé avec une excellente inférence de types. Voici les types primitifs essentiels à connaître.
fn main() {
// Entiers signés : i8, i16, i32, i64, i128, isize
let age: i32 = 30;
// Entiers non signés : u8, u16, u32, u64, u128, usize
let count: u64 = 1_000_000; // Underscores pour lisibilité
// Flottants : f32, f64 (défaut)
let pi: f64 = 3.14159;
// Booléen
let active: bool = true;
// Caractère Unicode (4 bytes)
let emoji: char = '🦀';
// Tuple : collection fixe de types différents
let person: (String, i32) = (String::from("Alice"), 28);
let (name, age) = person; // Destructuring
// Array : taille fixe, même type
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
let first = numbers[0];
// Slice : vue sur une portion de données
let slice: &[i32] = &numbers[1..4]; // [2, 3, 4]
}Ownership : le concept révolutionnaire
L'ownership est LA innovation de Rust. Ce système garantit la sécurité mémoire sans garbage collector, au prix d'une courbe d'apprentissage initiale.
Les trois règles de l'ownership
fn main() {
// Règle 1 : Chaque valeur a un unique propriétaire
let s1 = String::from("hello");
// Règle 2 : Quand le propriétaire sort du scope, la valeur est libérée
{
let s2 = String::from("world");
// s2 est valide ici
}
// s2 est libéré (drop), plus accessible
// Règle 3 : Une seule ownership à la fois (move)
let s3 = s1; // s1 est MOVE vers s3
// println!("{}", s1); // Erreur : s1 n'est plus valide !
println!("{}", s3); // OK : s3 est le propriétaire
}Move vs Clone
fn main() {
// Types simples (stack) : Copy automatique
let x = 5;
let y = x; // Copy, pas move
println!("x = {}, y = {}", x, y); // Les deux sont valides
// Types complexes (heap) : Move par défaut
let s1 = String::from("hello");
let s2 = s1; // Move
// s1 n'est plus utilisable
// Clone explicite pour dupliquer
let s3 = String::from("world");
let s4 = s3.clone(); // Deep copy
println!("s3 = {}, s4 = {}", s3, s4); // Les deux valides
}Passer une valeur à une fonction transfère l'ownership. La fonction devient propriétaire et la valeur n'est plus accessible après l'appel, sauf si elle est retournée.
fn main() {
let s = String::from("hello");
takes_ownership(s);
// println!("{}", s); // Erreur : s a été moved
let x = 5;
makes_copy(x);
println!("{}", x); // OK : i32 implémente Copy
// Pour récupérer l'ownership, retourner la valeur
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2);
println!("{}", s3); // OK
}
fn takes_ownership(s: String) {
println!("{}", s);
} // s est dropped ici
fn makes_copy(x: i32) {
println!("{}", x);
}
fn takes_and_gives_back(s: String) -> String {
s // Retourne l'ownership
}Borrowing : références sans transfert
Le borrowing permet d'utiliser une valeur sans en prendre l'ownership. C'est le mécanisme le plus utilisé en Rust.
fn main() {
let s1 = String::from("hello");
// Référence immutable : lecture seule
let len = calculate_length(&s1);
println!("Longueur de '{}' : {}", s1, len); // s1 toujours valide
// Référence mutable : modification possible
let mut s2 = String::from("hello");
change(&mut s2);
println!("{}", s2); // "hello, world"
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s sort du scope mais ne drop pas car c'est une référence
fn change(s: &mut String) {
s.push_str(", world");
}Règles du borrowing
fn main() {
let mut s = String::from("hello");
// Règle 1 : Plusieurs références immutables simultanées OK
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// Règle 2 : UNE SEULE référence mutable à la fois
let r3 = &mut s;
// let r4 = &mut s; // Erreur : déjà emprunté mutably
println!("{}", r3);
// Règle 3 : Pas de ref mutable si ref immutable existe
let r5 = &s;
// let r6 = &mut s; // Erreur : r5 est encore actif
println!("{}", r5);
// Une fois r5 utilisé pour la dernière fois, on peut emprunter mut
let r7 = &mut s; // OK : r5 n'est plus utilisé après
r7.push_str("!");
}Prêt à réussir tes entretiens Rust ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Lifetimes : garantir la validité des références
Les lifetimes s'assurent que les références restent valides. Le compilateur les infère souvent automatiquement, mais parfois une annotation explicite est nécessaire.
// Erreur classique : référence vers donnée libérée
// fn dangling() -> &String {
// let s = String::from("hello");
// &s // Erreur : s sera dropped, référence invalide !
// }
// Solution : retourner la valeur owned
fn no_dangle() -> String {
let s = String::from("hello");
s // Ownership transféré, pas de problème
}Annotations de lifetimes
// Le compilateur ne peut pas deviner quelle référence sera retournée
// fn longest(x: &str, y: &str) -> &str { ... } // Erreur !
// Annotation explicite : le retour vit aussi longtemps que x ET y
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
println!("Longest: {}", result); // OK ici
}
// println!("{}", result); // Erreur : string2 dropped
}Rust applique des règles d'élision pour éviter d'annoter les cas simples. Pour les débutants, il suffit de suivre les messages du compilateur qui sont très explicites.
Structs et implémentations
Les structs sont les blocs de construction pour les types personnalisés en Rust.
// Définition d'une struct
#[derive(Debug)] // Permet d'afficher avec {:?}
struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
// Bloc d'implémentation pour les méthodes
impl User {
// Constructeur (convention : fn new ou fn nom_explicite)
fn new(username: String, email: String) -> Self {
Self {
username,
email,
active: true,
sign_in_count: 1,
}
}
// Méthode : prend &self (référence à l'instance)
fn is_active(&self) -> bool {
self.active
}
// Méthode avec mutation : prend &mut self
fn deactivate(&mut self) {
self.active = false;
}
// Méthode consommant self (rare)
fn into_username(self) -> String {
self.username
}
}
fn main() {
let mut user = User::new(
String::from("alice"),
String::from("alice@example.com"),
);
println!("Active: {}", user.is_active());
user.deactivate();
println!("Active: {}", user.is_active());
// Debug print
println!("{:?}", user);
}Enums et Pattern Matching
Les enums Rust sont bien plus puissants que dans la plupart des langages : chaque variant peut contenir des données.
// Enum simple
enum Direction {
North,
South,
East,
West,
}
// Enum avec données (algebraic data type)
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
impl Message {
fn process(&self) {
match self {
Message::Quit => println!("Quitting"),
Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
Message::Write(text) => println!("Writing: {}", text),
Message::ChangeColor(r, g, b) => {
println!("Color: rgb({}, {}, {})", r, g, b)
}
}
}
}
fn main() {
let msg = Message::Move { x: 10, y: 20 };
msg.process();
let msg2 = Message::Write(String::from("Hello Rust!"));
msg2.process();
}Option et Result : gestion des erreurs
use std::fs::File;
use std::io::{self, Read};
fn main() {
// Option<T> : valeur présente ou absente (remplace null)
let numbers = vec![1, 2, 3];
let first: Option<&i32> = numbers.first();
match first {
Some(n) => println!("Premier : {}", n),
None => println!("Liste vide"),
}
// Méthodes utilitaires
let value = first.unwrap_or(&0);
let doubled = first.map(|n| n * 2);
// Result<T, E> : succès ou erreur
let result = read_file("config.txt");
match result {
Ok(content) => println!("Contenu : {}", content),
Err(e) => println!("Erreur : {}", e),
}
}
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?; // ? propage l'erreur
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}L'opérateur ? est du sucre syntaxique pour propager les erreurs. Il retourne automatiquement l'erreur si Result est Err, sinon il unwrap la valeur Ok.
Traits : polymorphisme à la Rust
Les traits définissent un comportement partagé, similaires aux interfaces Java ou aux protocols Swift.
// Définition d'un trait
trait Summary {
fn summarize(&self) -> String;
// Méthode avec implémentation par défaut
fn preview(&self) -> String {
format!("{}...", &self.summarize()[..50.min(self.summarize().len())])
}
}
struct Article {
title: String,
author: String,
content: String,
}
struct Tweet {
username: String,
content: String,
}
// Implémentation pour Article
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
// Implémentation pour Tweet
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
}
// Fonction acceptant tout type implémentant Summary
fn notify(item: &impl Summary) {
println!("Breaking news: {}", item.summarize());
}
// Syntaxe équivalente avec trait bound
fn notify_generic<T: Summary>(item: &T) {
println!("Breaking news: {}", item.summarize());
}
fn main() {
let article = Article {
title: String::from("Rust 2026"),
author: String::from("Community"),
content: String::from("..."),
};
let tweet = Tweet {
username: String::from("rustlang"),
content: String::from("Rust is awesome!"),
};
notify(&article);
notify(&tweet);
}Collections essentielles
Rust fournit des collections puissantes dans la bibliothèque standard.
use std::collections::HashMap;
fn main() {
// Vec<T> : tableau dynamique
let mut numbers: Vec<i32> = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);
// Macro vec! pour initialisation
let nums = vec![1, 2, 3, 4, 5];
// Itération
for n in &nums {
println!("{}", n);
}
// Méthodes fonctionnelles
let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();
let sum: i32 = nums.iter().sum();
let evens: Vec<&i32> = nums.iter().filter(|x| *x % 2 == 0).collect();
// String : chaîne UTF-8 growable
let mut s = String::from("Hello");
s.push_str(", World!");
s.push('!');
// Concaténation
let s1 = String::from("Hello, ");
let s2 = String::from("World!");
let s3 = s1 + &s2; // s1 moved, s2 borrowed
// ou avec format!
let s4 = format!("{}{}", "Hello, ", "World!");
// HashMap<K, V>
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 50);
// Accès avec get (retourne Option)
if let Some(score) = scores.get("Blue") {
println!("Blue: {}", score);
}
// Entry API pour insertion conditionnelle
scores.entry(String::from("Yellow")).or_insert(25);
}Gestion des erreurs idiomatique
Une bonne gestion des erreurs est essentielle en Rust. Voici les patterns recommandés.
use std::fs::File;
use std::io::{self, Read};
use std::num::ParseIntError;
// Définir un type d'erreur custom
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(ParseIntError),
Custom(String),
}
// Implémenter From pour conversion automatique
impl From<io::Error> for AppError {
fn from(err: io::Error) -> Self {
AppError::Io(err)
}
}
impl From<ParseIntError> for AppError {
fn from(err: ParseIntError) -> Self {
AppError::Parse(err)
}
}
// Fonction retournant Result avec erreur custom
fn read_number_from_file(path: &str) -> Result<i32, AppError> {
let mut file = File::open(path)?; // io::Error -> AppError
let mut content = String::new();
file.read_to_string(&mut content)?;
let number: i32 = content.trim().parse()?; // ParseIntError -> AppError
Ok(number)
}
fn main() {
match read_number_from_file("number.txt") {
Ok(n) => println!("Number: {}", n),
Err(AppError::Io(e)) => println!("IO error: {}", e),
Err(AppError::Parse(e)) => println!("Parse error: {}", e),
Err(AppError::Custom(msg)) => println!("Error: {}", msg),
}
}Pour des projets réels, les crates thiserror (pour les libraries) et anyhow (pour les applications) simplifient grandement la gestion des erreurs.
Tests en Rust
Rust intègre un framework de test directement dans le langage.
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
// Module de tests (compilé uniquement pour `cargo test`)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_divide_success() {
assert_eq!(divide(10, 2), Ok(5));
}
#[test]
fn test_divide_by_zero() {
assert!(divide(10, 0).is_err());
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_panic() {
let v = vec![1, 2, 3];
let _ = v[99]; // Panic !
}
}Conclusion
Rust offre un paradigme unique qui combine sécurité mémoire et performances. Les concepts d'ownership et de borrowing semblent contraignants au début, mais deviennent naturels avec la pratique. Le compilateur Rust est un allié précieux : ses messages d'erreur sont parmi les meilleurs de l'industrie.
Checklist pour bien démarrer
- ✅ Installer Rust via rustup et maîtriser Cargo
- ✅ Comprendre la différence entre immutabilité et mutabilité explicite
- ✅ Maîtriser les trois règles de l'ownership
- ✅ Pratiquer le borrowing avec références
&et&mut - ✅ Utiliser
OptionetResultau lieu de null et exceptions - ✅ Écrire des tests avec
#[test]
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
La communauté Rust est accueillante et les ressources abondantes. Le livre officiel "The Rust Programming Language" est disponible gratuitement en ligne. Avec ces bases solides, vous êtes prêt à explorer des sujets avancés comme async/await, les macros et WebAssembly.
Tags
Partager
Articles similaires

Ownership et Borrowing en Rust : Guide complet
Maîtrisez le système d'ownership et borrowing de Rust. Comprendre les règles de propriété, références, lifetimes et les patterns avancés de gestion mémoire.

Questions d'entretien Rust : Guide complet 2026
Les 25 questions d'entretien Rust les plus fréquentes. Ownership, borrowing, lifetimes, traits, async et concurrence avec réponses détaillées et exemples de code.

Ownership et Borrowing en Rust : le guide complet pour les entretiens techniques
Comprendre en profondeur l'ownership, le borrowing, les lifetimes et le borrow checker en Rust. Un guide essentiel pour se preparer aux entretiens techniques systemes.