Skip to main content
📝 Rust application development

Maîtriser le système de propriété de Rust : le guide complet de la sécurité de la mémoire sans récupération de place

Master Rust ownership, borrowing, and lifetimes. The complete guide to memory safety without garbage collection — with practical code examples.

24 min

Temps de lecture

4,795

Mots

Oct 30, 2025

Publié

Engr Mejba Ahmed

Écrit par

Engr Mejba Ahmed

Partager l'article

Maîtriser le système de propriété de Rust : le guide complet de la sécurité de la mémoire sans récupération de place

Maîtriser le système de propriété de Rust : le guide complet de la sécurité de la mémoire sans récupération de place


1. Pourquoi la propriété est importante : le problème des 2 000 milliards de dollars

Les bugs de sécurité de la mémoire coûtent à l’industrie du logiciel environ 2 000 milliards de dollars par an. Microsoft rapporte que 70 % de ses failles de sécurité sont des problèmes de sécurité de la mémoire. L'équipe Chrome de Google a trouvé des chiffres similaires. Ces bogues incluent :

  • Utilisation après libération : accès à la mémoire qui a été désallouée
  • Double-free : Libérer la mémoire deux fois, provoquant une corruption
  • Buffer overflows : écriture au-delà de la mémoire allouée
  • Courses de données : accès simultané provoquant un comportement imprévisible
  • Fuites de mémoire : Oubli de libérer la mémoire allouée

Le compromis traditionnel

Les langages de programmation ont historiquement choisi l’une des deux approches suivantes :

Approche 1 : Gestion manuelle de la mémoire (C/C++)

// Code C - sujet aux erreurs
char* créer_message() {
    char* msg = malloc(100);
    strcpy(msg, "Bonjour");
    renvoyer un message ;  // L'appelant doit se rappeler de libérer !
}

processus vide() {
    char* m = créer_message();
    printf("%s", m);
    // J'ai oublié de libérer (m) - fuite de mémoire !
}

Problèmes : Nécessite une discipline parfaite, des erreurs faciles à commettre, des failles de sécurité.

Approche 2 : Garbage Collection (Java/Go/JavaScript)

// Code Java - sûr mais avec une surcharge d'exécution
Chaîne createMessage() {
    renvoyer "Bonjour" ;  // GC finira par nettoyer
}
// Sûr, mais les pauses GC affectent les performances

Problèmes : pauses imprévisibles, surcharge de mémoire, moins de contrôle sur les performances.

La solution révolutionnaire de Rust

Rust propose une troisième voie : la sécurité de la mémoire sans garbage collection grâce à la vérification de propriété au moment de la compilation. Vous obtenez :

  • ✅ Sécurité de la mémoire garantie au moment de la compilation
  • ✅ Pas de surcharge d'exécution (abstractions sans coût)
  • ✅ Aucune pause du ramasse-miettes
  • ✅ Concurrence intrépide (courses aux données impossibles)
  • ✅ Performances prévisibles

Le piège ? Vous devez apprendre le système de propriété. Ce guide le rendra très clair.


2. Les trois règles d'or de la propriété

Chaque programme Rust suit ces trois règles, appliquées au moment de la compilation :

Règle 1 : Chaque valeur a un seul propriétaire

fn main() {
    let s = String::from("bonjour");  // s possède la chaîne
    // Une seule variable à la fois peut posséder ces données
} // s sort de la portée, la mémoire est libérée automatiquement

Règle 2 : lorsque le propriétaire sort du champ d'application, la valeur est supprimée

fn main() {
    {
        let s = String::from("bonjour");  // s est valide à partir d'ici
        // fait des trucs avec s
    } // s sort de la portée et est supprimé, mémoire libérée

    // println!("{}", s);  // ERREUR : s n'existe plus
}

Règle 3 : vous pouvez avoir soit une référence mutable, soit plusieurs références immuables

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

    // Plusieurs références immuables - OK
    soit r1 = &s;
    soit r2 = &s;
    println!("{} et {}", r1, r2);

    // Une référence mutable - OK (après r1, r2 ne sont plus utilisés)
    soit r3 = &mut s;
    r3.push_str("monde");
    println!("{}", r3);
}

Pourquoi ces règles ?

  • Règles 1 et 2 : évite les fuites de mémoire et les erreurs de double-libération
  • Règle 3 : empêche les courses de données au moment de la compilation

3. Sémantique du déplacement : comprendre le transfert de propriété

Le problème : la copie naïve coûte cher

fn main() {
    let s1 = String::from("bonjour");
    soit s2 = s1 ;  // Que se passe-t-il ici ?

    // println!("{}", s1);  // ERREUR : valeur déplacée vers s2
}

