IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Rust par l'exemple


précédentsommairesuivant

13. Les contextes

Les contextes jouent un rôle important dans le fonctionnement du systèmes de propriété (ownership), d'emprunts (borrowing) et de durées de vie (lifetime). Ils témoignent de la validité (ou non) d'un emprunt, indiquent au compilateur lorsqu'une ressource peut être libérée et lorsqu'une variable est créée ou détruite.

13-1. Le RAII

En Rust, les variables ne stockent pas seulement leurs données dans la pile : Elles sont responsables de leurs ressources (e.g. le conteneur Box<T> possède, alloue de la mémoire dans le tas). Rust imposant l'approche du RAII, lorsqu'un objet sort du contexte, son destructeur est appelé et les ressources, possédées par l'objet, sont libérées.

Ce fonctionnement prévient les problèmes de fuites mémoire et nous dispense donc de gérer manuellement la mémoire. Voici un exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
// Dans le fichier raii.rs
fn create_box() {
    // Allocation d'un entier dans le tas.
    let _box1 = Box::new(3i32);

    // `_box1` est détruit ici, et la mémoire est libérée.
}

fn main() {
    // Allocation d'un entier dans le tas.
    let _box2 = Box::new(5i32);

    // Contexte imbriqué:
    {
        // Allocation d'un entier dans le tas.
        let _box3 = Box::new(4i32);

        // `_box3` est détruit ici, et la mémoire est libérée.
    }

    // On créé ici un grand nombre de "box" juste pour l'exemple.
    // Il n'y a pas besoin de libérer manuellement la mémoire !
    for _ in 0u32..1_000 {
        create_box();
    }

    // `_box2` est détruit ici, et la mémoire est libérée.
}

Bien entendu, vous pouvez vérifier par vous-même si des fuites sont présentes en utilisant valgrind :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
$ rustc raii.rs && valgrind ./raii
==26873== Memcheck, a memory error detector
==26873== Copyright (C) 2002-2013, and GNU GPL′d, by Julian Seward et al.
==26873== Using Valgrind-3.9.0 and LibVEX; rerun with -h for copyright info
==26873== Command: ./raii
==26873==
==26873==
==26873== HEAP SUMMARY:
==26873==     in use at exit: 0 bytes in 0 blocks
==26873==   total heap usage: 1,013 allocs, 1,013 frees, 8,696 bytes allocated
==26873==
==26873== All heap blocks were freed -- no leaks are possible
==26873==
==26873== For counts of detected and suppressed errors, rerun with: -v
==26873== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

Voir aussi

Box.

13-2. Ownership et transferts

Parce que les variables sont responsables de la libération de leurs ressources, les ressources ne peuvent avoir qu'un seul propriétaire/responsable.

Cette règle évite également au développeur de libérer plus d'une fois une ressource. Notez toutefois que toutes les variables ne possèdent pas leurs propres ressources (e.g. les références).

Lorsque nous assignons quelque chose à une variable (let x = y) ou passons un (ou des) argument à une fonction par valeur (foo(x)), l'ownership des ressources est transféré. Dans le jargon, cette action est nommée « move » (transfert).

Après avoir transféré une ressource, l'ancien propriétaire ne peut plus être utilisé. Cela prévient la création de « dangling pointers » (i.e. des pointeurs sur une ressource qui n'est plus valide).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
// Cette fonction prend possession d'un entier alloué dans le tas.
fn destroy_box(c: Box<i32>) {
    println!("Destroying a box that contains {}", c);

    // `c` est détruit et la mémoire va être libérée.
}

fn main() {
    // Entier alloué dans la pile.
    let x = 5u32;

    // On copie `x` dans `y` - aucune ressource n'a été transféré
    // (l'ownership n'a pas été transféré).
    let y = x;

    // Les deux valeurs peuvent être utilisées indépendamment.
    println!("x is {}, and y is {}", x, y);

    // `a` est un pointeur sur un entier alloué dans le tas.
    let a = Box::new(5i32);

    println!("a contains: {}", a);

    // On transfert `a` dans `b`.
    let b = a;
    // L'adresse du pointeur `a` est copié (et non la donnée) dans `b`.
    // `a` et `b` sont désormais des pointeurs sur la même donnée allouée dans le 
    // tas, mais `b` la possède, désormais.

    // Erreur! `a` ne peut plus accéder à la donnée car il ne possède plus 
    // le bloc mémoire.
    // println!("a contains: {}", a);
    // TODO ^ Essayez de décommenter cette ligne.

    // Cette fonction prend possession de la mémoire allouée dans le tas 
    // à partir de `b`.
    destroy_box(b);

    // Puisque la mémoire allouée a été libérée à partir d'ici, 
    // cette action consisterait à déréférencer de la mémoire libérée, 
    // mais cela est interdit par le compilateur.
    // Erreur! `b` ne peut plus accéder à la donnée car il ne possède plus 
    // le bloc mémoire.
    // println!("b contains: {}", b);
    // TODO ^ Essayez de décommenter cette ligne.
}

