Nouveau : Datasets open source gratuits disponibles !Decouvrir →
🦀
Intermediaire 25 min Rust

Ownership et borrowing en Rust

Pourquoi Ownership et borrowing en Rust ?

L'Ownership et borrowing sont des concepts fondamentaux de la sécurité et de l'efficacité dans le langage de programmation Rust. Ils permettent de gérer efficacement les ressources du système tout en protégeant contre les erreurs courantes comme les fuites de mémoire, les double libérations et les accès concurrents non sécurisés.

Un cas d'usage concret est la gestion des fichiers sur un système d'exploitation. En utilisant l'ownership, Rust s'assure que chaque fichier est ouvert une seule fois et fermé correctement. Lorsqu'un fichier est fermé, les ressources associées sont libérées immédiatement, évitant ainsi toute fuite de mémoire.

Prerequis

  • Connaissance des bases de Rust (variables, types, structures)
  • Compréhension des blocs et des fonctions en Rust
  • Une installation de Rust avec rustc, cargo et un éditeur de code comme VSCode ou IntelliJ IDEA avec le plugin Rust

Concepts fondamentaux

Ownership

L'ownership est une règle fondamentale qui s'assure que chaque valeur a une seule variable propriétaire à tout moment. Lorsque la propriété d'une valeur est transférée, elle devient la propriété de la nouvelle variable.

Schéma mental :

  • Un objet est détenu par une personne
  • Quand cette personne le passe à une autre, elle le donne et perd la possession
  • La nouvelle personne doit prendre soin de l'objet
## ❌ Mauvais : On ne peut pas transférer la propriété d'une variable simple
fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 perd la possession, et s2 devient le propriétaire
    println!("{}", s1); // Erreur : s1 n'est plus valide ici
}
rust
## ✅ Correct : On peut transférer la propriété avec les fonctions
fn main() {
    let s1 = String::from("hello");
    let s2 = take_ownership(s1);
    println!("{}", s2); // Affiche "hello"
}

fn take_ownership(s: String) -> String {
    s
}

Borrowing

Le borrowing permet de partager une valeur sans transférer sa propriété. Il existe trois types de borrowing : immutable borrowing, mutable borrowing et shared borrowing.

Schéma mental :

  • Un objet est partagé avec plusieurs personnes
  • Chaque personne peut lire l'objet mais ne peut pas le modifier
  • Une seule personne peut modifier l'objet à la fois
## ❌ Mauvais : On ne peut pas emprunter une valeur mutable plus d'une fois
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1); // Emprunt immuable
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}
rust
## ✅ Correct : On peut emprunter une valeur mutable une seule fois
fn main() {
    let mut s1 = String::from("hello");
    change(&mut s1); // Emprunt mutable
    println!("{}", s1);
}

fn change(s: &mut String) {
    s.push_str(", world!");
}

Lifetimes

Les lifetimes sont un mécanisme qui assure que les références ne deviennent pas invalides. Ils déterminent la durée pendant laquelle une référence est valide.

Schéma mental :

  • L'age d'une personne
  • Une référence n'est valide que tant que le propriétaire est vivant
## ❌ Mauvais : On peut avoir une référence qui devient invalide
fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}

## ✅ Correct : On utilise les lifetimes pour s'assurer que les références sont valides
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Mise en pratique : projet fil rouge

Étape 1 : Création du projet

Commencez par créer un nouveau projet avec cargo :

cargo new ownership_borrowing_project
cd ownership_borrowing_project

Étape 2 : Création des fichiers

Créez les fichiers suivants dans le répertoire src :

  • main.rs
  • utils.rs

Étape 3 : Implémentation du code

main.rs

mod utils;

fn main() {
    let mut task_list = vec!["Task 1", "Task 2"];

    utils::add_task(&mut task_list, "Task 3");
    println!("Tasks: {:?}", task_list);
}

utils.rs

## ❌ Mauvais : On ne peut pas emprunter une valeur mutable plus d'une fois
pub fn add_task(tasks: &mut Vec<&str>, new_task: &str) {
    tasks.push(new_task); // Erreur : on ne peut pas emprunter `tasks` plusieurs fois
}
rust
## ✅ Correct : On utilise les lifetimes pour s'assurer que les références sont valides
pub fn add_task<'a>(tasks: &'a mut Vec<&'a str>, new_task: &'a str) {
    tasks.push(new_task);
}

Étape 4 : Exécution du code

Exécutez le projet avec cargo run :

cargo run

