Jaki jest prawidłowy sposób zwrócenia Iteratora (lub dowolnej innej cechy)?

114

Poniższy kod Rusta kompiluje się i działa bez żadnych problemów.

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

Potem próbowałem czegoś takiego ... ale to się nie skompilowało

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

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

Głównym problemem jest to, że nie jestem pewien, jaki typ zwrotu to_words()powinna mieć funkcja . Kompilator mówi:

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`

Jaki byłby poprawny kod, aby to uruchomić? .... a gdzie jest moja luka w wiedzy?

zapomnij
źródło

Odpowiedzi:

143

Zauważyłem, że warto pozwolić kompilatorowi mnie prowadzić:

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

Kompilacja daje:

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

Postępując zgodnie z sugestią kompilatora i kopiując i wklejając to jako mój typ zwrotu (z niewielkim porządkiem):

use std::str;

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

Problem polega na tym, że nie możesz zwrócić cechy takiej jak, Iteratorponieważ cecha nie ma rozmiaru. Oznacza to, że Rust nie wie, ile miejsca przeznaczyć na typ. Nie możesz również zwrócić odwołania do zmiennej lokalnej , więc zwracanie nie &dyn Iteratorjest początkowe.

Impl cecha

Od wersji Rust 1.26 możesz używać 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());
}

Istnieją ograniczenia dotyczące tego, jak można tego używać. Możesz zwrócić tylko jeden typ (bez warunków!) I musi być użyty w wolnej funkcji lub wewnętrznej implementacji.

W pudełku

Jeśli nie masz nic przeciwko utracie odrobiny wydajności, możesz zwrócić 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());
}

Jest to podstawowa opcja umożliwiająca dynamiczną wysyłkę . Oznacza to, że o dokładnej implementacji kodu decyduje się w czasie wykonywania, a nie kompilacji. Oznacza to, że jest to odpowiednie w przypadkach, w których musisz zwrócić więcej niż jeden konkretny typ iteratora na podstawie warunku.

Nowy typ

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

Wpisz alias

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

Radzenie sobie z zamknięciami

Kiedy impl Traitnie jest dostępny do użytku, zamknięcia komplikują sprawę. Zamknięcia tworzą anonimowe typy, których nie można nazwać w zwracanym typie:

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

W niektórych przypadkach te zamknięcia można zastąpić funkcjami, które można nazwać:

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

I postępując zgodnie z powyższą radą:

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

Radzenie sobie z warunkami

Jeśli chcesz warunkowo wybrać iterator, zapoznaj się z tematem Warunkowe iteracje po jednym z kilku możliwych iteratorów .

Shepmaster
źródło
Dziękuję, bardzo mi to pomogło. „Trik” pozwalający kompilatorowi poprowadzić Cię jest całkiem przydatny, na pewno użyję go w przyszłości. ... i tak, to jest naprawdę brzydkie! Mam nadzieję, że RFC dotrze do kandydata do wydania.
Forgeemo
8
Chociaż typy opakowań mogą być przyjemne do ukrycia złożoności, uważam, że lepiej jest typezamiast tego używać aliasów, ponieważ użycie nowego typu oznacza, że ​​twój Iterator nie zaimplementuje cech, jak RandomAccessIteratornawet jeśli Iterator bazowy to robi.
przypomnij sobie
4
Tak! Aliasy typów obsługują parametry ogólne. Na przykład wiele bibliotek robi type LibraryResult<T> = Result<T, LibraryError>to dla wygody podobnej do IoResult<T>, która jest również tylko aliasem typu.
przypomnij sobie
1
Czy mógłbyś wyjaśnić, dlaczego trzeba dodać 'acałe życie Box? Co to znaczy? Zawsze myślałem, że to tylko dla ograniczeń, aby powiedzieć: „T może zależeć tylko od czegoś, co żyje przynajmniej tak długo 'a”.
torkleyy
1
@torkleyy może stackoverflow.com/q/27790168/155423 lub stackoverflow.com/q/27675554/155423 odpowie na Twoje pytanie? Jeśli nie, zachęcam do wyszukania swojego pytania, a jeśli nie możesz go znaleźć, zadaj nowe.
Shepmaster