13-2-1. Mutabilité

La mutabilité d'une donnée peut être altérée lorsque l'ownership est transféré.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
fn main() {
    let immutable_box = Box::new(5u32);

    println!("immutable_box contains {}", immutable_box);

    // Erreur: `immutable_box` ne peut pas être déréférencé.
    // *immutable_box = 4;

    // On créé une copie mutable de `immutable_box`.
    let mut mutable_box = immutable_box;

    println!("mutable_box contains {}", mutable_box);

    // On modifie le contenu de la box.
    *mutable_box = 4;

    println!("mutable_box now contains {}", mutable_box);
}

13-3. Système d'emprunts

Très souvent, nous souhaiterions accéder à une ressource sans en prendre possession. Pour ce faire, Rust utilise un système d'emprunts. Plutôt que de passer un objet par valeur (T), il peut être passé par référence (&T).

Le compilateur garantit (grâce au vérificateur d'emprunts) que les références sont toujours valides. Tant qu'une référence de l'objet existe, il ne sera pas détruit.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
// Cette fonction prend possession d'une box et la détruit.
fn eat_box_i32(boxed_i32: Box<i32>) {
    println!("Destroying box that contains {}", boxed_i32);
}

// Cette fonction emprunte un entier signé
// codé sur 32 bits.
fn borrow_i32(borrowed_i32: &i32) {
    println!("This int is: {}", borrowed_i32);
}

fn main() {
    // On créé entier alloué dans le tas 
    // et un autre alloué dans la pile.
    let boxed_i32 = Box::new(5_i32);
    let stacked_i32 = 6_i32;

    // `borrow_i32()` emprunte le contenu de la box.
    // La fonction ne prend pas possession des ressources, 
    // donc le contenu peut être de nouveau emprunté.
    borrow_i32(&boxed_i32);
    borrow_i32(&stacked_i32);

    {
        // Récupère une référence de l'entier contenu dans la box.
        let _ref_to_i32: &i32 = &boxed_i32;

        // Erreur!
        // Vous ne pouvez pas détruire `boxed_i32` tant que la valeur 
        // qu'il contient est empruntée.
        // eat_box_i32(boxed_i32);
        // FIXME ^ Essayez de décommenter cette ligne.

        // La durée de vie de `_ref_to_i32` prend fin ici,
        // l'entier n'est donc plus emprunté.
    }

    // `boxed_i32` peut désormais être possédé par `eat_box()` et peut donc 
    // être détruit.
    eat_box_i32(boxed_i32);
}

13-3-1. Mutabilité

Une ressource mutable peut être modifiée, tout en étant empruntée, en utilisant &mut T. Nous passons alors une référence mutable de la ressource, donnant un accès en lecture et en écriture à l'emprunteur. En revanche, une référence dont la mutabilité n'est pas précisée (i.e. que le mot-clé mut n'est pas présent) empêche l'emprunteur d'accéder en écriture à la ressource.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
#[allow(dead_code)]
#[derive(Clone, Copy)]
struct Book {
    // `&'static str` est une référence d'une chaîne de caractères allouée 
    // dans un bloc mémoire qui ne peut être accédé qu'en lecture.
    author: &'static str,
    title: &'static str,
    year: u32,
}

// Cette fonction prend une référence d'une instance 
// de la structure `Book` en paramètre.
fn borrow_book(book: &Book) {
    println!("I immutably borrowed {} - {} edition", book.title, book.year);
}

// Cette fonction prend une référence mutable d'une instance de la 
// structure `Book` en paramètre, et initialise sa date de publication 
// à 2014.
fn new_edition(book: &mut Book) {
    book.year = 2014;
    println!("I mutably borrowed {} - {} edition", book.title, book.year);
}

fn main() {
    // On créé une instance immuable de la 
    // structure `Book` nommée `immutabook`.
    let immutabook = Book {
        // Les chaînes littérales sont typées `&'static str`.
        author: "Douglas Hofstadter",
        title: "Gödel, Escher, Bach",
        year: 1979,
    };

    // On créé une copie mutable de `immutabook` nommée
    // `mutabook`.
    let mut mutabook = immutabook;

    // Emprunte un objet en lecture seule.
    borrow_book(&immutabook);

    // Emprunte un objet en lecture seule.
    borrow_book(&mutabook);

    // Récupère une référence mutable d'un objet mutable.
    new_edition(&mut mutabook);

    // Erreur! Vous ne pouvez pas récupérer une référence 
    // mutable d'un objet immuable.
    // new_edition(&mut immutabook);
    // FIXME ^ Décommentez cette ligne.
}

Voir aussi

La lifetime 'static.

13-3-2. Verrouillage des ressources

Lorsqu'une référence immuable d'une ressource est récupérée, cette dernière est également « gelée », « verrouillée ». Lorsqu'une ressource est gelée, l'objet initial (celui à partir duquel une référence a été récupérée) ne peut être modifié jusqu'à ce que toutes les références soient détruites(i.e. ne figurent plus dans le contexte).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
fn main() {
    let mut _mutable_integer = 7i32;

    {
        // On emprunte `_mutable_integer`.
        // Accès en lecture uniquement.
        let _large_integer = &_mutable_integer;

        // Erreur! `_mutable_integer` est gelé dans ce contexte
        // (i.e. la ressource est empruntée).
        // _mutable_integer = 50;
        // FIXME ^ Décommentez/commentez cette ligne.

        // On sort du contexte de `_large_integer`.
    }

    // Aucun problème! `_mutable_integer` n'est plus gelé dans ce contexte.
    _mutable_integer = 3;
}

13-3-3. Limitations des accès lecture/écriture

Il est possible de faire autant « d'emprunts immuables » (i.e. récupérer une référence immuable) qu'on le souhaite. Toutefois, tant qu'il y a des accès en lecture par référence, l'objet original ne peut pas être modifié. Par contre, un seul accès en écriture, par ressource, est permis tant que le dernier emprunt par référence mutable n'est pas terminé (i.e. tant que la référence mutable est toujours dans le contexte).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
struct Point { x: i32, y: i32, z: i32 }

fn main() {
    let mut point = Point { x: 0, y: 0, z: 0 };

    {
        let borrowed_point = &point;
        let another_borrow = &point;

        // Vous pouvez accéder à la ressource par le biais des références 
        // créées et par l'objet original.
        println!("Point has coordinates: ({}, {}, {})",
                 borrowed_point.x, another_borrow.y, point.z);

        // Erreur! Vous ne pouvez pas avoir des accès en écriture à une ressource 
        // qui est déjà empruntée par une référence immuable (i.e. accès en lecture).
        // let mutable_borrow = &mut point;
        // TODO ^ Essayez de décommenter cette ligne.

        // On sort du contexte des références 
        // immuables.
    }

    {
        let mutable_borrow = &mut point;

        // On modifie la ressource par le biais d'une 
        // référence mutable.
        mutable_borrow.x = 5;
        mutable_borrow.y = 2;
        mutable_borrow.z = 1;

        // Erreur! Vous ne pouvez pas accéder à `point` en lecture 
        // alors que la ressource est potentiellement en train d'être modifiée.
        // let y = &point.y;
        // TODO ^ Essayez de décommenter cette ligne.

        // Erreur! On ne peut pas afficher `point.z` en utilisant 
        // la macro `println!` car elle prend en paramètre une référence 
        // immuable.
        // println!("Point Z coordinate is {}", point.z);
        // TODO ^ Essayez de décommenter cette ligne.

        // Ok! Les références mutables peuvent être passées comme 
        // références immuables à `println!`.
        println!("Point has coordinates: ({}, {}, {})",
                 mutable_borrow.x, mutable_borrow.y, mutable_borrow.z);

        // On sort du contexte des références 
        // mutables.
    }

    // Les accès en lecture sur `point` sont de nouveau 
    // permis par le vérificateur d'emprunts.
    let borrowed_point = &point;
    println!("Point now has coordinates: ({}, {}, {})",
             borrowed_point.x, borrowed_point.y, borrowed_point.z);
}

13-3-4. Le pattern ref

Lorsque vous vous servez du pattern matching ou de la déstructuration dans une assignation (let), le mot-clé ref peut être utilisé pour récupérer une référence d'un (ou plusieurs) champ d'une structure et/ou d'un tuple. Le code ci-dessous propose des exemples où ce modèle peut être utile :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
#[derive(Clone, Copy)]
struct Point { x: i32, y: i32 }

fn main() {
    let c = 'Q';

    // Un emprunt effectué avec le mot-clé `ref` (placé dans la l-value) 
    // est équivalent à une assignation "C-like" avec le `&` (placé dans la r-value).
    let ref ref_c1 = c;
    let ref_c2 = &c;

    println!("ref_c1 equals ref_c2: {}", *ref_c1 == *ref_c2);

    let point = Point { x: 0, y: 0 };

    // `ref` peut également être utilisé lors de la déstructuration 
    // d'une structure (aussi valable pour les tuples(structures et littéraux)).
    let _copy_of_x = {
        // `ref_to_x` est une référence du champ `x` contenu dans 
        // l'instance `point`.
        let Point { x: ref ref_to_x, y: _ } = point;

        // Renvoie une copie du champ `x` de l'instance `point`.
        *ref_to_x
    };

    // Copie mutable de l'instance `point`.
    let mut mutable_point = point;

    {
        // `ref` peut être combiné avec `mut` pour récupérer 
        // une référence mutable.
        let Point { x: _, y: ref mut mut_ref_to_y } = mutable_point;

        // On modifie le champ `y` de l'instance `mutable_point` par 
        // le biais de la référence mutable qu'on a récupéré.
        *mut_ref_to_y = 1;
    }

    println!("point is ({}, {})", point.x, point.y);
    println!("mutable_point is ({}, {})", mutable_point.x, mutable_point.y);

    // Un tuple mutable qui contient un pointeur et un entier non-signé 
    // codé sur 32 bits.
    let mut mutable_tuple = (Box::new(5u32), 3u32);

    {
        // Nous déstructurons le tuple `mutable_tuple` pour modifier 
        // la valeur de `last` (dernière élément indexé par le tuple).
        let (_, ref mut last) = mutable_tuple;
        *last = 2u32;
        // `_` est ajouté dans la valeur de gauche car on ne souhaite 
        // pas en récupérer le contenu.
    }

    println!("tuple is {:?}", mutable_tuple);
}

13-4. Système de durée de vie

Une « lifetime » est une construction utilisée par le compilateur pour s'assurer que tous les emprunts (i.e. références immuables/mutables) sont valides. Plus précisément, la durée de vie d'une variable commence lorsqu'elle est créée et se termine une fois cette dernière détruite.

Note : Bien que les lifetimes et les contextes soient très souvent cités ensembles cela reste, malgré tout, deux concepts bien distincts.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
// La création et destruction des lifetimes sont illustrées ci-dessous par des lignes.
// `i` possède la plus grande lifetime car son contexte englobe 
// `borrow1` et `borrow2`. La durée de vie de `borrow1` ne peut pas être 
// comparée à celle de `borrow2` car elle ne se trouve pas dans le 
// même contexte.
fn main() {
    let i = 3; // Lifetime for `i` starts. ────────────────┐
    //                                                     │
    { //                                                   │
        let borrow1 = &i; // `borrow1` lifetime starts. ──┐│
        //                                                ││
        println!("borrow1: {}", borrow1); //              ││
    } // `borrow1 ends. ──────────────────────────────────┘│
    //                                                     │
    //                                                     │
    { //                                                   │
        let borrow2 = &i; // `borrow2` lifetime starts. ──┐│
        //                                                ││
        println!("borrow2: {}", borrow2); //              ││
    } // `borrow2` ends. ─────────────────────────────────┘│
    //                                                     │
}   // Lifetime ends. ─────────────────────────────────────┘

Notez qu'aucun nom ou type n'est assigné aux labels de lifetimes. Nous allons apprendre, dans la prochaine section, à nous servir des durées de vie (et appréhender l'utilisation des labels).

13-4-1. Les labels

Le compilateur se sert des « annotations explicites » (labels) pour déterminer la durée de validité d'une référence. Dans le cas où les lifetimes ne peuvent pas être omises (note : Elles peuvent l'être grâce au concept d'élision), Rust dispose d'annotations explicites (labels) pour déterminer quelle devrait être la durée de vie d'une référence. Les labels sont précédés d'une 'apostrophe :

 
Sélectionnez
foo<'a>
// `foo` dispose de la durée de vie `'a`.

Tout comme les closures, pour utiliser les labels, vous devrez avoir recours à la généricité. De plus, cette syntaxe permet de préciser que la durée de vie de foo ne peut pas excéder celle de 'a.

Voici la syntaxe des labels lorsqu'ils sont appliqués à un type : &'a T (ou &'a mut T) où 'a est un label déclaré près de l'identificateur de la fonction.

Si vous devez déclarer plusieurs lifetimes, la syntaxe est tout aussi simple :

 
Sélectionnez
foo<'a, 'b>
// `foo` dispose des lifetimes `'a` et `'b`.

Dans ce cas, la durée de vie de foo ne peut pas excéder la lifetime 'a ou 'b.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
// `print_refs` prend deux références d'entiers possédant une durée de 
// vie différente chacune (i.e. `'a` et `'b`). Ces deux lifetimes doivent être 
// au moins aussi longues que celle de la fonction `print_refs`.
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("x is {} and y is {}", x, y);
}

// Une fonction qui ne prend aucun argument, mais qui déclare quand même 
// une lifetime `'a`.
fn failed_borrow<'a>() {
    let _x = 12;

    // ERREUR: `_x` ne vit pas assez longtemps.
    // let y: &'a i32 = &_x;
    // Tenter d'utiliser la lifetime `'a` sur une ressource au sein de la fonction
    // ne fonctionnera pas car la durée de vie de `&_x` est plus courte que celle 
    // de `y` .
    // Une courte durée de vie ne peut être rallongée en cours de route.
}

fn main() {
    // On crée ces variables pour les emprunter 
    // un peu plus bas.
    let (four, nine) = (4, 9);

    // `print_refs` emprunte (`&`) les deux ressources précédemment 
    // créées.
    print_refs(&four, &nine);
    // Une ressource empruntée doit survivre à la fonction qui l'emprunte.
    // Autrement dit, la durée de vie de `four` et `nine` doit être 
    // plus longue que celle de `print_refs`.

    failed_borrow();

    // `failed_borrow` ne contient aucune référence qui force la lifetime `'a` à 
    // être plus longue que celle de la fonction, mais `'a` reste plus longue.
    // Parce que la durée de vie d'une ressource (empruntée) n'est jamais imposée, 
    // la durée de vie par défaut est `'static`.
}

Voir aussi

La généricité et les closures.

13-4-2. Les fonctions

Lorsque le concept d'élision ne peut pas être appliqué, les signatures de fonctions comportant des (labels de) lifetimes sont régies par quelques règles :

  1. Toute référence doit être annotée d'une lifetime ;
  2. Toute référence renvoyée doit posséder la même lifetime qu'en entrée ou la lifetime 'static.

Notez également que, si une fonction n'a pas de références en entrée, le renvoi de références est interdit car cela pourrait conduire à l'utilisation de ressources invalides.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
// La référence passée en paramètre possédant la lifetime `'a` 
// doit vivre au moins aussi longtemps que le fonction.
fn print_one<'a>(x: &'a i32) {
    println!("`print_one`: x is {}", x);
}

// L'utilisation des références mutables est également possible 
// avec les lifetimes.
fn add_one<'a>(x: &'a mut i32) {
    *x += 1;
}