Ce qui se passe réellement :

Pile : Tas :
s1 -> [ptr, len, cap] -> données "bonjour"
      |
      | (bouger)
      v
s2 -> [ptr, len, cap] -> (mêmes données de tas)

Rust déplace la propriété au lieu de copier les données du tas. Après « let s2 = s1 », seul « s2 » est valide. Cela évite :

  • Copies complètes coûteuses par défaut
  • Erreurs doublement gratuites (seul s2 libérera la mémoire)

Quand Rust copie-t-il au lieu de déplacer ?

Les types qui implémentent le trait « Copier » sont copiés au lieu d'être déplacés :

fn main() {
    // Les entiers, les flottants, les booléens, les caractères implémentent Copier
    soit x = 5 ;
    soit y = x ;  // x est copié dans y
    println!("x = {}, y = {}", x, y);  // Les deux valides !

    // Les tuples de types Copy sont également Copy
    soit point = (3, 4);
    soit point2 = point ;  // copié
    println!("{:?} et {:?}", point, point2);  // Les deux valides !
}

Règle générale : si un type stocke des données sur le tas ou possède des ressources, il n'implémentera pas « Copie ».

Clonage explicite lorsque vous en avez besoin

fn main() {
    let s1 = String::from("bonjour");
    soit s2 = s1.clone();  // Copie complète explicite

    println!("s1 = {}, s2 = {}", s1, s2);  // Les deux valides
}

Utilisez le clonage lorsque :

  • Vous avez besoin de copies indépendantes
  • Le coût de performance est acceptable
  • Rend l'intention claire

Évitez de cloner lorsque :

  • Vous pouvez restructurer pour recourir à l'emprunt à la place
  • Les performances sont essentielles
  • Travailler en boucles chaudes

4. Emprunter : des références qui ne sont pas propriétaires

L'emprunt vous permet de référencer des données sans en devenir propriétaire.

Références immuables (&T)

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

    let len ​​= calculate_length(&s);  // Emprunter

    println!("La longueur de '{}' est {}", s, len);  // est toujours valable !
}

fn calculate_length(s: &String) -> usize {
    s.len()
} // s sort du champ d'application, mais ne possède pas les données, donc rien ne se passe

Points clés :

  • &s crée une référence à s
  • Les références sont immuables par défaut
  • Plusieurs références immuables autorisées
  • Le propriétaire d'origine peut toujours lire les données

Références mutables (&mut T)

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

    changer(&mut s);  // L'emprunt est modifiable

    println!("{}", s);  // Affiche "bonjour le monde"
}

fn changement(s : &mut String) {
    s.push_str(", monde");
}

Restriction critique : une seule une référence mutable à la fois !

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

    soit r1 = &mut s;
    soit r2 = &mut s;  // ERREUR : impossible d'emprunter comme mutable plus d'une fois

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

Pourquoi ? Empêche les courses de données au moment de la compilation :

// C'est impossible en Rust (compilerait en C++)
laissez mut data = vec![1, 2, 3];
laissez ref1 = &mut data;
laissez ref2 = &mut data;
ref1.push(4);        // Pourrait réaffecter
ref2.push(5);        // Pourrait provoquer une utilisation après libération !

Le secret du vérificateur d'emprunt : durées de vie non lexicales (NLL)

Modern Rust (édition 2018+) est plus intelligent quant à la fin des références :

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

    soit r1 = &s;
    soit r2 = &s;
    println!("{} et {}", r1, r2);
    // r1 et r2 ne sont plus utilisés après ce point

    soit r3 = &mut s;  // D'ACCORD! r1 et r2 sont "morts"
    r3.push_str("monde");
    println!("{}", r3);
}

Avant NLL (édition 2015), cela ne serait pas compilé. Désormais, le compilateur suit où les références sont réellement utilisées, et pas seulement leur portée lexicale.

Modèle d'emprunt courant : lectures multiples, écriture unique

fn main() {
    laissez mut data = vec![1, 2, 3, 4, 5];

    // Phase de lecture : plusieurs emprunts immuables
    soit d'abord = &data[0];
    let last = &data[data.len() - 1];
    println!("premier : {}, dernier : {}", premier, dernier);

    // Phase d'écriture : emprunt mutable exclusif
    data.push(6);
    println!("Mise à jour : {:?}", données);
}

5. Durées de vie : enseigner au compilateur les références

Le problème à vie résolu

fn le plus long(x : &str, y : &str) -> &str {
    si x.len() > y.len() {
        x // Quelle durée de vie doit avoir le retour ?
    } autre {
        y // la durée de vie de x ou la durée de vie de y ?
    }
}

