web-dev-qa-db-fra.com

Quelle est la bonne façon de retourner un Itérateur (ou tout autre trait)?

Le code Rust) suivant est compilé et s’exécute sans problème.

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

Après cela, j'ai essayé quelque chose comme ça .... mais ça n'a pas 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(' '))
}

Le problème principal est que je ne suis pas sûr du type de retour que la fonction to_words() devrait avoir. Le compilateur dit:

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`

Quel serait le bon code pour faire cette course? .... et où est mon manque de connaissances?

86
forgemo

J'ai trouvé utile de laisser le compilateur me guider:

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

La compilation donne:

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

En suivant les suggestions du compilateur et en les copiant-collant comme type de retour (avec un peu de nettoyage):

use std::str;

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

Le problème est que vous ne pouvez pas retourner un trait tel que Iterator car un trait n'a pas de taille. Cela signifie que Rust ne sait pas combien d'espace allouer pour le type. You ne peut pas renvoyer de référence à une variable locale non plus , donc retournant &dyn Iterator est un non-starter.

Trait d'impl

À partir de Rust 1,26, vous pouvez utiliser 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());
}

Il y a des restrictions sur la façon dont cela peut être utilisé. Vous ne pouvez renvoyer qu'un seul type (pas de condition!) Et il doit être utilisé avec une fonction libre ou une implémentation inhérente.

En boîte

Si cela ne vous dérange pas de perdre un peu d’efficacité, vous pouvez renvoyer 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());
}

C’est l’option principale qui permet l’envoi dynamique . C'est-à-dire que l'implémentation exacte du code est décidée au moment de l'exécution plutôt que lors de la compilation. Cela signifie que cela convient aux cas où vous devez renvoyer plusieurs types d'itérateurs concrets en fonction d'une condition.

Nouveau genre

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

Type alias

Comme souligné par 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());
}

Faire face aux fermetures

Quand impl Trait _ n'est pas utilisable, les fermetures rendent les choses plus compliquées. Les fermetures créent des types anonymes et ils ne peuvent pas être nommés dans le type de retour:

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

Dans certains cas, ces fermetures peuvent être remplacées par des fonctions pouvant être nommées:

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

Et en suivant les conseils ci-dessus:

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

Faire face aux conditions

Si vous devez choisir un itérateur de manière conditionnelle, reportez-vous à la section itérer conditionnellement sur un ou plusieurs itérateurs possibles .

111
Shepmaster