rust

rust - ¿Por qué la vinculación de vidas solo importa con referencias mutables?



(2)

Hace unos días, había una pregunta en la que alguien tenía un problema con vidas vinculadas de una referencia mutable a un tipo que contenía datos prestados. El problema fue proporcionar una referencia al tipo con un préstamo de la misma vida útil que los datos prestados dentro del tipo. Traté de recrear el problema:

struct VecRef<''a>(&''a Vec<u8>); struct VecRefRef<''a>(&''a mut VecRef<''a>); fn main() { let v = vec![8u8, 9, 10]; let mut ref_v = VecRef(&v); create(&mut ref_v); } fn create<''b, ''a>(r: &''b mut VecRef<''a>) { VecRefRef(r); }

Código de ejemplo

Anoté explícitamente ''b aquí en create() . Esto no compila:

error[E0623]: lifetime mismatch --> src/main.rs:12:15 | 11 | fn create<''b, ''a>(r: &''b mut VecRef<''a>) { | ------------------ | | | these two types are declared with different lifetimes... 12 | VecRefRef(r); | ^ ...but data from `r` flows into `r` here

La vida útil ''b es algo así como ''b < ''a y, por lo tanto, viola la restricción en VecRefRef<''a> para ser exactamente la misma vida útil que la referida a VecRef<''a> .

VecRef<''a> la vida útil de la referencia mutable con los datos prestados dentro de VecRef<''a> :

fn create<''a>(r: &''a mut VecRef<''a>) { VecRefRef(r); }

Ahora funciona. ¿Pero por qué? ¿Cómo pude proporcionar tal referencia? La referencia mutable r dentro de create() tiene la vida útil de VecRef<''a> not ''a . ¿Por qué el problema no fue llevado al lado de la función create() ?

Noté otra cosa que no entendí. Si uso una referencia inmutable dentro de la VecRefRef<''a> , de alguna manera ya no importa al proporcionar una referencia con una vida útil diferente de ''a :

struct VecRef<''a>(&''a Vec<u8>); struct VecRefRef<''a>(&''a VecRef<''a>); // now an immutable reference fn main() { let v = vec![8u8, 9, 10]; let mut ref_v = VecRef(&v); create(&mut ref_v); } fn create<''b, ''a>(r: &''b mut VecRef<''a>) { VecRefRef(r); }

Código de ejemplo

Esto funciona en oposición al primer ejemplo donde VecRefRef<''a> tomó una referencia mutable a VecRef<''a> . Sé que las referencias mutables tienen diferentes reglas de alias (sin alias), pero ¿qué tiene eso que ver con las vidas vinculadas aquí?


La referencia mutable r dentro de create() tiene la vida útil de VecRef<''a> not ''a

Esta es una fuente común de confusión. Verifique esta definición de función:

fn identity<''a, T>(val: &''a T) -> &''a T { val }

En una definición de función, ''a es un parámetro genérico de por vida, que es paralelo a un parámetro de tipo genérico ( T ). Cuando se llama a la función, la persona que llama decide cuáles serán los valores concretos de ''a y T ''a . Echemos un vistazo a su main :

fn main() { let v = vec![8u8, 9, 10]; // 1 |-lifetime of `v` let mut ref_v = VecRef(&v); // 2 | |-lifetime of `ref_v` create(&mut ref_v); // 3 | | }

v vivirá durante toda la ejecución de main (1-3), pero ref_v solo vive para las dos declaraciones finales (2-3). Tenga en cuenta que ref_v refiere a un valor que lo sobrevive. Si luego toma una referencia a ref_v , tiene una referencia a algo que vive de (2-3) que en sí tiene una referencia a algo que vive de (1-3).

Mira tu método fijo:

fn create<''a>(r: &''a mut VecRef<''a>)

Esto dice que para esta llamada de función , la referencia a VecRef y la referencia que contiene deben ser las mismas. Hay una vida útil que se puede elegir que satisface esto: (2-3).

Tenga en cuenta que su definición de estructura actualmente requiere que las dos vidas sean las mismas. Podrías permitir que difieran:

struct VecRefRef<''a, ''b: ''a>(&''a mut VecRef<''b>); fn create<''a, ''b>(r: &''a mut VecRef<''b>)

Tenga en cuenta que debe usar la sintaxis ''b: ''a para denotar que la vida útil ''b sobrevivirá ''a .

Si uso una referencia inmutable [...], de alguna manera ya no importa

De esto estoy menos seguro. Creo que lo que está sucediendo es que debido a que tiene un préstamo inmutable, está bien que el compilador vuelva a tener un alcance menor automáticamente. Esto permite que las vidas coincidan. Como señaló, una referencia mutable no puede tener ningún alias, incluso aquellos con un alcance más pequeño, por lo que el compilador no puede ayudar en ese caso.


Advertencia: estoy hablando desde un nivel de experiencia que realmente no tengo. Dada la extensión de esta publicación, probablemente me equivoque muchas veces.

TL; DR: los tiempos de vida de los valores de nivel superior son contravariantes. Las vidas de los valores referenciados son invariables.

Introduciendo el problema

Puede simplificar su ejemplo significativamente, reemplazando VecRef<''a> con &''a mut T

Además, uno debe eliminar main , ya que es más completo hablar sobre el comportamiento general de una función que una instanciación de vida particular.

En lugar del VecRefRef de VecRefRef , usemos esta función:

fn use_same_ref_ref<''c>(reference: &''c mut &''c mut ()) {}

Antes de continuar, es importante comprender cómo las vidas se vuelven implícitamente en Rust. Cuando uno asigna un puntero a otro nombre explícitamente anotado, ocurre una coerción de por vida. Lo más obvio que esto permite es reducir la vida útil del puntero de nivel superior. Como tal, este no es un movimiento típico.

Aparte: digo "explícitamente anotado" porque en casos implícitos como let x = y o fn f<T>(_: T) {} , parece que no ocurre el mañana . No está claro si esto se pretende.

El ejemplo completo es entonces

fn use_same_ref_ref<''c>(reference: &''c mut &''c mut ()) {} fn use_ref_ref<''a, ''b>(reference: &''a mut &''b mut ()) { use_same_ref_ref(reference); }

que da el mismo error:

error[E0623]: lifetime mismatch --> src/main.rs:5:26 | 4 | fn use_ref_ref<''a, ''b>(reference: &''a mut &''b mut ()) { | ------------------ | | | these two types are declared with different lifetimes... 5 | use_same_ref_ref(reference); | ^^^^^^^^^ ...but data from `reference` flows into `reference` here

Una solución trivial

Uno puede arreglarlo haciendo

fn use_same_ref_ref<''c>(reference: &''c mut &''c mut ()) {} fn use_ref_ref<''a>(reference: &''a mut &''a mut ()) { use_same_ref_ref(reference); }

ya que las firmas ahora son lógicamente las mismas. Sin embargo, lo que no es obvio es por qué

let mut val = (); let mut reference = &mut val; let ref_ref = &mut reference; use_ref_ref(ref_ref);

es capaz de producir un &''a mut &''a mut () .

Una solución menos trivial

En su lugar, se puede aplicar ''a: ''b

fn use_same_ref_ref<''c>(reference: &''c mut &''c mut ()) {} fn use_ref_ref<''a: ''b, ''b>(reference: &''a mut &''b mut ()) { use_same_ref_ref(reference); }

Esto significa que la vida útil de la referencia externa es al menos tan grande como la vida útil de la referencia interna.

No es obvio

  • por qué &''a mut &''b mut () no se puede convertir a &''c mut &''c mut () , o

  • si esto es mejor que &''a mut &''a mut () .

Espero responder estas preguntas.

Un no arreglo

Afirmar ''b: ''a no soluciona el problema.

fn use_same_ref_ref<''c>(reference: &''c mut &''c mut ()) {} fn use_ref_ref<''a, ''b: ''a>(reference: &''a mut &''b mut ()) { use_same_ref_ref(reference); }

Otra solución más sorprendente

Hacer que la referencia externa sea inmutable corrige el problema

fn use_same_ref_ref<''c>(reference: &''c &''c mut ()) {} fn use_ref_ref<''a, ''b>(reference: &''a &''b mut ()) { use_same_ref_ref(reference); }

¡Y una solución aún más sorprendente!

¡Hacer que la referencia interna sea inmutable no ayuda en absoluto!

fn use_same_ref_ref<''c>(reference: &''c mut &''c ()) {} fn use_ref_ref<''a, ''b>(reference: &''a mut &''b ()) { use_same_ref_ref(reference); }

¡¿¿PERO POR QUÉ??!

Y la razón es ...

Espera, primero cubrimos la varianza

Dos conceptos muy importantes en informática son covarianza y contravarianza . No voy a usar estos nombres (voy a ser muy explícito sobre la forma en que estoy emitiendo las cosas), pero esos nombres siguen siendo muy útiles para buscar en Internet .

Es muy importante comprender el concepto de varianza antes de poder comprender el comportamiento aquí. Si ha tomado un curso universitario que cubre esto, o puede recordarlo desde otro contexto, está en una buena posición. Sin embargo, es posible que aún aprecies la ayuda que vincula la idea con las vidas.

El caso simple: un puntero normal

Considere algunas posiciones de pila con un puntero:

║ Name │ Type │ Value ───╫───────────┼─────────────────────┼─────── 1 ║ val │ i32 │ -1 ───╫───────────┼─────────────────────┼─────── 2 ║ reference │ &''x mut i32 │ 0x1

La pila crece hacia abajo, por lo que la posición de la pila de reference se creó después de val , y se eliminará antes de val .

Considera que lo haces

let new_ref = reference;

Llegar

║ Name │ Type │ Value ───╫───────────┼─────────────┼─────── 1 ║ val │ i32 │ -1 ───╫───────────┼─────────────┼─────── 2 ║ reference │ &''x mut i32 │ 0x1 ───╫───────────┼─────────────┼─────── 3 ║ new_ref │ &''y mut i32 │ 0x1

¿Qué vidas son válidas para ''y ?

Considere las dos operaciones de puntero mutable:

  • Leer
  • Escribir

Leer evita que ''y crezca, porque una referencia ''x solo garantiza que el objeto permanezca vivo durante el alcance de ''x . Sin embargo, la lectura no evita que ''y reduzca, ya que cualquier lectura cuando el valor señalado esté vivo dará como resultado un valor independiente de la vida útil ''y .

La escritura también evita que crezca, ya que no se puede escribir en un puntero invalidado. Sin embargo, la escritura no evita que ''y reduzca, ya que cualquier escritura en el puntero copia el valor en, lo que lo deja invariable durante toda la vida ''y .

El caso difícil: un puntero puntero

Considere algunas posiciones de pila con un puntero puntero:

║ Name │ Type │ Value ───╫───────────┼─────────────────────┼─────── 1 ║ val │ i32 │ -1 ───╫───────────┼─────────────────────┼─────── 2 ║ reference │ &''a mut i32 │ 0x1 ───╫───────────┼─────────────────────┼─────── 3 ║ ref_ref │ &''x mut &''a mut i32 │ 0x2

Considera que lo haces

let new_ref_ref = ref_ref;

Llegar

║ Name │ Type │ Value ───╫─────────────┼─────────────────────┼─────── 1 ║ val │ i32 │ -1 ───╫─────────────┼─────────────────────┼─────── 2 ║ reference │ &''a mut i32 │ 0x1 ───╫─────────────┼─────────────────────┼─────── 3 ║ ref_ref │ &''x mut &''a mut i32 │ 0x2 ───╫─────────────┼─────────────────────┼─────── 4 ║ new_ref_ref │ &''y mut &''b mut i32 │ 0x2

Ahora hay dos preguntas:

  1. ¿Qué vidas son válidas para ''y ?

  2. ¿Qué vidas son válidas para ''b ?

Primero consideremos y con las dos operaciones de puntero mutable:

  • Leer
  • Escribir

Leer evita que ''y crezca, porque una referencia ''x solo garantiza que el objeto permanezca vivo durante el alcance de ''x . Sin embargo, la lectura no evita que ''y reduzca, ya que cualquier lectura cuando el valor señalado esté vivo dará como resultado un valor independiente de la vida útil ''y .

La escritura también evita que crezca, ya que no se puede escribir en un puntero invalidado. Sin embargo, la escritura no evita que ''y reduzca, ya que cualquier escritura en el puntero copia el valor en, lo que lo deja invariable durante toda la vida ''y .

Esto es lo mismo que antes.

Ahora, considere ''b con las dos operaciones de puntero mutable

Leer evita que ''b crezca, ya que si uno extrajera el puntero interno del externo, podría leerlo después de que ''a haya expirado.

La escritura también evita que ''b crezca, ya que si uno extrajera el puntero interno del externo, podría escribirle después de que ''a haya expirado.

Leer y escribir juntos también evita que ''b reduzca, debido a este escenario:

let ref_ref: &''x mut &''a mut i32 = ...; { // Has lifetime ''b, which is smaller than ''a let new_val: i32 = 123; // Shrink ''a to ''b let new_ref_ref: &''x mut &''b mut i32 = ref_ref; *new_ref_ref = &mut new_val; } // new_ref_ref is out of scope, so ref_ref is usable again let ref_ref: &''a mut i32 = *ref_ref; // Oops, we have an &''a mut i32 pointer to a dropped value!

Ergo, ''b no puede reducirse y no puede crecer desde ''a , entonces ''a == ''b exactamente.

OK, ¿esto resuelve nuestras preguntas?

¿Recuerdas el código?

fn use_same_ref_ref<''c>(reference: &''c mut &''c mut ()) {} fn use_ref_ref<''a, ''b>(reference: &''a mut &''b mut ()) { use_same_ref_ref(reference); }

Cuando llamas a use_same_ref_ref , se intenta lanzar

&''a mut &''b mut () → &''c mut &''c mut ()

Ahora tenga en cuenta que ''b == ''c debido a nuestra discusión sobre la varianza. Por lo tanto, en realidad estamos lanzando

&''a mut &''b mut () → &''b mut &''b mut ()

El exterior &''a solo puede ser reducido. Para hacer esto, el compilador necesita saber

''a: ''b

El compilador no lo sabe, por lo que falla la compilación.

¿Qué hay de nuestros otros ejemplos?

El primero fue

fn use_same_ref_ref<''c>(reference: &''c mut &''c mut ()) {} fn use_ref_ref<''a>(reference: &''a mut &''a mut ()) { use_same_ref_ref(reference); }

En lugar de ''a: ''b , el compilador ahora necesita ''a: ''a , que es trivialmente cierto.

El segundo afirmó directamente ''a: ''b

fn use_same_ref_ref<''c>(reference: &''c mut &''c mut ()) {} fn use_ref_ref<''a: ''b, ''b>(reference: &''a mut &''b mut ()) { use_same_ref_ref(reference); }

El tercero afirmó ''b: ''a

fn use_same_ref_ref<''c>(reference: &''c mut &''c mut ()) {} fn use_ref_ref<''a, ''b: ''a>(reference: &''a mut &''b mut ()) { use_same_ref_ref(reference); }

Esto no funciona, porque esta no es la afirmación necesaria.

¿Qué pasa con la inmutabilidad?

Tuvimos dos casos aquí. El primero fue hacer que la referencia externa sea inmutable.

fn use_same_ref_ref<''c>(reference: &''c &''c mut ()) {} fn use_ref_ref<''a, ''b>(reference: &''a &''b mut ()) { use_same_ref_ref(reference); }

Este funcionó. ¿Por qué?

Bueno, considere nuestro problema con la reducción de &''b de antes:

Leer y escribir juntos también evita que ''b reduzca, debido a este escenario:

let ref_ref: &''x mut &''a mut i32 = ...; { // Has lifetime ''b, which is smaller than ''a let new_val: i32 = 123; // Shrink ''a to ''b let new_ref_ref: &''x mut &''b mut i32 = ref_ref; *new_ref_ref = &mut new_val; } // new_ref_ref is out of scope, so ref_ref is usable again let ref_ref: &''a mut i32 = *ref_ref; // Oops, we have an &''a mut i32 pointer to a dropped value!

Ergo, ''b no puede reducirse y no puede crecer desde ''a , entonces ''a == ''b exactamente.

Esto solo puede suceder porque podemos intercambiar la referencia interna por alguna referencia nueva, de duración insuficiente. Si no podemos intercambiar la referencia, esto no es un problema. Por lo tanto, es posible reducir la vida útil de la referencia interna.

¿Y el que falla?

Hacer que la referencia interna sea inmutable no ayuda:

fn use_same_ref_ref<''c>(reference: &''c mut &''c ()) {} fn use_ref_ref<''a, ''b>(reference: &''a mut &''b ()) { use_same_ref_ref(reference); }

Esto tiene sentido cuando considera que el problema mencionado anteriormente nunca involucra ninguna lectura de la referencia interna. De hecho, aquí está el código problemático modificado para demostrar que:

let ref_ref: &''x mut &''a i32 = ...; { // Has lifetime ''b, which is smaller than ''a let new_val: i32 = 123; // Shrink ''a to ''b let new_ref_ref: &''x mut &''b i32 = ref_ref; *new_ref_ref = &new_val; } // new_ref_ref is out of scope, so ref_ref is usable again let ref_ref: &''a i32 = *ref_ref; // Oops, we have an &''a i32 pointer to a dropped value!

Hubo otra pregunta

Ha pasado bastante tiempo, pero piensa en:

En su lugar, se puede aplicar ''a: ''b

fn use_same_ref_ref<''c>(reference: &''c mut &''c mut ()) {} fn use_ref_ref<''a: ''b, ''b>(reference: &''a mut &''b mut ()) { use_same_ref_ref(reference); }

Esto significa que la vida útil de la referencia externa es al menos tan grande como la vida útil de la referencia interna.

No es obvio

  • por qué &''a mut &''b mut () no se puede convertir a &''c mut &''c mut () , o

  • si esto es mejor que &''a mut &''a mut () .

Espero responder estas preguntas.

Hemos respondido la primera pregunta puntiaguda, pero ¿qué pasa con la segunda? ¿ ''a: ''b permite más que ''a == ''b ?

Considere alguna llamada con tipo &''x mut &''y mut () . Si ''x : ''y , se convertirá automáticamente en &''y mut &''y mut () . En cambio, si ''x == ''y , entonces ''x : ''y ya se mantiene! Por lo tanto, la diferencia solo es importante si desea devolver un tipo que contenga ''x a la persona que llama, quien es el único capaz de distinguir los dos. Como este no es el caso aquí, los dos son equivalentes.

Una cosa más

Si tú escribes

let mut val = (); let mut reference = &mut val; let ref_ref = &mut reference; use_ref_ref(ref_ref);

donde use_ref_ref está definido

fn use_ref_ref<''a: ''b, ''b>(reference: &''a mut &''b mut ()) { use_same_ref_ref(reference); }

¿Cómo puede el código hacer cumplir ''a: ''b ? ¡Parece en la inspección que lo contrario es cierto!

Bueno, recuerda eso

let reference = &mut val;

es capaz de reducir su vida útil, ya que es la vida útil externa en este momento. Por lo tanto, puede referirse a una vida útil menor que la vida real de val , ¡incluso cuando el puntero está fuera de esa vida!