una tope sube salio que poner mochila deslizador cremallera como cierre cambiar arreglar anclaje closures rust

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 ser seen 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 el filter_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); }