Le compilateur ne peut pas déterminer si la référence renvoyée est valide :

fn main() {
    let string1 = String::from("chaîne longue");
    laissez le résultat ;

    {
        let string2 = String::from("short");
        résultat = le plus long (&string1, &string2);
    } // string2 déposé ici

    // Le résultat est-il valide ? Cela dépend de l'entrée renvoyée !
}

Annotations à vie : contrats explicites

fn le plus long<'a>(x : &'a str, y : &'a str) -> &'a str {
    si x.len() > y.len() {
        x
    } autre {
        oui
    }
}

Lecture de ceci : "La référence renvoyée sera valide tant que les x et y sont valides."

fn main() {
    let string1 = String::from("chaîne longue");
    laissez le résultat ;

    {
        let string2 = String::from("short");
        résultat = le plus long (&string1, &string2);
        println!("{}", résultat);  // OK : les deux sont toujours valides
    } // chaîne2 supprimée

    // println!("{}", résultat);  // ERREUR : string2 a peut-être été renvoyé
}

Élision à vie : quand vous n'avez pas besoin d'annotations

Le compilateur peut déduire des durées de vie selon des modèles courants :

// Aucune annotation nécessaire - durée de vie d'une seule entrée
fn premier_mot(s : &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

// Le compilateur voit cela comme :
fn premier_mot<'a>(s : &'a str) -> &'a str {
    s.split_whitespace().next().unwrap_or("")
}

Règles d'élision :

  1. Chaque référence d'entrée a sa propre durée de vie
  2. S'il existe exactement une durée de vie d'entrée, la sortie obtient cette durée de vie
  3. Si la méthode avec &self, la sortie obtient la durée de vie de self

Durées de vie dans les structures

Lorsque les structures contiennent des références, vous devez annoter les durées de vie :

struct ImportantExcerpt<'a> {
    partie : &'a str,
}

fn main() {
    let roman = String::from("Appelle-moi Ismaël. Il y a quelques années...");
    let first_sentence = roman.split('.').next().unwrap();

    let extrait = ImportantExcerpt {
        partie : première_phrase,
    } ;

    println!("{}", extrait.part);
} // extrait et roman abandonnés, tout va bien

Signification : Un « ImportantExcerpt » ne peut pas survivre aux données auxquelles il fait référence.

fn main() {
    laissez extrait;

    {
        let roman = String::from("Appelle-moi Ismaël.");
        extrait = ImportantExtrait {
            partie : &roman,
        } ;
    } // ERREUR : roman abandonné, extrait.part suspendu

    // println!("{}", extrait.part);
}

La « durée de vie statique »

« statique » signifie « vit pendant toute la durée du programme » :

// Les littéraux de chaîne ont une « durée de vie statique
let s: &'static str = "Je suis stocké dans le binaire";

// Variables statiques
static GLOBAL : &str = "Également 'statique" ;

Erreur courante : N'utilisez pas « statique » juste pour faire disparaître les erreurs !

// MAUVAIS : Forcer 'static à compiler
fn bad_function() -> &'static str {
    let s = String::from("bonjour");
    // &s // Impossible de revenir - ne vit pas assez longtemps
    // Une fuite de mémoire pour obtenir « statique » est une erreur !
}

// BON : renvoie les données possédées à la place
fn good_function() -> Chaîne {
    String::from("bonjour")
}

6. Pièges courants et comment les résoudre

Piège 1 : Impossible d'emprunter en tant que mutable car il est déjà emprunté