// Plusieurs entrées avec différentes lifetimes. Dans ce cas, 
// il serait plus simple, pour les deux paramètres, d'avoir 
// la même durée de vie (`'a`), mais dans des cas plus délicats, 
// plusieurs lifetimes peuvent être requises.
fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("`print_multi`: x is {}, y is {}", x, y);
}

// Renvoyer des références qui ont été passées en paramètre est légal.
// Toutefois, veillez à renvoyer la bonne lifetime.
fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { x }

// fn invalid_output<'a>() -> &'a i32 { &7 }
// La déclaration ci-dessus est invalide: `'a` doit, au moins, 
// survivre à la fonction. Ici, `&7` créerait un entier et récupérerait 
// sa référence. La ressource serait alors perdue une fois l'exécution 
// de la fonction terminée, renvoyant une référence d'une ressource qui n'existe plus.

fn main() {
    let x = 7;
    let y = 9;

    print_one(&x);
    print_multi(&x, &y);

    let z = pass_x(&x, &y);
    print_one(z);

    let mut t = 3;
    add_one(&mut t);
    print_one(&t);
}

Voir aussi

Les fonctions.

13-4-3. Les méthodes

Les méthodes sont annotées de la même manière que les fonctions :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
struct Owner(i32);

impl Owner {
    // On déclare et annote les lifetimes comme 
    // dans une fonction traditionnelle.
    fn add_one<'a>(&'a mut self) { self.0 += 1; }
    fn print<'a>(&'a self) {
        println!("`print`: {}", self.0);
    }
}

