web-dev-qa-db-fra.com

Quelle est la bonne façon d'utiliser des durées de vie avec une structure dans Rust?

Je veux écrire cette structure:

struct A {
    b: B,
    c: C,
}

struct B {
    c: &C,
}

struct C;

Le B.c doit être emprunté à A.c.

A ->
  b: B ->
    c: &C -- borrow from --+
                           |
  c: C  <------------------+

C'est ce que j'ai essayé: struct C;

struct B<'b> {
    c: &'b C,
}

struct A<'a> {
    b: B<'a>,
    c: C,
}

impl<'a> A<'a> {
    fn new<'b>() -> A<'b> {
        let c = C;
        A {
            c: c,
            b: B { c: &c },
        }
    }
}

fn main() {}

Mais cela échoue:

error[E0597]: `c` does not live long enough
  --> src/main.rs:17:24
   |
17 |             b: B { c: &c },
   |                        ^ borrowed value does not live long enough
18 |         }
19 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'b as defined on the method body at 13:5...
  --> src/main.rs:13:5
   |
13 |     fn new<'b>() -> A<'b> {
   |     ^^^^^^^^^^^^^^^^^^^^^

error[E0382]: use of moved value: `c`
  --> src/main.rs:17:24
   |
16 |             c: c,
   |                - value moved here
17 |             b: B { c: &c },
   |                        ^ value used here after move
   |
   = note: move occurs because `c` has type `C`, which does not implement the `Copy` trait

J'ai lu la documentation Rust sur la propriété, mais je ne sais toujours pas comment y remédier.

27
Quan Brew

Il y a en fait plus d'une raison pour laquelle le code ci-dessus échoue. Décomposons-le un peu et explorons quelques options sur la façon de le réparer.

Supprimons d'abord le new et essayons de construire une instance de A directement dans main, de sorte que vous voyez que la première partie du problème n'a rien à voir avec les durées de vie:

struct C;

struct B<'b> {
    c: &'b C,
}

struct A<'a> {
    b: B<'a>,
    c: C,
}

fn main() {
    // I copied your new directly here
    // and renamed c1 so we know what "c"
    // the errors refer to
    let c1 = C;

    let _ = A {
        c: c1,
        b: B { c: &c1 },
    };
}

cela échoue avec:

error[E0382]: use of moved value: `c1`
  --> src/main.rs:20:20
   |
19 |         c: c1,
   |            -- value moved here
20 |         b: B { c: &c1 },
   |                    ^^ value used here after move
   |
   = note: move occurs because `c1` has type `C`, which does not implement the `Copy` trait

ce qu'il dit, c'est que si vous attribuez c1 à c, vous déplacez sa propriété vers c (c'est-à-dire que vous ne pouvez plus y accéder via c1, uniquement via c). Cela signifie que toutes les références à c1 ne serait plus valide. Mais vous avez un &c1 toujours dans la portée (en B), donc le compilateur ne peut pas vous laisser compiler ce code.

Le compilateur indique une solution possible dans le message d'erreur lorsqu'il dit que le type C n'est pas copiable. Si vous pouviez faire une copie d'un C, votre code serait alors valide, car l'attribution de c1 à c créerait une nouvelle copie de la valeur au lieu de déplacer la propriété de la copie d'origine.

Nous pouvons rendre C copiable en changeant sa définition comme ceci:

#[derive(Copy, Clone)]
struct C;

Maintenant, le code ci-dessus fonctionne. Notez que ce que @ commentaires matthieu-m est toujours vrai: nous ne pouvons pas stocker à la fois la référence à une valeur et la valeur elle-même dans B (nous stockons une référence à une valeur et une COPIE de la valeur ici). Ce n'est pas seulement pour les structures, cependant, c'est comment fonctionne la propriété.

Maintenant, si vous ne voulez pas (ou ne pouvez pas) rendre C copiable, vous pouvez stocker des références à la fois dans A et B.

struct C;

struct B<'b> {
    c: &'b C,
}

struct A<'a> {
    b: B<'a>,
    c: &'a C, // now this is a reference too
}

fn main() {
    let c1 = C;
    let _ = A {
        c: &c1,
        b: B { c: &c1 },
    };
}

Tout va bien alors? Pas vraiment ... nous voulons toujours déplacer la création de A dans une méthode new. Et c'est là que nous aurons des ennuis avec la vie. Déplaçons la création de A dans une méthode:

impl<'a> A<'a> {
    fn new() -> A<'a> {
        let c1 = C;
        A {
            c: &c1,
            b: B { c: &c1 },
        }
    }
}

comme prévu, voici notre erreur de durée de vie:

error[E0597]: `c1` does not live long enough
  --> src/main.rs:17:17
   |
17 |             c: &c1,
   |                 ^^ borrowed value does not live long enough
...
20 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 13:1...
  --> src/main.rs:13:1
   |
13 | impl<'a> A<'a> {
   | ^^^^^^^^^^^^^^

error[E0597]: `c1` does not live long enough
  --> src/main.rs:18:24
   |
18 |             b: B { c: &c1 },
   |                        ^^ borrowed value does not live long enough
19 |         }
20 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 13:1...
  --> src/main.rs:13:1
   |
13 | impl<'a> A<'a> {
   | ^^^^^^^^^^^^^^

ceci est dû au fait c1 est détruit à la fin de la méthode new, nous ne pouvons donc pas lui renvoyer une référence.

fn new() -> A<'a> {
    let c1 = C; // we create c1 here
    A {
        c: &c1,          // ...take a reference to it
        b: B { c: &c1 }, // ...and another
    }
} // and destroy c1 here (so we can't return A with a reference to c1)

Une solution possible consiste à créer C en dehors de new et à le passer en paramètre:

struct C;

struct B<'b> {
    c: &'b C,
}

struct A<'a> {
    b: B<'a>,
    c: &'a C
}

fn main() {
    let c1 = C;
    let _ = A::new(&c1);
}

impl<'a> A<'a> {
    fn new(c: &'a C) -> A<'a> {
        A {c: c, b: B{c: c}}
    }
}

aire de jeux

36
Paolo Falabella

Après avoir vérifié avec Manishearth et eddyb sur l'IRC #Rust, je pense qu'il n'est pas possible pour une structure de stocker une référence à elle-même ou à une partie d'elle-même. Ce que vous essayez de faire n'est donc pas possible dans le système de type de Rust.

4
Rufflewind