// ERREUR
fn main() {
    soit mut vec = vec![1, 2, 3];

    soit d'abord = &vec[0];  // Emprunt immuable
    vec.push(4);          // ERREUR : emprunt mutable
    println!("{}", d'abord);
}

Pourquoi cela échoue : push pourrait être réaffecté, invalidant first.

Solution 1 : Restructurer pour séparer les emprunts

fn main() {
    soit mut vec = vec![1, 2, 3];

    soit first_value = vec[0];  // Copie la valeur
    vec.push(4);               // OK : aucun emprunt en cours
    println!("{}", first_value);
}

Solution 2 : clonez les données avant de muter

fn main() {
    soit mut vec = vec![1, 2, 3];

    laissez d'abord = vec.get(0).cloned();  // Option<i32>, pas d'emprunt
    vec.push(4);
    si let Some(val) = first {
        println!("{}", val);
    }
}

Piège 2 : Impossible de renvoyer une référence à une variable locale

// ERREUR
fn create_string() -> &String {
    let s = String::from("bonjour");
    &s // ERREUR : renvoie la référence aux données appartenant à la fonction
} // s est déposé ici, renverrait un pointeur suspendu !

Solution : Renvoyer les données détenues

fn create_string() -> Chaîne {
    String::from("hello") // Propriété transférée à l'appelant
}

Piège 3 : impossible de sortir du contenu emprunté

// ERREUR
fn main() {
    laissez vec = vec![String::from("a"), String::from("b")];
    laissez d'abord = &vec;

    laissez pris = vec[0];  // ERREUR : impossible de quitter le contenu indexé
}

Solution 1 : Cloner la valeur

fn main() {
    laissez vec = vec![String::from("a"), String::from("b")];
    laissez pris = vec[0].clone();
    println!("{}", pris);
}

Solution 2 : Utiliser des méthodes qui transfèrent la propriété

fn main() {
    laissez mut vec = vec![String::from("a"), String::from("b")];
    laisser pris = vec.swap_remove(0);  // Prend possession
    println!("{}", pris);
}

Piège 4 : emprunts mutables et immuables simultanés

// ERREUR
fn main() {
    laissez mut map = HashMap::new();
    map.insert("clé", "valeur");

    let value = map.get("clé");
    map.insert("clé2", "valeur2");  // ERREUR : impossible de muter pendant l'emprunt
    println!("{:?}", valeur);
}

Solution : utilisez l'API d'entrée

fn main() {
    laissez mut map = HashMap::new();
    map.insert("clé", "valeur");

    map.entry("key2").or_insert("value2");  // Aucun emprunt conflictuel

    if let Some(value) = map.get("key") {
        println!("{}", valeur);
    }
}

Piège 5 : inadéquation à vie dans les structures

// ERREUR
struct Conteneur {
    data : &str, // ERREUR : annotation de durée de vie manquante
}

Solution : Ajouter un paramètre de durée de vie

struct Conteneur<'a> {
    données : &'a str,
}

impl<'a> Conteneur<'a> {
fn new(texte : &'a str) -> Soi {
        Conteneur { données : texte }
    }

    fn get_data(&self) -> &str {
        données personnelles
    }
}

Piège 6 : Invalidation de l'itérateur

// ERREUR
fn main() {
    soit mut vec = vec![1, 2, 3, 4, 5];

    pour moi dans &vec {
        si *je % 2 == 0 {
            vec.push(*i * 2);  // ERREUR : impossible de modifier pendant l'itération
        }
    }
}

Solution : Collectez d'abord les indices

fn main() {
    soit mut vec = vec![1, 2, 3, 4, 5];

    let to_add : Vec<i32> = vec.iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| x * 2)
        .collecter();

    vec.extend(to_add);
    println!("{:?}", vec);
}

7. Modèles avancés : au-delà de la propriété de base

Modèle 1 : Mutabilité intérieure avec RefCell

Parfois, vous devez muter des données avec seulement une référence immuable :

utilisez std :: cell :: RefCell ;

struct Enregistreur {
    logs : RefCell<Vec<String>>, // Peut muter via &self
}

impl Enregistreur {
    fn new() -> Soi {
        Enregistreur {
            journaux : RefCell::new(Vec::new()),
        }
    }

    fn log(&self, message: &str) { // Prend &self, pas &mut self
        self.logs.borrow_mut().push(message.to_string());
    }

    fn print_logs(&self) {
        pour vous connecter self.logs.borrow().iter() {
            println!("{}", journal);
        }
    }
}

fn main() {
    let logger = Logger::new();
    logger.log("Premier message");
    logger.log("Deuxième message");
    logger.print_logs();
}

Quand utiliser :

  • Implémentation de caches ou de loggers
  • Structures graphiques ou arborescentes avec mutabilité intérieure
  • Objets simulés dans les tests

Attention : Vérification des emprunts à l'exécution : panique en cas de violation des règles !

laissez cell = RefCell::new(5);
laissez emprunté1 = cell.borrow();
laissez emprunté2 = cell.borrow_mut();  // PANIQUE : déjà emprunté !

Modèle 2 : Comptage de références avec Rc

Partagez la propriété des données avec plusieurs propriétaires :

utilisez std :: rc :: Rc ;

Nœud struct {
    valeur : i32,
    enfants : Vec<Rc<Node>>,
}

fn main() {
    laissez leaf = Rc::new(Noeud {
        valeur : 3,
        enfants : vec ![],
    });

    laissez branch1 = Rc::new(Noeud {
        valeur : 1,
        enfants : vec![Rc::clone(&leaf)], // Propriété partagée
    });

    laissez branch2 = Rc::new(Noeud {
        valeur : 2,
        children: vec![Rc::clone(&leaf)], // Les deux branches possèdent une feuille
    });

    println!("Nombre de références de feuilles : {}", Rc::strong_count(&leaf));  // 3
}

