Références et Emprunt

RustRustBeginner
Pratiquer maintenant

This tutorial is from open-source community. Access the source code

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Bienvenue dans References and Borrowing. Ce laboratoire est une partie du Rust Book. Vous pouvez pratiquer vos compétences Rust dans LabEx.

Dans ce laboratoire, nous apprenons à utiliser les références en Rust pour emprunter des valeurs plutôt que de prendre la propriété, ce qui nous permet de passer et de manipuler des données sans avoir à renvoyer la propriété à la fonction appelante.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/MemorySafetyandManagementGroup(["Memory Safety and Management"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") rust/DataTypesGroup -.-> rust/string_type("String Type") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/MemorySafetyandManagementGroup -.-> rust/lifetime_specifiers("Lifetime Specifiers") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") subgraph Lab Skills rust/variable_declarations -.-> lab-100393{{"Références et Emprunt"}} rust/mutable_variables -.-> lab-100393{{"Références et Emprunt"}} rust/string_type -.-> lab-100393{{"Références et Emprunt"}} rust/function_syntax -.-> lab-100393{{"Références et Emprunt"}} rust/expressions_statements -.-> lab-100393{{"Références et Emprunt"}} rust/lifetime_specifiers -.-> lab-100393{{"Références et Emprunt"}} rust/method_syntax -.-> lab-100393{{"Références et Emprunt"}} end

References and Borrowing

Le problème avec le code de tuple dans la liste 4-5 est que nous devons renvoyer la String à la fonction appelante afin que nous puissions toujours utiliser la String après l'appel à calculate_length, car la String a été déplacée dans calculate_length. Au lieu de cela, nous pouvons fournir une référence à la valeur String. Une référence est comme un pointeur dans le sens où c'est une adresse que nous pouvons suivre pour accéder aux données stockées à cette adresse ; ces données sont possédées par une autre variable. Contrairement à un pointeur, une référence est garantie pour pointer vers une valeur valide d'un type particulier pour la durée de vie de cette référence.

Voici comment vous définiriez et utiliseriez une fonction calculate_length qui a une référence à un objet en tant que paramètre au lieu de prendre la propriété de la valeur :

Nom de fichier : src/main.rs

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

Tout d'abord, remarquons que tout le code de tuple dans la déclaration de variable et la valeur de retour de fonction est disparu. Deuxièmement, notons que nous passons &s1 à calculate_length et, dans sa définition, nous prenons &String au lieu de String. Ces ampersands représentent des références, et elles vous permettent de vous référer à une certaine valeur sans en prendre la propriété. La figure 4-5 illustre ce concept.

Figure 4-5 : Un diagramme de &String s pointant vers String s1

Note : L'opposé de la référence en utilisant & est le déréférencement, qui est effectué avec l'opérateur de déréférencement, *. Nous verrons quelques utilisations de l'opérateur de déréférencement au chapitre 8 et discuter des détails du déréférencement au chapitre 15.

Examillons de plus près l'appel de fonction ici :

let s1 = String::from("hello");

let len = calculate_length(&s1);

La syntaxe &s1 nous permet de créer une référence qui se réfère à la valeur de s1 mais ne la possède pas. Comme elle ne la possède pas, la valeur à laquelle elle pointe ne sera pas supprimée lorsque la référence cesse d'être utilisée.

De même, la signature de la fonction utilise & pour indiquer que le type du paramètre s est une référence. Ajoutons quelques annotations explicatives :

fn calculate_length(s: &String) -> usize { // s est une référence à une String
    s.len()
} // Ici, s sort de portée. Mais comme elle n'a pas la propriété de ce à quoi elle
  // se réfère, la String n'est pas supprimée

La portée dans laquelle la variable s est valide est la même que celle de tout paramètre de fonction, mais la valeur pointée par la référence n'est pas supprimée lorsque s cesse d'être utilisée, car s n'a pas la propriété. Lorsque les fonctions ont des références comme paramètres au lieu des valeurs réelles, nous n'aurons pas besoin de renvoyer les valeurs pour redonner la propriété, car nous n'avons jamais eu la propriété.

Nous appelons l'action de créer une référence emprunt. Comme dans la vie réelle, si une personne possède quelque chose, vous pouvez l'emprunter d'elle. Lorsque vous avez fini, vous devez le lui rendre. Vous ne le possédez pas.

Alors, que se passe-t-il si nous essayons de modifier quelque chose que nous empruntons? Essayez le code de la liste 4-6. Avertissement : ça ne fonctionne pas!

Nom de fichier : src/main.rs

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

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

Liste 4-6 : Tentative de modification d'une valeur empruntée

Voici l'erreur :

error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&`
reference
 --> src/main.rs:8:5
  |
7 | fn change(some_string: &String) {
  |                        ------- help: consider changing this to be a mutable
reference: `&mut String`
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so
the data it refers to cannot be borrowed as mutable

De même que les variables sont immuables par défaut, les références le sont également. Nous ne sommes pas autorisés à modifier quelque chose à quoi nous avons une référence.

Références mutables

Nous pouvons corriger le code de la liste 4-6 pour nous permettre de modifier une valeur empruntée avec quelques petits ajustements qui utilisent, au lieu de cela, une référence mutable :

Nom de fichier : src/main.rs

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

    change(&mut s);
}

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

Tout d'abord, nous changeons s en mut. Ensuite, nous créons une référence mutable avec &mut s où nous appelons la fonction change, et nous mettons à jour la signature de la fonction pour accepter une référence mutable avec some_string: &mut String. Cela rend très clair que la fonction change va modifier la valeur qu'elle emprunte.

Les références mutables ont une grande restriction : si vous avez une référence mutable à une valeur, vous ne pouvez avoir aucune autre référence à cette valeur. Ce code qui tente de créer deux références mutables à s échouera :

Nom de fichier : src/main.rs

let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s;

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

Voici l'erreur :

error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 |
7 |     println!("{r1}, {r2}");
  |                -- first borrow later used here

Cette erreur indique que ce code est invalide car nous ne pouvons pas emprunter s en tant que mutable plus d'une fois à la fois. Le premier emprunt mutable est dans r1 et doit durer jusqu'à ce qu'il soit utilisé dans le println!, mais entre la création de cette référence mutable et son utilisation, nous avons essayé de créer une autre référence mutable dans r2 qui emprunte les mêmes données que r1.

La restriction qui empêche d'avoir plusieurs références mutables à la même donnée en même temps permet la mutation mais de manière très contrôlée. C'est quelque chose avec quoi les nouveaux Rustaceans ont du mal car la plupart des langages vous laissent muter quand vous le voulez. L'avantage de cette restriction est que Rust peut empêcher les courses de données à la compilation. Une course de données est similaire à une condition de course et se produit lorsque ces trois comportements se produisent :

  • Deux ou plusieurs pointeurs accèdent à la même donnée en même temps.
  • Au moins l'un des pointeurs est utilisé pour écrire dans les données.
  • Il n'y a aucun mécanisme utilisé pour synchroniser l'accès aux données.

Les courses de données entraînent un comportement indéfini et peuvent être difficiles à diagnostiquer et à corriger lorsque vous essayez de les repérer à l'exécution ; Rust empêche ce problème en refusant de compiler le code avec des courses de données!

Comme toujours, nous pouvons utiliser des accolades pour créer un nouveau scope, permettant plusieurs références mutables, juste pas des simultanées :

let mut s = String::from("hello");

{
    let r1 = &mut s;
} // r1 sort de portée ici, donc nous pouvons créer une nouvelle référence sans problème

let r2 = &mut s;

Rust applique une règle similaire pour combiner les références mutables et immuables. Ce code entraîne une erreur :

let mut s = String::from("hello");

let r1 = &s; // pas de problème
let r2 = &s; // pas de problème
let r3 = &mut s; // GRAND PROBLÈME

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

Voici l'erreur :

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as
immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s; // pas de problème
  |              -- immutable borrow occurs here
5 |     let r2 = &s; // pas de problème
6 |     let r3 = &mut s; // GRAND PROBLÈME
  |              ^^^^^^ mutable borrow occurs here
7 |
8 |     println!("{r1}, {r2}, and {r3}");
  |                -- immutable borrow later used here

Phew! Nous ne pouvons pas non plus avoir une référence mutable tandis que nous avons une référence immuable à la même valeur.

Les utilisateurs d'une référence immuable n'attendent pas que la valeur change soudainement sous leurs yeux! Cependant, plusieurs références immuables sont autorisées car personne qui ne lit que les données n'a la capacité d'affecter la lecture des autres.

Notez que la portée d'une référence commence où elle est introduite et continue jusqu'à la dernière fois que cette référence est utilisée. Par exemple, ce code compilera car la dernière utilisation des références immuables, le println!, se produit avant l'introduction de la référence mutable :

let mut s = String::from("hello");

let r1 = &s; // pas de problème
let r2 = &s; // pas de problème
println!("{r1} and {r2}");
// les variables r1 et r2 ne seront pas utilisées après ce point

let r3 = &mut s; // pas de problème
println!("{r3}");

Les portées des références immuables r1 et r2 se terminent après le println! où elles sont dernièrement utilisées, ce qui est avant la création de la référence mutable r3. Ces portées ne se chevauchent pas, donc ce code est autorisé : le compilateur peut voir que la référence n'est plus utilisée à un moment avant la fin de la portée.

Même si les erreurs d'emprunt peuvent parfois être frustrantes, rappelez-vous que c'est le compilateur Rust qui indique un bogue potentiel tôt (au moment de la compilation plutôt qu'à l'exécution) et vous montre exactement où se trouve le problème. Alors, vous n'avez pas à chercher pourquoi vos données ne sont pas ce que vous pensiez qu'elles étaient.

Références périssantes

Dans les langages avec des pointeurs, il est facile de créer erronément un pointeur périssant --- un pointeur qui référence un emplacement en mémoire qui peut avoir été donné à quelqu'un d'autre --- en libérant une partie de la mémoire tout en conservant un pointeur vers cette mémoire. En revanche, en Rust, le compilateur garantit que les références ne seront jamais des références périssantes : si vous avez une référence à certaines données, le compilateur vous assurera que les données ne sortiront pas de portée avant que la référence aux données ne le fasse.

Essayons de créer une référence périssante pour voir comment Rust les empêche avec une erreur de compilation :

Nom de fichier : src/main.rs

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

Voici l'erreur :

error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value,
but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
5 | fn dangle() -> &'static String {
  |                ~~~~~~~~

Ce message d'erreur fait référence à une fonctionnalité que nous n'avons pas encore abordée : les durées de vie. Nous aborderons les durées de vie en détail au chapitre 10. Mais, si vous ignorez les parties sur les durées de vie, le message contient bien la clé du problème de ce code :

this function's return type contains a borrowed value, but there
is no value for it to be borrowed from

Examillons de plus près ce qui se passe à chaque étape de notre code dangle :

// src/main.rs
fn dangle() -> &String { // dangle renvoie une référence à une String

    let s = String::from("hello"); // s est une nouvelle String

    &s // nous renvoyons une référence à la String, s
} // Ici, s sort de portée et est supprimé, donc sa mémoire disparaît
  // Danger!

Comme s est créé à l'intérieur de dangle, lorsque le code de dangle est terminé, s sera désalloué. Mais nous avons essayé de renvoyer une référence à elle. Cela signifie que cette référence pointerait vers une String invalide. C'est pas bon! Rust ne nous laissera pas faire.

La solution ici est de renvoyer directement la String :

fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

Cela fonctionne sans problème. La propriété est transférée, et rien n'est désalloué.

Les règles des références

Récapitulons ce que nous avons discuté au sujet des références :

  • A tout moment donné, vous pouvez avoir soit une référence mutable soit un nombre quelconque de références immuables.
  • Les références doivent toujours être valides.

Ensuite, nous allons examiner un autre type de référence : les slices.

Sommaire

Félicitations! Vous avez terminé le laboratoire sur les Références et l'Emprunt. Vous pouvez pratiquer d'autres laboratoires dans LabEx pour améliorer vos compétences.