Qual è il modo corretto per restituire un Iteratore (o qualsiasi altro tratto)?


114

Il seguente codice Rust si compila e funziona senza problemi.

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

Dopodiché, ho provato qualcosa di simile ... ma non si è compilato

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

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

Il problema principale è che non sono sicuro del tipo di ritorno che la funzione to_words()dovrebbe avere. Il compilatore 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`

Quale sarebbe il codice corretto per eseguire questa operazione? .... e dov'è la mia lacuna di conoscenza?

Risposte:


143

Ho trovato utile lasciarmi guidare dal compilatore:

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

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

Seguendo il suggerimento del compilatore e copiandolo come tipo di ritorno (con un po 'di pulizia):

use std::str;

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

Il problema è che non puoi restituire un tratto come Iteratorperché un tratto non ha una dimensione. Ciò significa che Rust non sa quanto spazio allocare per il tipo. Non è nemmeno possibile restituire un riferimento a una variabile locale , quindi restituire non &dyn Iteratorè un elemento iniziale.

Tratto Impl

A partire da Rust 1.26, puoi usare 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());
}

Ci sono restrizioni su come questo può essere utilizzato. È possibile restituire un solo tipo (senza condizioni!) E deve essere utilizzato su una funzione libera o un'implementazione intrinseca.

Incorniciato

Se non ti dispiace perdere un po 'di efficienza, puoi restituire 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());
}

Questa è l'opzione principale che consente l' invio dinamico . Cioè, l'esatta implementazione del codice viene decisa in fase di esecuzione, piuttosto che in fase di compilazione. Ciò significa che è adatto per i casi in cui è necessario restituire più di un tipo concreto di iteratore in base a una condizione.

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

Alias ​​di tipo

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

Trattare con chiusure

Quando impl Traitnon è disponibile per l'uso, le chiusure rendono le cose più complicate. Le chiusure creano tipi anonimi e questi non possono essere nominati nel tipo restituito:

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

In alcuni casi, queste chiusure possono essere sostituite con funzioni, che possono essere denominate:

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 seguendo il consiglio di cui sopra:

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

Trattare con i condizionali

Se è necessario scegliere in modo condizionale un iteratore, fare riferimento a iterazione condizionale su uno dei numerosi iteratori possibili .


Grazie, questo mi ha aiutato molto. Il "trucco" per farti guidare dal compilatore è piuttosto utile, lo userò sicuramente in futuro. ... e sì, questo è davvero brutto! Spero che RFC arrivi alla release candidate.
forgemo

8
Sebbene i tipi di wrapper possano essere utili per nascondere la complessità, trovo che sia meglio usare solo gli typealias, poiché l'uso di un newtype significa che il tuo Iterator non implementerà i tratti come RandomAccessIteratoranche se l'iteratore sottostante lo fa.
reem

4
Sì! Gli alias di tipo supportano parametri generici. Ad esempio, molte librerie fanno type LibraryResult<T> = Result<T, LibraryError>per comodità simile a IoResult<T>, che è anche solo un alias di tipo.
reem il

1
Potresti per favore chiarire perché si deve aggiungere una 'avita Box? Cosa significa? Ho sempre pensato che fosse solo per limiti, per dire "posso dipendere solo da qualcosa che vive almeno finché 'a".
torkleyy

1
@torkleyy forse stackoverflow.com/q/27790168/155423 o stackoverflow.com/q/27675554/155423 sarebbe rispondere alla tua domanda? In caso contrario, ti incoraggio a cercare la tua domanda e, se non riesci a trovarla, chiedine una nuova.
Shepmaster
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.