Erreurs frequentes et debugging

  1. Erreur : error[E0505]: cannot move out of a shared reference

    • Code incorrect :
      fn main() {
          let s1 = String::from("hello");
          let len = calculate_length(s1);
          println!("The length of '{}' is {}.", s1, len); // Erreur : s1 n'est plus valide ici
      }
      
      fn calculate_length(s: String) -> usize {
          s.len()
      }
      
    • Code correct :
      fn main() {
          let s1 = String::from("hello");
          let len = calculate_length(&s1);
          println!("The length of '{}' is {}.", s1, len); // Affiche "The length of 'hello' is 5."
      }
      
      fn calculate_length(s: &String) -> usize {
          s.len()
      }
      
  2. Erreur : error[E0382]: borrow of moved value

    • Code incorrect :
      fn main() {
          let mut s1 = String::from("hello");
          let len = calculate_length(&mut s1);
          println!("The length of '{}' is {}.", s1, len); // Erreur : on ne peut pas emprunter `s1` plusieurs fois
      }
      
      fn calculate_length(s: &mut String) -> usize {
          s.len()
      }
      
    • Code correct :
      fn main() {
          let mut s1 = String::from("hello");
          let len = calculate_length(&mut s1);
          println!("The length of '{}' is {}.", s1, len); // Affiche "The length of 'hello' is 5."
      }
      
      fn calculate_length(s: &mut String) -> usize {
          s.len()
      }
      
  3. Erreur : error[E0597]: borrowed value does not live long enough

    • Code incorrect :
      pub fn longest<'a>(x: &'a str, y: &str) -> &'a str {
          if x.len() > y.len() {
              x
          } else {
              y
          }
      }
      
      fn main() {
          let string1 = String::from("abcd");
          let string2 = "xyz";
          
          let result = longest(string1.as_str(), string2); // Erreur : on ne peut pas emprunter `string2` plusieurs fois
          println!("The longest string is {}", result);
      }
      
    • Code correct :
      pub 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("abcd");
          let string2 = "xyz";
          
          let result = longest(string1.as_str(), string2); // Affiche "The longest string is xyz"
          println!("The longest string is {}", result);
      }
      

Pour aller plus loin

1. Lifetimes avancés

2. Smart pointers

3. Borrowing vs Ownership

  • Comparer les avantages et inconvénients de l'ownership et du borrowing.
  • Exemple pratique avec des structures complexes.

Défi pratique

Implémentez une structure Person qui peut avoir plusieurs amis (également des Person). Chaque ami peut envoyer un message à cette personne. Utilisez des lifetimes et des méthodes pour gérer correctement les références et l'ownership.

struct Person {
    name: String,
}

impl Person {
    fn new(name: &str) -> Person {
        Person {
            name: name.to_string(),
        }
    }

    fn add_friend(&mut self, friend: &Person) {
        // Implémenter la logique ici
    }

    fn send_message(&self, message: &str, friend: &Person) {
        // Implémenter la logique ici
    }
}

fn main() {
    let alice = Person::new("Alice");
    let bob = Person::new("Bob");

    alice.add_friend(&bob);
    bob.send_message("Hello!", &alice); // Devrait afficher "Alice received a message: Hello!"
}

Ce défi vous permettra d'appliquer les concepts d'ownership, de borrowing et de lifetimes dans une situation réelle.

Besoin d'aide sur Rust ?

Besoin d'aide sur un projet technique ? Decrivez-le pour des conseils personnalises.

Recevoir des conseils

Questions frequentes

Qu'est-ce que le concept d'ownership en Rust ?
En Rust, l'ownership est un mécanisme qui gère la mémoire automatiquement. Chaque valeur dans Rust a une variable appelée son propriétaire. Une seule valeur peut à la fois être propriétaire et lorsque le propriétaire sort de sa portée, la valeur est détruite.
Comment fonctionne le concept de borrowing en Rust ?
Le borrowing en Rust permet d'utiliser une valeur sans prendre la propriété d'elle. Il existe trois règles principales : une valeur peut avoir exactement un owner, plusieurs références immuables ou une seule référence mutable à tout moment.
Quelle est l'importance de gérer les propriétés et le borrowing en Rust ?
Gérer les propriétés et le borrowing en Rust est crucial pour éviter des erreurs communes comme la double déallocation (double free) et les accès à des données après leur libération. Cela permet une gestion sûre et efficace de la mémoire sans avoir besoin d'un garbage collector.

Pages liees

Chaque semaine, le meilleur de la tech francaise

Tendances, salaires, outils et opportunites — directement dans votre boite mail.

Gratuit. Desabonnement en un clic. Pas de spam.