closures - tope - ¿Cómo puedo mover una variable capturada a un cierre dentro de un cierre?
como cambiar un cierre (2)
Esto es un poco sorprendente, pero no es un error.
flat_map
toma un FnMut
ya que necesita llamar al cierre varias veces. El código con move
en el cierre interno falla porque ese cierre se crea varias veces, una vez para cada número inner_numbers
. Si escribo los cierres en forma explícita (es decir, una estructura que almacena las capturas y una implementación de uno de los rasgos de cierre), su código se ve (un poco) como
struct OuterClosure {
seen: Vec<i32>
}
struct InnerClosure {
seen: Vec<i32>
}
impl FnMut(&Vec<i32>) -> iter::FilterMap<..., InnerClosure> for OuterClosure {
fn call_mut(&mut self, (inner_numbers,): &Vec<i32>) -> iter::FilterMap<..., InnerClosure> {
let inner = InnerClosure {
seen: self.seen // uh oh! a move out of a &mut pointer
};
inner_numbers.iter().filter_map(inner)
}
}
impl FnMut(&i32) -> Option<i32> for InnerClosure { ... }
Lo que hace que la ilegalidad sea más clara: intentar salir de la variable &mut OuterClosure
.
En teoría , basta con capturar una referencia mutable, ya que lo seen
solo se modifica (no se mueve) dentro del cierre. Sin embargo, las cosas son demasiado perezosas para que esto funcione ...
error: lifetime of `seen` is too short to guarantee its contents can be safely reborrowed
--> src/main.rs:9:45
|
9 | inner_numbers.iter().filter_map(|&number| {
| ^^^^^^^^^
|
note: `seen` would have to be valid for the method call at 7:20...
--> src/main.rs:7:21
|
7 | let a: Vec<_> = items.iter()
| _____________________^
8 | | .flat_map(|inner_numbers| {
9 | | inner_numbers.iter().filter_map(|&number| {
10| | if !seen.contains(&number) {
... |
17| | })
18| | .collect();
| |__________________^
note: ...but `seen` is only valid for the lifetime as defined on the body at 8:34
--> src/main.rs:8:35
|
8 | .flat_map(|inner_numbers| {
| ___________________________________^
9 | | inner_numbers.iter().filter_map(|&number| {
10| | if !seen.contains(&number) {
11| | seen.push(number);
... |
16| | })
17| | })
| |_________^
Eliminar los move
hace que las capturas de cierre funcionen como
struct OuterClosure<''a> {
seen: &''a mut Vec<i32>
}
struct InnerClosure<''a> {
seen: &''a mut Vec<i32>
}
impl<''a> FnMut(&Vec<i32>) -> iter::FilterMap<..., InnerClosure<??>> for OuterClosure<''a> {
fn call_mut<''b>(&''b mut self, inner_numbers: &Vec<i32>) -> iter::FilterMap<..., InnerClosure<??>> {
let inner = InnerClosure {
seen: &mut *self.seen // can''t move out, so must be a reborrow
};
inner_numbers.iter().filter_map(inner)
}
}
impl<''a> FnMut(&i32) -> Option<i32> for InnerClosure<''a> { ... }
(He nombrado la vida &mut self
en este, con fines pedagógicos).
Este caso es definitivamente más sutil. El iterador FilterMap
almacena el cierre internamente, lo que significa que cualquier referencia en el valor de cierre (es decir, cualquier referencia que capture) tiene que ser válida siempre que los valores de FilterMap
se estén FilterMap
y, para las referencias de &mut
, cualquier referencia debe ser cuidado de no ser alias
El compilador no puede estar seguro de que flat_map
no lo haga, por ejemplo, almacene todos los iteradores devueltos en un Vec<FilterMap<...>>
lo que resultaría en un montón de alias &mut
s ... ¡muy mal! Creo que este uso específico de flat_map
es seguro, pero no estoy seguro de que sea en general, y ciertamente hay funciones con el mismo estilo de firma que flat_map
(por ejemplo, map
) definitivamente unsafe
sería unsafe
. (De hecho, reemplazar flat_map
con map
en el código da la situación de Vec
que acabo de describir).
Para el mensaje de error: self
es efectivamente (ignorando la envoltura de la estructura) &''b mut (&''a mut Vec<i32>)
donde ''b
es la vida útil de &mut self
reference y ''a
es la vida útil de la referencia en la struct
. Mover el interior &mut
es ilegal: no se puede mover un tipo afín como &mut
fuera de una referencia (aunque funcionaría con &Vec<i32>
), por lo que la única opción es volver a crecer. Un reborde está pasando por la referencia externa y, por lo tanto, no puede sobrevivir a la misma, es decir, el &mut *self.seen
reborrow es un &''b mut Vec<i32>
, no un &''a mut Vec<i32>
.
Esto hace que el cierre interno tenga el tipo InnerClosure<''b>
y, por lo tanto, el método call_mut
está intentando devolver un FilterMap<..., InnerClosure<''b>>
. Desafortunadamente, el rasgo call_mut
define call_mut
como solo
pub trait FnMut<Args>: FnOnce<Args> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
En particular, no hay conexión entre el tiempo de vida de la self
referencia y el valor devuelto, por lo que es ilegal intentar devolver InnerClosure<''b>
que tiene ese enlace. Esta es la razón por la que el compilador se queja de que la vida útil es demasiado corta para poder volver a crecer.
Esto es extremadamente similar al método Iterator::next
, y el código aquí está fallando básicamente por la misma razón por la que no se puede tener un iterador sobre las referencias en la memoria que posee el propio iterador. (Imagino que un "iterador de transmisión" (los iteradores con un enlace entre &mut self
y el valor de retorno en la next
) podrían proporcionar un flat_map
que funciona con el código casi escrito: necesitaría rasgos de "cierre" con un enlace similar. )
Las soluciones incluyen:
- el
RefCell
sugerido por Renato Zannon, que permite serseen
como prestado como un&
compartido. El código de cierre descatalogado es básicamente el mismo que no sea cambiar el&mut Vec<i32>
a&Vec<i32>
. Este cambio significa que "reborrow" del&''b mut &''a RefCell<Vec<i32>>
puede ser solo una copia del&''a ...
out del&mut
. Es una copia literal, por lo que se conserva la vida útil. - evitando la pereza de los iteradores, para evitar devolver el cierre interno, específicamente
.collect::<Vec<_>>()
dentro del bucle para ejecutar todo elfilter_map
completo antes de regresar.
fn main() {
let mut seen = vec![];
let items = vec![vec![1i32, 2], vec![3], vec![1]];
let a: Vec<_> = items
.iter()
.flat_map(|inner_numbers| {
inner_numbers
.iter()
.filter_map(|&number| if !seen.contains(&number) {
seen.push(number);
Some(number)
} else {
None
})
.collect::<Vec<_>>()
.into_iter()
})
.collect();
println!("{:?}", a);
}
Me imagino que la versión RefCell
es más eficiente.
Este código es una forma ineficiente de producir un conjunto único de elementos desde un iterador. Para lograr esto, estoy intentando usar un Vec
para realizar un seguimiento de los valores que he visto. Creo que este Vec
debe ser propiedad del cierre más interno:
fn main() {
let mut seen = vec![];
let items = vec![vec![1i32, 2], vec![3], vec![1]];
let a: Vec<_> = items
.iter()
.flat_map(move |inner_numbers| {
inner_numbers.iter().filter_map(move |&number| {
if !seen.contains(&number) {
seen.push(number);
Some(number)
} else {
None
}
})
})
.collect();
println!("{:?}", a);
}
Sin embargo, la compilación falla con:
error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
--> src/main.rs:8:45
|
2 | let mut seen = vec![];
| -------- captured outer variable
...
8 | inner_numbers.iter().filter_map(move |&number| {
| ^^^^^^^^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure
Parece que el verificador de préstamos se confunde en los cierres anidados + préstamo mutable. Podría valer la pena presentar un problema. Edición: vea la respuesta de Huan para saber por qué esto no es un error.
Como solución alternativa, es posible recurrir a RefCell
aquí:
use std::cell::RefCell;
fn main() {
let seen = vec![];
let items = vec![vec![1i32, 2], vec![3], vec![1]];
let seen_cell = RefCell::new(seen);
let a: Vec<_> = items
.iter()
.flat_map(|inner_numbers| {
inner_numbers.iter().filter_map(|&number| {
let mut borrowed = seen_cell.borrow_mut();
if !borrowed.contains(&number) {
borrowed.push(number);
Some(number)
} else {
None
}
})
})
.collect();
println!("{:?}", a);
}