fn main() {
    let mut owner  = Owner(18);

    owner.add_one();
    owner.print();
}

Voir aussi

Les méthodes.

13-4-4. Les structures

La déclaration des labels pour les structures ne diffère pas beaucoup non plus de celle des fonctions :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
// On créé un type nommé `Borrowed` qui a pour attribut 
// une référence d'un entier codé sur 32 bits. La référence 
// doit survivre à l'instance de la structure `Borrowed`.
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);

// Même combat, ces deux références doivent survivre à l'instance
// (ou aux instances)  de la structure `NamedBorrowed`.
#[derive(Debug)]
struct NamedBorrowed<'a> {
    x: &'a i32,
    y: &'a i32,
}

// On créé une énumération qui contient deux variantes :
// 1. Un tuple qui prend en entrée un entier codé sur 32 bits;
// 2. Un tuple qui prend en entrée une référence d'un `i32`.
#[derive(Debug)]
enum Either<'a> {
    Num(i32),
    Ref(&'a i32),
}

fn main() {
    let x = 18;
    let y = 15;

    let single = Borrowed(&x);
    let double = NamedBorrowed { x: &x, y: &y };
    let reference = Either::Ref(&x);
    let number    = Either::Num(y);

    println!("x is borrowed in {:?}", single);
    println!("x and y are borrowed in {:?}", double);
    println!("x is borrowed in {:?}", reference);
    println!("y is *not* borrowed in {:?}", number);
}

