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);
}
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);
}
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 decreate()
tiene la vida útil deVecRef<''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
ofn 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:
-
¿Qué vidas son válidas para
''y
? -
¿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 ()
, osi 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!