3 votes

Un problème de durée de vie surgit après un changement apparemment sans effet

#![allow(dead_code)]
#![allow(unused_variables)]

use std::cell::Cell;

pub struct Foo<'a> {
    pub x1: &'a i32,
    pub x2: &'a i32,
    pub data: Cell<&'a i32>,
}

fn test<'a>(foo: &Foo<'a>) {
    let x1 = 1;
    let data = Cell::new(foo.data.get());
    // UNCOMMENT THE NEXT LINE
    // let data = foo.data.clone();
    Foo {
        x1: &x1,
        x2: foo.x2,
        data,
    };
}

Le code ci-dessus se compile, mais si vous décommentez la ligne que j'ai marquée dans le code (et éventuellement commentez la ligne qui la précède), vous verrez l'erreur suivante (j'utilise Rust 1.58.1) :

error[E0597]: `x1` does not live long enough
  --> src/lib.rs:18:13
   |
12 | fn test<'a>(foo: &Foo<'a>) {
   |         -- lifetime `'a` defined here
...
18 |         x1: &x1,
   |             ^^^
   |             |
   |             borrowed value does not live long enough
   |             this usage requires that `x1` is borrowed for `'a`
...
22 | }
   | - `x1` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.

La ligne qui provoque l'erreur lorsqu'elle est décommentée fortement semble avoir le même effet que la ligne qui la précède - j'ai essentiellement intégré l'implémentation du clone ; citation de la source stdlib de la rouille :

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: Copy> Clone for Cell<T> {
    #[inline]
    fn clone(&self) -> Cell<T> {
        Cell::new(self.get())
    }
}

Question : d'où vient la différence ?

5voto

Kevin Reid Points 8806

Lorsque vous écrivez

    let data = Cell::new(foo.data.get());

la valeur foo.data.get() a le type &'a i32 . Cette référence peut alors être implicitement réinterprétée comme ayant une durée de vie quelconque pas plus de 'a . En particulier, nous pouvons la considérer comme la durée de vie que j'appellerai 'f , d'une durée de vie réellement innommable qui est dépassée par 'a et le champ d'application de l'accord local x1 . Nous appelons alors Cell::new pour construire un Foo<'f> .

En revanche, si data est un clone de data de Foo alors data doit avoir le type exact Cell<&'a i32> et parce que Cell est mutable à l'intérieur, son paramètre de type est invariant , de sorte que le Cell<&'a i32> ne peut pas être réinterprétée comme contenant une référence ayant une durée de vie plus courte, et donc l'élément Foo doit être Foo<'a> - mais x ne survit pas 'a así que &x1 ne peut pas être stocké dans le Foo<'a> .


Vous écrivez que vous avez "intégré l'implémentation du clone", et vous vous demandez pourquoi cela fait une différence ; les principales différences sont que la version intégrée

  1. es conscient qu'il a affaire à une référence partagée plutôt qu'un T en Cell<T> (pour qu'il puisse utiliser le raccourcissement/emprunt/covariance), et
  2. n'oblige pas l'entrée et la sortie à être du même type (y compris les durées de vie).

Pour illustrer cela, voici une version réécrite avec deux fonctions aux corps identiques, mais aux signatures différentes. Cette version se compile, mais si vous remplacez clone_cell para rebuild_cell il ne sera pas compilé.

use std::cell::Cell;

// Type inside the Cell is opaque and will remain unchanged.
fn clone_cell<T: Copy>(cell: &Cell<T>) -> Cell<T> {
    Cell::new(cell.get())
}

// Input and output lifetimes are separated, and have a relationship
// but are not necessarily equal.
fn rebuild_cell<'input, 'output>(cell: &Cell<&'input i32>) -> Cell<&'output i32>
where
    'input: 'output
{
    // When the data flows from cell.get() to Cell::new(), the compiler 
    // notices that the shared reference can be coerced from &'input i32
    // to &'output i32. Thus, 'output is allowed to be shorter, to satisfy
    // any other lifetime requirement imposed on the output cell.
    Cell::new(cell.get())
}

fn test<'a>(foo: &[Cell<&'a i32>; 2]) {
    let x1 = 1;
    let _ = [rebuild_cell(&foo[0]), Cell::new(&x1)];
}

Même dans les utilisations courantes de la bibliothèque standard, Clone::clone n'a pas toujours la signature souhaitée ; par exemple, la fonction ToOwned existe pour permettre un changement de type. Malheureusement, il n'y a pas de moyen général d'appliquer cette technique à des problèmes comme celui-ci, puisqu'il n'y a aucun moyen d'exprimer "permettre aux durées de vie n'importe où dans ce type de devenir flexibles" en tant que signature de trait.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X