¿Cuál es la razón por la que el siguiente código se compila bien, a pesar de que las vidas 'a y 'b son independientes entre sí?

struct Foo<'a> {
    i: &'a i32
}

fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

fn main() {}

Si hago la referencia i en Foo mutable, da el siguiente error.

5 | fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 {
  |                    -----------     -------
  |                    |
  |                    this parameter and the return type are declared with different lifetimes...
6 |     x.i
  |     ^^^ ...but data from `x` is returned here

¿Cuál es la razón por la que da el error anterior? ¿Considera que es propiedad sobre una referencia mutable y ve que algo (de Foo) se está eliminando (con una vida útil independiente), lo que no es posible, de ahí el error?

Este código (que pensé que pasaría) también falla:

struct Foo<'a> {
    i: &'a mut i32
}

fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

fn main() {}

Falla con error:

 error[E0623]: lifetime mismatch
 --> src/main.rs:6:5
  |
5 | fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
  |                        -----------
  |                        |
  |                        these two types are declared with different lifetimes...
6 |     x.i
  |     ^^^ ...but data from `x` flows into `x` here

Pero este pasa:

struct Foo<'a> {
    i: &'a mut i32
}

fn func<'a: 'b, 'b>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

fn main() {}

Esto me parece un poco intuitivo. Aquí, la vida útil externa ('a) puede sobrevivir a la vida útil interna ('b). ¿Por qué esto no es un error?

4
soupybionics 8 sep. 2018 a las 23:17

3 respuestas

La mejor respuesta

¿Cuál es la razón por la que da el error anterior? ¿Considera que es propiedad sobre la referencia mutable y ve que algo (de Foo) se está sacando (con una vida útil independiente), que no es posible, de ahí el error?

El préstamo mutable no se puede sacar de Foo ya que los préstamos mutables no son Copy. Está implícitamente inmutablemente mañana:

fn func <'a, 'b> (x:&'a Foo<'b>) -> &'b i32 {
    &*x.i
}

Sin embargo, esta no es la fuente del problema. Primero, aquí hay un resumen de las cuatro versiones de func (la estructura Foo no tiene relevancia en mi explicación):

Versión 1 (compila):

fn func<'a, 'b>(x: &'a &'b i32) -> &'b i32 {
    x
}

Versión 2 (no se compila):

fn func<'a, 'b>(x: &'a &'b mut i32) -> &'b i32 {
    x
}

Versión 3 (no se compila):

fn func<'a, 'b: 'a>(x: &'a &'b mut i32) -> &'b i32 {
    x
}

Versión 4 (compila):

fn func<'a: 'b, 'b>(x: &'a &'b mut i32) -> &'b i32 {
    x
}

Las versiones 2 y 3 fallan porque violan la regla de no alias que prohíbe tener una referencia mutable y una referencia inmutable a un recurso al mismo tiempo. En ambas versiones, 'b puede estrictamente sobrevivir a 'a. Por lo tanto, &'b mut i32 y &'b i32 podrían coexistir. La versión 1 se compila, porque las reglas de alias permiten múltiples referencias inmutables a un recurso al mismo tiempo. Por lo tanto, &'b i32 puede coexistir legalmente con otro &'b i32.

A primera vista, parece que la versión 4 debería fallar ya que nuevamente hay un préstamo mutable y un préstamo inmutable de la misma vida útil. La diferencia con las versiones 2 y 3 es que esta vez 'a vive al menos tanto tiempo como 'b debido al requisito 'a: 'b, lo que implica que 'b puede no estrictamente más allá de 'a. Mientras dure la vida 'a, el i32 referenciado no se puede pedir prestado de forma mutable por segunda vez (las referencias mutables no son Copy) - el i32 ya está prestado de forma mutable para el {{ X9}} llamada.

Aquí hay un ejemplo que demuestra cómo las versiones 2 y 3 podrían conducir a un comportamiento indefinido:

fn func<'a, 'b: 'a>(x: &'a &'b mut String) -> &'b str {
    unsafe { std::mem::transmute(&**x as &str) } // force compilation
}

fn main() {
    let mut s = String::from("s");
    let mutref_s = &mut s;
    let ref_s = {
        let ref_mutref_s = &mutref_s;
        func(ref_mutref_s)
    };
    // use the mutable reference to invalidate the string slice       
    mutref_s.clear();
    mutref_s.shrink_to_fit();
    // use the invalidated string slice
    println!("{:?}", ref_s);
}

El intercambio de la versión 3 con la versión 4 muestra cómo, en este caso, el préstamo externo inmutable aún activo evita el segundo préstamo mutable. El nuevo requisito 'a: 'b obliga a ampliar el tiempo de vida 'a en el préstamo externo inmutable para que sea igual al tiempo de vida 'b:

error[E0502]: cannot borrow `*mutref_s` as mutable because `mutref_s` is also borrowed as immutable
  --> src/main.rs:20:5
   |
17 |         let ref_mutref_s = &mutref_s;
   |                             -------- immutable borrow occurs here
...
20 |     mutref_s.clear();
   |     ^^^^^^^^ mutable borrow occurs here
...
23 | }
   | - immutable borrow ends here
3
Calculator 10 sep. 2018 a las 14:51

Solo para agregar una sugerencia (consulte las otras respuestas para obtener una explicación detallada de su pregunta):

Siempre que sea posible no sobrediseñe las vidas.

En este caso, ser explícito sobre todas las vidas implica 4 casos (¡y mucho pensar!).

Caso 1 (compilar)

fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'a i32 {
    x.i
}

Caso 2 (compilar)

fn func<'a: 'b, 'b>(x: &'a Foo<'b>) -> &'a i32 {
    x.i
}

Caso 3 (compilar)

fn func<'a: 'b, 'b>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

Caso 4 (no compilar)

fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

Si usa vidas anónimas y deja que el compilador cree las dependencias entre las regiones de por vida, es tan simple como:

fn func<'a>(x: &'a Foo) -> &'a i32 {
    x.i
}
0
attdona 10 sep. 2018 a las 14:58

¿Cuál es la razón por la que el siguiente código se compila bien, a pesar de que las vidas 'a y 'b son independientes entre sí?

fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

La razón es que son no independientes entre sí.

El tipo &'a Foo<'b> sería imposible si 'a sobreviviera a 'b. Por lo tanto, implícitamente, el verificador de préstamos Rust infiere que debe haber pensado que 'b: 'a ('b sobrevive 'a). Entonces, el código anterior es semánticamente el mismo que este:

fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

Si hago la referencia i en Foo mutable, da el siguiente error.

5 | fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 {
  |                    -----------     -------
  |                    |
  |                    this parameter and the return type are declared with different lifetimes...
6 |     x.i
  |     ^^^ ...but data from `x` is returned here

¿Cuál es la razón por la que da el error anterior?

Cuando pasa una referencia inmutable , se copia . En el ejemplo anterior, eso significa que el &'b i32 se puede mover solo, y su vida no está ligada a dónde lo obtuvo. Esta referencia copiada siempre apunta a la dirección original de los datos. Y es por eso que el primer ejemplo funciona: incluso si se eliminó x, la referencia original seguiría siendo válida.

Cuando pasa una referencia mutable , se mueve . Una consecuencia de esto es que la referencia todavía está "a través" de la variable x. Si este no fuera el caso, la referencia mutable de x al contenido de Foo podría estar activa al mismo tiempo que esta nueva referencia inmutable, lo cual no está permitido. Entonces, en este caso, la referencia no puede sobrevivir a 'a, o dicho de otra manera: 'a: 'b.

¿Pero ya no dijimos 'b: 'a? La única conclusión aquí es que 'a y 'b deben ser la misma vida útil , que es lo que exigía su mensaje de error anterior.

4
Peter Hall 9 sep. 2018 a las 18:18