Points clés :

  • Non thread-safe (utilisez Arc pour les threads)
  • Le comptage de références a des frais généraux
  • Crée des cycles s'il n'est pas prudent (utilisez « Faible » pour briser les cycles)

Modèle 3 : Combinaison de Rc et RefCell

Propriétaires multiples avec mutation :

utilisez std :: rc :: Rc ;
utilisez std :: cell :: RefCell ;

#[dériver (Débogage)]
struct SharedCounter {
    nombre : Rc<RefCell<i32>>,
}

impl SharedCounter {
    fn new() -> Soi {
        Compteur partagé {
            nombre : Rc::new(RefCell::new(0)),
        }
    }

    fn incrément(&soi) {
        *self.count.borrow_mut() += 1;
    }

    fn obtenir(&soi) -> i32 {
        *self.count.borrow()
    }
}

fn main() {
    laissez counter1 = SharedCounter::new();
    laissez counter2 = SharedCounter {
        compte : Rc::clone(&counter1.count),
    } ;

    counter1.increment();
    counter2.increment();

    println!("Count : {}", counter1.get());  // 2
}

Modèle 4 : Modèle de constructeur avec propriété

struct Serveur {
    hôte : chaîne,
    port: u16,
    délai d'attente : u64,
}

struct ServerBuilder {
    hôte : Option<String>,
    port : Option<u16>,
    délai d'attente : Option<u64>,
}

impl ServerBuilder {
    fn new() -> Soi {
        Générateur de serveurs {
            hôte : Aucun,
            port: aucun,
            délai d'attente : aucun,
        }
    }

    fn host(mut self, host: impl Into<String>) -> Self {
        self.host = Certains(host.into());
        self // Revenir à la propriété
    }

    port fn (mut self, port : u16) -> Self {
        self.port = Certains (port);
        soi
    }

    fn timeout (mut self, timeout : u64) -> Self {
        self.timeout = Certains (timeout);
        soi
    }

    fn build(self) -> Résultat<Serveur, &'static str> {
        Ok (serveur {
            hôte : self.host.ok_or("L'hôte est requis") ?,
            port : self.port.unwrap_or(8080),
            délai d'attente : self.timeout.unwrap_or(30),
        })
    }
}

fn main() {
    laissez serveur = ServerBuilder::new()
        .host("localhost")
        .port(3000)
        .timeout(60)
        .build()
        .unwrap();

    println!("Serveur : {}:{}", serveur.hôte, serveur.port);
}

Modèle 5 : RAII (l'acquisition de ressources est une initialisation)

La propriété permet le nettoyage automatique des ressources :

utilisez std::fs::Fichier ;
utilisez std::io::{self, Write} ;

struct FichierJournal {
    fichier : Fichier,
}

impl LogFile {
    fn new(chemin : &str) -> io::Result<Self> {
        Ok (Fichier journal {
            fichier : Fichier : créer (chemin )?,
        })
    }

    fn write_log(&mut self, message: &str) -> io::Result<()> {
        writeln!(self.file, "{}", message)
    }
}

impl Drop pour le fichier journal {
    fn drop(&mut soi) {
        println!("Fermeture du fichier journal");
        // Fichier automatiquement fermé lorsqu'il est déposé
    }
}

fn main() -> io::Result<()> {
    {
        let mut log = LogFile::new("app.log")?;
        log.write_log("Application démarrée"?);
        log.write_log("Traitement des données"?);
    } // Fichier automatiquement fermé ici via Drop

    println!("Fichier journal fermé automatiquement");
    D'accord(())
}

8. Stratégies de refactorisation du monde réel

Scénario 1 : Transmission de données aux fonctions

Avant (combattre le vérificateur d'emprunt) :

struct Utilisateur {
    nom : chaîne,
    email : chaîne,
}

fn process_user (utilisateur : utilisateur) {
    println!("Traitement {}", user.name);
}

fn main() {
    laissez l'utilisateur = Utilisateur {
        nom : String::from("Alice"),
        email : String::from("[email protected]"),
    } ;

    process_user(utilisateur);
    // println!("{}", user.name);  // ERREUR : utilisateur déplacé
}

Après (emprunter au lieu de déménager) :

fn process_user(user: &User) { // Emprunter à la place
    println!("Traitement {}", user.name);
}

fn main() {
    laissez l'utilisateur = Utilisateur {
        nom : String::from("Alice"),
        email : String::from("[email protected]"),
    } ;

    process_user(&utilisateur);
    println!("{}", user.name);  // OK : l'utilisateur est toujours propriétaire
}

