¿Cuál es la forma correcta de devolver un iterador (o cualquier otro rasgo)?

114

El siguiente código de Rust se compila y se ejecuta sin problemas.

fn main() {
    let text = "abc";
    println!("{}", text.split(' ').take(2).count());
}

Después de eso, intenté algo como esto ... pero no se compiló

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

fn to_words(text: &str) -> &Iterator<Item = &str> {
    &(text.split(' '))
}

El principal problema es que no estoy seguro de qué tipo de retorno to_words()debería tener la función . El compilador dice:

error[E0599]: no method named `count` found for type `std::iter::Take<std::iter::Iterator<Item=&str>>` in the current scope
 --> src/main.rs:3:43
  |
3 |     println!("{}", to_words(text).take(2).count());
  |                                           ^^^^^
  |
  = note: the method `count` exists but the following trait bounds were not satisfied:
          `std::iter::Iterator<Item=&str> : std::marker::Sized`
          `std::iter::Take<std::iter::Iterator<Item=&str>> : std::iter::Iterator`

¿Cuál sería el código correcto para ejecutar esto? .... y donde esta mi brecha de conocimiento?

forgemo
fuente

Respuestas:

143

Me ha resultado útil dejar que el compilador me guíe:

fn to_words(text: &str) { // Note no return type
    text.split(' ')
}

La compilación da:

error[E0308]: mismatched types
 --> src/lib.rs:5:5
  |
5 |     text.split(' ')
  |     ^^^^^^^^^^^^^^^ expected (), found struct `std::str::Split`
  |
  = note: expected type `()`
             found type `std::str::Split<'_, char>`
help: try adding a semicolon
  |
5 |     text.split(' ');
  |                    ^
help: try adding a return type
  |
3 | fn to_words(text: &str) -> std::str::Split<'_, char> {
  |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Siguiendo la sugerencia del compilador y copiando y pegando eso como mi tipo de retorno (con un poco de limpieza):

use std::str;

fn to_words(text: &str) -> str::Split<'_, char> {
    text.split(' ')
}

El problema es que no puedes devolver un rasgo como Iteratorporque un rasgo no tiene un tamaño. Eso significa que Rust no sabe cuánto espacio asignar para el tipo. Usted no puede devolver una referencia a una variable local, ya sea , por lo que volver &dyn Iteratores un non-starter.

Rasgo implícito

A partir de Rust 1.26, puede utilizar impl trait:

fn to_words<'a>(text: &'a str) -> impl Iterator<Item = &'a str> {
    text.split(' ')
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

Existen restricciones sobre cómo se puede utilizar. Solo puede devolver un solo tipo (¡sin condicionales!) Y debe usarse en una función libre o una implementación inherente.

En caja

Si no le importa perder un poco de eficiencia, puede devolver un Box<dyn Iterator>:

fn to_words<'a>(text: &'a str) -> Box<dyn Iterator<Item = &'a str> + 'a> {
    Box::new(text.split(' '))
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

Esta es la opción principal que permite el envío dinámico . Es decir, la implementación exacta del código se decide en tiempo de ejecución, en lugar de en tiempo de compilación. Eso significa que es adecuado para los casos en los que necesita devolver más de un tipo concreto de iterador en función de una condición.

Nuevo tipo

use std::str;

struct Wrapper<'a>(str::Split<'a, char>);

impl<'a> Iterator for Wrapper<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        self.0.next()
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.0.size_hint()
    }
}

fn to_words(text: &str) -> Wrapper<'_> {
    Wrapper(text.split(' '))
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

Escriba alias

Como lo señaló reem

use std::str;

type MyIter<'a> = str::Split<'a, char>;

fn to_words(text: &str) -> MyIter<'_> {
    text.split(' ')
}

fn main() {
    let text = "word1 word2 word3";
    println!("{}", to_words(text).take(2).count());
}

Lidiando con cierres

Cuando impl Traitno está disponible para su uso, los cierres complican las cosas. Los cierres crean tipos anónimos y estos no se pueden nombrar en el tipo de retorno:

fn odd_numbers() -> () {
    (0..100).filter(|&v| v % 2 != 0)
}
found type `std::iter::Filter<std::ops::Range<{integer}>, [closure@src/lib.rs:4:21: 4:36]>`

En ciertos casos, estos cierres se pueden reemplazar con funciones, que se pueden nombrar:

fn odd_numbers() -> () {
    fn f(&v: &i32) -> bool {
        v % 2 != 0
    }
    (0..100).filter(f as fn(v: &i32) -> bool)
}
found type `std::iter::Filter<std::ops::Range<i32>, for<'r> fn(&'r i32) -> bool>`

Y siguiendo los consejos anteriores:

use std::{iter::Filter, ops::Range};

type Odds = Filter<Range<i32>, fn(&i32) -> bool>;

fn odd_numbers() -> Odds {
    fn f(&v: &i32) -> bool {
        v % 2 != 0
    }
    (0..100).filter(f as fn(v: &i32) -> bool)
}

Tratar con condicionales

Si necesita elegir condicionalmente un iterador, consulte Reiterar condicionalmente uno de varios posibles iteradores .

Pastor
fuente
Gracias, esto me ayudó mucho. El "truco" para dejar que el compilador te guíe es bastante útil, definitivamente lo usaré en el futuro. ... y sí, ¡esto es realmente feo! Espero que RFC llegue al candidato de lanzamiento.
forgemo
8
Si bien los tipos de envoltura pueden ser agradables para ocultar la complejidad, creo que es mejor usar typealias en su lugar, ya que usar un nuevo tipo significa que su Iterador no implementará rasgos como RandomAccessIteratorsi lo hiciera el Iterador subyacente.
reem
4
¡Sip! Los alias de tipo admiten parámetros genéricos. Por ejemplo, muchas bibliotecas funcionan type LibraryResult<T> = Result<T, LibraryError>como una conveniencia similar a IoResult<T>, que también es solo un alias de tipo.
reem
1
¿Podría aclarar por qué hay que añadir una 'avida Box? Qué significa eso? Siempre pensé que esto era solo para los límites, para decir "Yo solo puedo depender de algo que viva al menos mientras 'a".
torkleyy
1
@torkleyy, ¿quizás stackoverflow.com/q/27790168/155423 o stackoverflow.com/q/27675554/155423 respondería a su pregunta? De lo contrario, le animo a que busque su pregunta y, si no puede encontrarla, pregunte una nueva.
Shepmaster