reference rust iterator traits

reference - ¿Cómo puedo crear un iterador de & T desde un & Vec<T> o Vec<& T>?



rust iterator (5)

Tengo una enumeración con dos variantes. O contiene una referencia a un Vec de String s o contiene un Vec de referencias a String s:

enum Foo<''a> { Owned(&''a Vec<String>), Refs(Vec<&''a String>), }

Quiero iterar sobre las referencias a la String s en esta enumeración.

Intenté implementar un método en Foo , pero no sé cómo hacer que devuelva el iterador correcto:

impl<''a> Foo<''a> { fn get_items(&self) -> Iter<''a, String> { match self { Foo::Owned(v) => v.into_iter(), Foo::Refs(v) => /* what to put here? */, } } } fn main() { let test: Vec<String> = vec!["a".to_owned(), "b".to_owned()]; let foo = Foo::Owned(&test); for item in foo.get_items() { // item should be of type &String here println!("{:?}", item); } }

playground

¿Cuál es un método idiomático para lograr esta abstracción sobre &Vec<T> y Vec<&T> ? get_items también puede devolver algo diferente, siempre que implemente el rasgo IntoIterator para que pueda usarlo en el bucle for .


Algunas cosas que debes saber primero:

  • Definitivamente vas a tener dos iteradores diferentes, porque son diferentes tipos base sobre los que estás iterando. Por lo tanto, voy a usar un Box<dyn Iterator<Item = &''a _>> , pero siéntase libre de usar una enum si esto causa una caída cuantificable del rendimiento.
  • Necesita introducir aquí la vida útil de uno mismo, porque ¿qué pasa si devolvemos un iterador cuya vida útil es ''a , pero ''a > ''self ? Por lo tanto, hacemos una nueva vida (que llamaré ''b .).
  • Ahora es solo una cuestión de disputas con las capas de referencia:

Aquí está la implementación usando los tipos originales:

enum Foo<''a> { Owned(&''a Vec<String>), Refs(Vec<&''a String>) } impl<''a> Foo<''a> { fn get_items<''b>(&''b self) -> Box<dyn Iterator<Item = &''a String> + ''b> { match self { Foo::Owned(v) => //v: &''a Vec<String> Box::new( v.iter() //Iterator<Item = &''a String> -- Good! ), Foo::Refs(v) => //v: Vec<&''a String> Box::new( v.iter() //Iterator<Item = &''b &''a String> -- Bad! .map(|x| *x) //Iterator<Item = &''a String> -- Good! ), } } }

Estos tipos no son realmente como el óxido (o más formalmente, idiomático ), así que aquí está esa versión que usa cortes y str :

enum Foo<''a> { Owned(&''a [String]), Refs(Vec<&''a str>) } impl<''a> Foo<''a> { fn get_items<''b>(&''b self) -> Box<dyn Iterator<Item = &''a str> + ''b> { match self { Foo::Owned(v) => Box::new( v.into_iter() .map(|x| &**x) //&''a String -> &''a str ), Foo::Refs(v) => Box::new( v.iter() .map(|x| *x) //&''b &''a str -> &''a str )/* what to put here? */, } } }

Playground


Hay una caja práctica, auto_enums , que puede generar un tipo para que una función pueda tener múltiples tipos de retorno, siempre que implementen el mismo rasgo. Es similar al código en la respuesta de Denys Séguret, excepto que todo está hecho por la macro auto_enum :

use auto_enums::auto_enum; impl<''a> Foo<''a> { #[auto_enum(Iterator)] fn get_items(&self) -> impl Iterator<Item = &String> { match self { Foo::Owned(v) => v.iter(), Foo::Refs(v) => v.iter().copied(), } } }

Agregue la dependencia agregando esto en su Cargo.toml :

[dependencies] auto_enums = "0.6.3"


Lo ideal es que quieras:

fn get_items(&self) -> impl Iterator<Item = &String> { match self { Foo::Owned(v) => v.into_iter(), Foo::Refs(v) => v.iter().copied(), } }

La llamada a copied está aquí para convertir un Iterator<Item = &&String> en el Iterator<Item = &String> que queramos. Esto no funciona porque los dos brazos combinados tienen diferentes tipos:

error[E0308]: match arms have incompatible types --> src/main.rs:12:30 | 10 | / match self { 11 | | Foo::Owned(v) => v.into_iter(), | | ------------- this is found to be of type `std::slice::Iter<''_, std::string::String>` 12 | | Foo::Refs(v) => v.iter().copied(), | | ^^^^^^^^^^^^^^^^^ expected struct `std::slice::Iter`, found struct `std::iter::Copied` 13 | | } | |_________- `match` arms have incompatible types | = note: expected type `std::slice::Iter<''_, std::string::String>` found type `std::iter::Copied<std::slice::Iter<''_, &std::string::String>>`

Puede corregir este error gracias a las itertools o either cajas, que contienen un práctico adaptador llamado Either ( * ) que le permite elegir dinámicamente entre dos iteradores:

fn get_items(&self) -> impl Iterator<Item = &String> { match self { Foo::Owned(v) => Either::Left(v.into_iter()), Foo::Refs(v) => Either::Right(v.iter().copied()), } }

playground


No puede usar el tipo std::slice::Iter para esto.

Si no desea copiar las cadenas o el vector, deberá implementar su propio iterador, por ejemplo:

struct FooIter<''a, ''b> { idx: usize, foo: &''b Foo<''a>, } impl<''a, ''b> Iterator for FooIter<''a, ''b> { type Item = &''a String; fn next(&mut self) -> Option<Self::Item> { self.idx += 1; match self.foo { Foo::Owned(v) => v.get(self.idx - 1), Foo::Refs(v) => v.get(self.idx - 1).map(|s| *s), } } } impl<''a, ''b> Foo<''a> { fn get_items(&''b self) -> FooIter<''a, ''b> { FooIter { idx: 0, foo: self } } } fn main() { let test: Vec<String> = vec!["a".to_owned(), "b".to_owned()]; let foo = Foo::Owned(&test); for item in foo.get_items() { println!("{:?}", item); } let a = "a".to_string(); let b = "b".to_string(); let test: Vec<&String> = vec![&a, &b]; let foo = Foo::Refs(test); for item in foo.get_items() { println!("{:?}", item); } }


Si no desea implementar su propio iterador, necesita un despacho dinámico para esto, porque desea devolver diferentes iteradores dependiendo de la variante enum.

Necesitamos un objeto de rasgo ( &dyn Trait , &mut dyn Trait o Box<dyn Trait> ) para usar el despacho dinámico:

impl<''a> Foo<''a> { fn get_items(&''a self) -> Box<dyn Iterator<Item = &String> + ''a> { match self { Foo::Owned(v) => Box::new(v.into_iter()), Foo::Refs(v) => Box::new(v.iter().copied()), } } }

.copied() convierte el Iterator<Item = &&String> en un Iterator<Item = &String> , por lo que esto no copia nada :)