Scénario 2 : Travailler avec des collections

Avant :

fn get_first_name(utilisateurs : Vec<Utilisateur>) -> Option<String> {
    users.first().map(|u| u.name.clone()) // Clone inutile
}

fn main() {
    laissez les utilisateurs = vec![/* ... */];
    let name = get_first_name (utilisateurs);
    // Je ne peux plus utiliser les utilisateurs - déplacé
}

Après :

fn get_first_name(utilisateurs : &[Utilisateur]) -> Option<&str> {
    utilisateurs.first().map(|u| u.name.as_str())
}

fn main() {
    laissez les utilisateurs = vec![/* ... */];
    let name = get_first_name(&users);
    // les utilisateurs sont toujours utilisables
}

Scénario 3 : Struct avec plusieurs champs de chaîne

Avant (beaucoup de clonage) :

fn build_full_name (premier : chaîne, dernier : chaîne) -> Chaîne {
    format!("{} {}", premier, dernier)
}

fn main() {
    let first = String::from("John");
    let last = String::from("Doe");

    let full = build_full_name(first.clone(), last.clone());
    println!("Premier : {}, Dernier : {}", premier, dernier);
}

Après (utiliser des tranches de chaîne) :

fn build_full_name(premier : &str, dernier : &str) -> Chaîne {
    format!("{} {}", premier, dernier)
}

fn main() {
    let first = String::from("John");
    let last = String::from("Doe");

    let full = build_full_name(&first, &last);
    println!("Premier : {}, Dernier : {}", premier, dernier);
}

Scénario 4 : mise en cache des résultats

Problème : Besoin d'un cache mutable avec des méthodes immuables

Solution : mutabilité intérieure

utilisez std :: cell :: RefCell ;
utilisez std :: collections :: HashMap ;

struct Calculateur Cher {
    cache : RefCell<HashMap<i32, i32>>,
}

impl Calculateur Cher {
    fn new() -> Soi {
        Calculatrice chère {
            cache : RefCell::new(HashMap::new()),
        }
    }

    fn calculate(&self, input: i32) -> i32 { // &self, pas &mut self
        // Vérifie le cache
        if let Some(&cached) = self.cache.borrow().get(&input) {
            retourner en cache ;
        }

        // Calcul coûteux
        soit résultat = entrée * entrée ;

        // Stocker en cache
        self.cache.borrow_mut().insert(entrée, résultat);

        résultat
    }
}

fn main() {
    let calc = ExpensiveCalculator::new();
    println!("{}", calc.calculate(5));  // Calculé
    println!("{}", calc.calculate(5));  // Depuis le cache
}

Scénario 5 : Structures arborescentes

Défi : les relations parents-enfants créent des conflits d'emprunt

Solution : Utiliser des indices ou Rc/Weak

utilisez std::rc::{Rc, Faible} ;
utilisez std :: cell :: RefCell ;

Nœud struct {
    valeur : i32,
    parent : RefCell<Weak<Node>>,
    enfants : RefCell<Vec<Rc<Node>>>,
}

Nœud impl {
    fn nouveau (valeur : i32) -> Rc<Self> {
        Rc::nouveau(Nœud {
            valeur,
            parent : RefCell::new(Weak::new()),
            enfants : RefCell::new(vec![]),
        })
    }
fn add_child(parent : &Rc<Node>, enfant : Rc<Node>) {
        *child.parent.borrow_mut() = Rc::downgrade(parent);
        parent.enfants.borrow_mut().push(enfant);
    }
}

fn main() {
    laissez root = Node::new(1);
    laissez child1 = Node::new(2);
    laissez child2 = Node::new(3);

    Node::add_child(&root, child1);
    Node::add_child(&root, child2);

    println!("La racine a {} enfants", root.children.borrow().len());
}

9. Implications en termes de performances : abstractions à coût nul

La propriété est sans coût

Le système de propriété n'a aucune surcharge d'exécution :

// Ce code Rust :
processus fn (données : Vec<i32>) -> i32 {
    data.iter().sum()
}

// Compile dans le même assembly que :
// processus int (int* données, size_t len) {
// somme int = 0 ;
// pour (size_t i = 0; i < len; i++) {
// somme += données[i];
// }
// renvoie la somme ;
// }

Preuve : Vérifiez l'assemblage avec cargo build --release et des outils comme cargo-asm.

Quand le clonage a un coût

// Cher : copie approfondie
soit vec1 = vec![1, 2, 3, 4, 5];
soit vec2 = vec1.clone();  // Alloue une nouvelle mémoire tas, copie tous les éléments