Voir aussi

Les structures.

13-4-5. Les restrictions

Tout comme les types génériques, les lifetimes (elles-mêmes génériques) utilisent les restrictions. Ici, le caractère : a une signification quelque peu différente, mais + possède la même fonction. La déclaration se lit comme suit :

  1. T: 'a : Toutes les références dans le type T doivent, au moins, survivre à la lifetime 'a ;
  2. T: Trait + 'a : Le type T doit implémenter le trait Trait et toutes les références doivent survivre à la lifetime 'a.

L'exemple ci-dessous illustre les explications précédentes :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
use std::fmt::Debug; // On importe le trait `Debug`.
// Trait avec lequel nous allons filtrer les entrées 
// des fonctions génériques.

#[derive(Debug)]
struct Ref<'a, T: 'a>(&'a T);
// `Ref` contient la référence d'un type générique `T` qui possède 
// une lifetime inconnue nommée `'a`. Toutes les références contenues par 
// le type `T` sont contraintes à survivre à la lifetime `'a`. De plus, 
// la durée de vie de `Ref` ne peut pas excéder la lifetime `'a`.

// Une fonction générique qui affiche le résultat de l'utilisation 
// du trait `Debug`.
fn print<T>(t: T) where
    T: Debug {
    println!("`print`: t is {:?}", t);
}

// Ici, nous avons une référence de type `T` en entrée où 
// `T` implémente le trait `Debug` et toutes les références 
// contenues par le type `T` survivent à la lifetime `'a`. La lifetime 
// `'a` doit survivre à l'appel de la fonction.
fn print_ref<'a, T>(t: &'a T) where
    T: Debug + 'a {
    println!("`print_ref`: t is {:?}", t);
}

