rust - Anotación de por vida para el argumento de cierre
lifetime (2)
Me gustaría hacer la siguiente compilación de código:
struct Provider {}
impl Provider {
fn get_string<''a>(&''a self) -> &''a str { "this is a string" }
}
fn main() {
let provider = Provider{};
let mut vec: Vec<&str> = Vec::new();
// PROBLEM: how do I say that this reference s here
// needs to live as long as vec?
let fun = |s: &str| {
vec.push(s);
};
fun(provider.get_string());
}
Este es el error de compilación que obtengo:
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> src/main.rs:9:22
|
9 | let mut vec: Vec<&str> = Vec::new();
| ^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the block at 11:24...
--> src/main.rs:11:25
|
11| let fun = |s: &str| {
| ^
note: ...so that reference does not outlive borrowed content
--> src/main.rs:12:18
|
12| vec.push(s);
| ^
note: but, the lifetime must be valid for the block suffix following statement 2 at 13:6...
--> src/main.rs:13:7
|
13| };
| ^
note: ...so that variable is valid at time of its declaration
--> src/main.rs:11:9
|
11| let fun = |s: &str| {
| ^^^
Su código funciona bien si elimina todas las anotaciones de por vida y deja que la inferencia del compilador haga su trabajo:
struct Provider;
impl Provider {
fn get_string(&self) -> &str { "this is a string" }
}
fn main() {
let provider = Provider;
let mut vec = Vec::new();
let mut fun = |s| {
vec.push(s);
};
fun(provider.get_string());
}
En resumen, no hay forma de referirse explícitamente a la duración de una variable local, solo argumentos de funciones. El compilador sabe cómo hacerlo, sin embargo.
Si realmente lo necesita, puede crear una función para permitir anotar las vidas:
fn thing<''a>(provider: &''a Provider) -> Vec<&''a str> {
let mut vec: Vec<&''a str> = Vec::new();
{
let mut fun = |s: &''a str| vec.push(s);
fun(provider.get_string());
} // End mutable borrow of `vec`
vec
}
fn main() {
let provider = Provider;
thing(&provider);
}
¿Por qué las anotaciones originales impiden que las cosas funcionen?
Específicamente, es este bit:
let fun = |s: &str| {
vec.push(s);
};
Esto declara una nueva vida en el cierre. El uso de una sintaxis inventada (no se pueden declarar tiempos de vida en los argumentos de cierre ) sería equivalente a:
let fun = <''a> |s: &''a str| {
vec.push(s);
};
Por eso el compilador tiene el error:
la duración no puede sobrevivir al tiempo de vida n. ° 1 definido en [el bloqueo del cierre]
No hay conexión entre esa vida generada y la del Provider
. Salir de él permite al compilador insertar el tiempo de vida deseado pero innombrable.
Aquí hay una versión que compila:
use std::marker::PhantomData;
struct Provider<''a> {
_dummy: PhantomData<&''a ()>,
}
impl<''a> Provider<''a> {
fn get_string(&self) -> &''a str {
"this is a string"
}
}
fn f<''b>() {
let provider = Provider { _dummy: PhantomData };
let mut vec: Vec<&str> = Vec::new();
// PROBLEM: how do I say that this reference s here
// needs to live as long as vec?
let mut fun = |s: &''b str| { vec.push(s); };
fun(provider.get_string());
}
fn main() {
f()
}
Hice los siguientes cambios:
- Agregue una vida útil al
Provider
(agregué unPhantomData
, pero supongo que su proveedor ya posee algunos datos que proporcionará). - Actualice el método
get_string
para mostrar que devuelve algo con la duración del proveedor, no la duración de la entrada (es decir, según el parámetro de por vida delProvider
). - Agregue un nuevo parámetro de por vida
''b
a la función (que renombré af()
, ya quemain()
no puede tener uno), que utilizo para nombrar el tiempo de vida del parámetro de cierre.
El último es un poco confuso, ya que aparentemente simplemente agregar un nombre a una vida (sin agregar ninguna restricción) lo ha hecho funcionar.
Creo (pero me gustaría algo de documentación para esto) que esto se debe a la elisión de por vida. Un cierre es realmente una struct
oculta con un método fn call(&self, s: &str)
(en este caso). De acuerdo con las reglas de elisión de por vida , el parámetro s
gana la misma duración que &self
, que es el cierre en sí mismo. En este caso, el cierre se declara después de vec
, por lo que la vida útil es demasiado corta. La vida explícita significa que está desacoplada de la propia vida del cierre.