// Gratuit : Référence
soit vec1 = vec![1, 2, 3, 4, 5];
soit vec2 = &vec1;  // Pas d'allocation, juste un pointeur

Benchmark : Cloner ou Emprunter

utilisez std::time::Instant ;

fn process_by_value(données : Vec<i32>) -> i32 {
    data.iter().sum()
}

fn process_by_reference(data: &[i32]) -> i32 {
    data.iter().sum()
}

fn main() {
    laissez les données : Vec<i32> = (0..1_000_000).collect();

    // Version clone
    let start = Instant::now();
    pour _ dans 0..1000 {
        laissez result = process_by_value(data.clone());  // Cloner à chaque fois
    }
    println!("Clone : {:?}", start.elapsed());

    // Emprunter la version
    let start = Instant::now();
    pour _ dans 0..1000 {
        laissez result = process_by_reference(&data);  // Pas de clone
    }
    println!("Emprunter : {:?}", start.elapsed());
}

// Résultats typiques :
// Clonage : 850 ms
// Emprunter : 120 ms

Frais généraux du pointeur intelligent

// Rc a une petite surcharge
utilisez std :: rc :: Rc ;
utilisez std::time::Instant ;

fn avec_rc(données : Rc<Vec<i32>>) {
    let _ = data.len();
}

fn avec_ref(données : &Vec<i32>) {
    let _ = data.len();
}

// Rc est 2 mots (pointeur + nombre de références)
// La référence est 1 mot (juste un pointeur)
// Mais Rc permet la propriété partagée là où les références ne le peuvent pas

Quand les frais généraux sont importants :

  • Boucles chaudes avec des millions d'itérations
  • Systèmes temps réel
  • Systèmes embarqués avec ressources limitées

Lorsque les frais généraux n'ont pas d'importance :

  • La plupart des codes d'application
  • Quand cela permet une meilleure conception
  • Quand le clonage coûterait plus cher

10. Guide de migration : à partir d'autres langues

Venant de C++

État d'esprit C++ :

std::string* createString() {
    return new std::string("bonjour");  // L'appelant doit supprimer
}

processus vide() {
    std::string* s = createString();
    std :: cout << * s;
    supprimer les s ;  // Nettoyage manuel
}

Équivalent rouille :

fn create_string() -> Chaîne {
    String::from("hello") // Propriété transférée
}

processus fn() {
    soit s = create_string();
    println!("{}", s);
} // Supprimé automatiquement

Différences clés :

  • Pas de « nouveau »/« suppression » manuelle
  • Pas de pointeurs bruts dans le code sécurisé
  • Les références sont vérifiées à vie
  • Déplacer la sémantique par défaut

Venant de Go

Allez avec l'état d'esprit :

processus func (données [] int) {
    data[0] = 100 // Mute l'original
}

fonction main() {
    nums := []int{1, 2, 3}
    processus (numéros)
    fmt.Println(nums) // [100, 2, 3]
}

Rust nécessite une mutabilité explicite :

processus fn (données : &mut [i32]) { // &mut explicite
    données[0] = 100 ;
}

fn main() {
    laissez mut nums = vec![1, 2, 3];  // mot-clé mut requis
    processus(&mut nums);            // &mut explicite
    println!("{:?}", nums);        // [100, 2, 3]
}

Différences clés :

  • La mutabilité doit être explicite
  • Pas de courses de données cachées
  • Les références sont explicites (& vs valeur)

Venant de Python

État d'esprit Python :

def modifier_list (éléments):
    items.append(4) # Mute l'original

nombres = [1, 2, 3]
modifier_liste (numéros)
print(nombres) # [1, 2, 3, 4]

Équivalent rouille :

fn modifier_list(éléments : &mut Vec<i32>) {
    articles.push(4);
}

fn main() {
    soit mut nums = vec![1, 2, 3];
    modifier_list(&mut nums);
    println!("{:?}", nums);  // [1, 2, 3, 4]
}

Différences clés :

  • Tout en Python est une référence ; Rust distingue les valeurs et les références
  • Python GC gère le nettoyage ; Rust utilise la propriété
  • Python permet la mutation librement ; Rust nécessite mut

Provenant de JavaScript

État d'esprit JavaScript :

fonction créerUtilisateur() {
    return { nom : "Alice", email : "[email protected]" } ;
}

laissez l'utilisateur = createUser();
laissez user2 = utilisateur ;  // Copie superficielle
utilisateur2.nom = "Bob" ;
console.log(utilisateur.nom);  // "Bob" - les deux font référence au même objet

