Qual é a maneira correta de retornar um Iterador (ou qualquer outra característica)?

114

O seguinte código Rust é compilado e executado sem problemas.

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

Depois disso, tentei algo assim ... mas não compilou

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

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

O principal problema é que não tenho certeza de qual tipo de retorno a função to_words()deve ter. O compilador diz:

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`

Qual seria o código correto para fazer isso? .... e onde está minha lacuna de conhecimento?

Forgemo
fonte

Respostas:

143

Achei útil deixar o compilador me guiar:

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

Compilar dá:

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> {
  |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Seguindo a sugestão do compilador e copiando e colando isso como meu tipo de retorno (com uma pequena limpeza):

use std::str;

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

O problema é que você não pode retornar uma característica como Iteratorporque uma característica não tem tamanho. Isso significa que Rust não sabe quanto espaço alocar para o tipo. Você também não pode retornar uma referência a uma variável local , portanto, retornar &dyn Iteratornão é inicial.

Traço Impl

A partir do Rust 1.26, você pode usar 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());
}

Existem restrições sobre como isso pode ser usado. Você só pode retornar um único tipo (sem condicionais!) E deve ser usado em uma função livre ou uma implementação inerente.

Encaixotado

Se você não se importa em perder um pouco de eficiência, pode retornar um 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 é a opção principal que permite o envio dinâmico . Ou seja, a implementação exata do código é decidida em tempo de execução, ao invés de tempo de compilação. Isso significa que é adequado para casos em que você precisa retornar mais de um tipo concreto de iterador com base em uma condição.

Newtype

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());
}

Digite o alias

Conforme apontado por 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());
}

Lidando com fechamentos

Quando impl Traitnão está disponível para uso, os fechos tornam as coisas mais complicadas. Os fechamentos criam tipos anônimos e não podem ser nomeados no 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]>`

Em certos casos, esses encerramentos podem ser substituídos por funções, que podem ser nomeadas:

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>`

E seguindo o conselho acima:

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)
}

Lidando com condicionais

Se você precisar escolher condicionalmente um iterador, consulte Iterar condicionalmente sobre um dos vários iteradores possíveis .

Shepmaster
fonte
Obrigado, isso me ajudou muito. O "truque" para deixar o compilador guiá-lo é muito útil, com certeza vou usá-lo no futuro. ... e sim, isso é muito feio! Espero que o RFC chegue ao candidato a lançamento.
forgemo
8
Embora os tipos de wrapper possam ser bons para ocultar a complexidade, acho melhor apenas usar typealiases em vez disso, já que usar um newtype significa que seu Iterator não implementará características como RandomAccessIteratormesmo se o Iterator subjacente o fizesse.
reem
4
Sim! Os aliases de tipo oferecem suporte a parâmetros genéricos. Por exemplo, muitas bibliotecas têm type LibraryResult<T> = Result<T, LibraryError>uma conveniência semelhante a IoResult<T>, que também é apenas um alias de tipo.
reem
1
Você poderia esclarecer por que é necessário adicionar uma 'avida inteira Box? O que isso significa? Eu sempre pensei que isso era apenas para limites, dizer "T pode depender apenas de algo que vive pelo menos enquanto 'a".
torkleyy
1
@torkleyy talvez stackoverflow.com/q/27790168/155423 ou stackoverflow.com/q/27675554/155423 responda à sua pergunta? Caso contrário, encorajo você a pesquisar sua pergunta e, se não conseguir encontrar, faça uma nova.
Shepmaster