fn main() {
    let x = 7;
    let ref_x = Ref(&x);

    print_ref(&ref_x);
    print(ref_x);
}

Voir aussi

La généricité, les restrictions et les restrictions multiples.

13-4-6. La coercition

Une longue durée de vie peut être raccourcie afin d'opérer dans un contexte où elle ne fonctionnerait pas en temps normal. Cette opération peut être effectuée par inférence du compilateur ou en déclarant une différence de durée de vie(e.g. l'une est, au moins, aussi longue que l'autre, sinon plus).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
// Ici, Rust va inférer une durée de vie aussi courte que possible. 
// Les deux références sont alors assignées à cette durée de vie.
fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32 {
    first * second
}

// `<'a: 'b, 'b>` se lit comme suit: La lifetime `'a` est au moins aussi longue 
// que `'b`. Ici, nous prenons en entrée un entier soumis à la lifetime 
// `&'a i32` et renvoyons un entier (le même entier, en fait) soumis à la lifetime 
// `&'b i32` comme résultat de la coercition.
// Note: Nous pouvons renvoyer l'entier taggé avec la lifetime `'b` car `'a` et `'b`
// ont une durée de vie aussi longue, pour le temps de l'exécution de la fonction, 
// tout du moins.
fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
    first
}

fn main() {
    let first = 2; // Longue lifetime.

    {
        let second = 3; // Courte lifetime.

        // Note: Ici `first` et `second` possèdent la même durée de vie.
        println!("The product is {}", multiply(&first, &second));
        println!("{} is the first", choose_first(&first, &second));
    };
}