Comportement de la rouille :

#[dériver(Cloner)]
struct Utilisateur {
    nom : chaîne,
    email : chaîne,
}

fn create_user() -> Utilisateur {
    Utilisateur {
        nom : String::from("Alice"),
        email : String::from("[email protected]"),
    }
}

fn main() {
    laissez l'utilisateur = create_user();
    laissez user2 = utilisateur ;  // Déplacé, pas copié
    // println!("{}", user.name);  // ERREUR : valeur déplacée

    // Si vous souhaitez un comportement de copie :
    laissez l'utilisateur = create_user();
    laissez user2 = user.clone();  // Clone explicite
    println!("{}", user.name);  //D'accord
}

Différences clés :

  • Les objets JS sont comptés par référence ; Rust se déplace par défaut
  • JS a GC ; Rust est propriétaire
  • La mutation JS n'est pas restreinte ; Rust applique les règles d'emprunt

Conclusion : adoptez le vérificateur d'emprunt

Le système de propriété semble restrictif au début, mais il est en réalité libérateur :

Aucune fuite de mémoire : S'il compile, la mémoire est gérée correctement ✅ Pas de course aux données : les bugs simultanés sont détectés au moment de la compilation ✅ Pas d'utilisation après-libération : impossible d'accéder à la mémoire libérée ✅ Performances prévisibles : pas de pause GC, pas d'allocations cachées ✅ Refactoring sans peur : le compilateur détecte les modifications importantes

Obtenir des services professionnels →

La courbe d'apprentissage

Semaine 1-2 : Frustration. Le vérificateur d'emprunt rejette tout. Semaine 3-4 : Compréhension. Vous commencez à penser en tant que propriétaire. Mois 2 : Maîtrise. Vous concevez des API qui fonctionnent avec la propriété. Mois 3+ : Maîtrise. Vous écrivez Rust plus rapidement que votre ancien langage.

Conseils pratiques pour apprendre

  1. Lisez attentivement les erreurs du compilateur : ce sont d'excellents professeurs
  2. Commencez avec de petits programmes : maîtrisez les bases avant de créer de grands systèmes
  3. Utilisez généreusement clone() au début - optimisez plus tard
  4. Ne combattez pas le vérificateur d'emprunt - repensez la conception si vous avez des difficultés
  5. Étudiez le code de bibliothèque standard - voyez comment les experts procèdent

Prochaines étapes

  • Pratique : Résolvez des problèmes sur Exercism, LeetCode ou Advent of Code dans Rust
  • Lire : The Rust Book, Rust by Exemple, Rustonomicon (Rust dangereux)
  • Build : de vrais projets vous obligent à rencontrer et à résoudre de vrais problèmes
  • Contribuer : les projets open source Rust accueillent les nouveaux arrivants

Ressources


Coffee cup

Vous avez apprécié cet article ?

Votre soutien m'aide à créer davantage de contenu technique approfondi, d'outils open source et de ressources gratuites pour la communauté des développeurs.

Sujets connexes

Engr Mejba Ahmed

À propos de l'auteur

Engr Mejba Ahmed

Engr. Mejba Ahmed builds AI-powered applications and secure cloud systems for businesses worldwide. With 10+ years shipping production software in Laravel, Python, and AWS, he's helped companies automate workflows, reduce infrastructure costs, and scale without security headaches. He writes about practical AI integration, cloud architecture, and developer productivity.

Discussion

Comments

0

No comments yet

Be the first to share your thoughts

Leave a Comment

Your email won't be published

13  -  8  =  ?

Comments

Leave a Comment

Comments are moderated before appearing.

Learning Resources

Expand Your Knowledge

Accelerate your growth with structured courses, verified certificates, interactive flashcards, and production-ready AI agent skills.

Sample Certificate of Completion

Sample certificate — complete any course to earn yours

Engr Mejba Ahmed

Engr Mejba Ahmed

Claude Code Expert · Online

👋

Hey there!

Quick Actions

WhatsApp Instant reply

Chat on WhatsApp

+880 1723 741224 · Instant reply

Popular Questions

Engr Mejba Ahmed is connected
Engr Mejba Ahmed is typing...
Engr Mejba Ahmed avatar

✉ Want me to follow up? Drop your email

Engr Mejba Ahmed avatar

📞 Connect Directly

Choose how you'd like to reach me

WhatsApp

+880 1723 741224

Email

[email protected]

✓ Details sent! I'll get back to you shortly.

Powered by OpenAI

335+

Blog Posts

25

AI Courses

63

Projects

Services & Expertise

Pricing & Process

Learning & Resources

Connect & Support