13-4-7. La lifetime 'static

Une ressource annotée du label 'static possède la plus longue durée de vie qu'on puisse assigner. La durée de vie de 'static est égale à celle du programme lui-même et peut également être raccourcie selon le contexte.

Il y a deux façons d'assigner la lifetime 'static, et toutes deux stockeront la ressource directement dans le binaire en lecture seule :

  1. Créer une constante avec la déclaration static ;
  2. Créer une chaîne de caractères avec le "littéral" en l'annotant du type &'static str.

Voici un exemple pour illustrer les deux manières de faire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
// On créé une constante avec la lifetime `'static` en utilisant 
// le mot-clé `static`.
static NUM: i32 = 18;

// Renvoie une référence de `NUM` où sa lifetime `'static` 
// est obligée de s'aligner avec la durée de vie du paramètre 
// passé à la fonction.
fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
    &NUM
}

fn main() {
    {
        // On créé une chaîne de caractères littérale, primitive 
        // et on l'affiche.
        let static_string = "I'm in read-only memory";
        println!("static_string: {}", static_string);

        // Lorsque `static_string` sortira du contexte, la référence 
        // ne pourra plus être utilisée, mais la ressource restera 
        // présente dans le binaire.
    }

    {
        // On créé un entier à passer à la 
        // fonction `coerce_static`:
        let lifetime_num = 9;

        // On aligne, adapte la durée de vie de `NUM à 
        // celle de `lifetime_num`:
        let coerced_static = coerce_static(&lifetime_num);

        println!("coerced_static: {}", coerced_static);
    }

    println!("NUM: {} stays accessible!", NUM);
}

Voir aussi

Les constantes.

13-4-8. Annotation implicite

En règle générale, décrire explicitement la durée de vie d'une référence n'est pas nécessaire et nous préférerons passer la main au compilateur, qui se chargera de nous épargner l'écriture des annotations et d'améliorer la lisibilité du code. Ce processus d'annotation implicite se nomme l'élision. Il s'agit ici d'omettre volontairement les annotations pour que le compilateur le fasse à notre place. L'élision ne peut être appliquée que lorsqu'un pattern de durée de vie est commun, simple à deviner.

Le code source qui suit présente quelques exemples où nous avons volontairement omis les annotations. Pour une description plus exhaustive du concept d'élision, n'hésitez pas à consulter la section lifetime elision du livre.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
// `elided_input` et `annotated_input` ont fondamentalement la même signature, 
// sauf que la lifetime de l'entrée de la fonction `elided_input` a été omise 
// et ajoutée par le compilateur.
fn elided_input(x: &i32) {
    println!("`elided_input`: {}", x)
}

fn annotated_input<'a>(x: &'a i32) {
    println!("`annotated_input`: {}", x)
}

// Même combat, `elided_pass` et `annotated_pass` possèdent la même signature 
// sauf que la lifetime de `x`, pour la fonction `elided_pass`, a été omise 
// et ajoutée par le compilateur. Annotation implicite.
fn elided_pass(x: &i32) -> &i32 { x }

fn annotated_pass<'a>(x: &'a i32) -> &'a i32 { x }

fn main() {
    let x = 3;

    elided_input(&x);
    annotated_input(&x);

    println!("`elided_pass`: {}", elided_pass(&x));
    println!("`annotated_pass`: {}", annotated_pass(&x));
}

Voir aussi

Le chapitre du livre sur l'élision.


précédentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Rust Core Team et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.