Який правильний спосіб повернути ітератор (або будь-яку іншу ознаку)?


114

Наступний код Rust збирається та працює без проблем.

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

Після цього я спробував щось подібне .... але воно не склалося

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

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

Основна проблема полягає в тому, що я не впевнений, який тип повернення to_words()має мати функція . Компілятор каже:

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`

Який би був правильний код, щоб зробити цей запуск? .... а де мій розрив знань?

Відповіді:


143

Я вважаю корисним дозволити компілятору керувати мною:

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

Складання дає:

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

Після пропозиції компілятора та вставлення копії, що як мій тип повернення (з невеликим очищенням):

use std::str;

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

Проблема полягає в тому, що ви не можете повернути ознаку на зразок, Iteratorоскільки ознака не має розміру. Це означає, що Руст не знає, скільки місця виділити для типу. Ви також не можете повернути посилання на локальну змінну , тому повернення &dyn Iteratorне є початковим.

Impl риса

Станом на Іржу 1,26, ви можете використовувати 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());
}

Існують обмеження щодо того, як це можна використовувати. Ви можете повернути лише один тип (без умовних умов!), І він повинен бути використаний у вільній функції або вродженій реалізації.

У коробці

Якщо ви не проти втратити ефективність, ви можете повернути 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());
}

Це основний варіант, який дозволяє динамічно відправляти . Тобто точна реалізація коду вирішується під час виконання, а не під час компіляції. Це означає, що це підходить у випадках, коли вам потрібно повернути більше одного конкретного типу ітератора на основі умови.

Новий тип

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

Введіть псевдонім

Як вказував реем

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

Справа із закриттями

Якщо impl Traitнедоступні для використання, закриття ускладнюють справи. Закриття створюють анонімні типи, і вони не можуть бути названі у типі повернення:

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

У деяких випадках ці закриття можна замінити функціями, які можна назвати:

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

І дотримуючись вищевказаних порад:

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

Справа з умовними умовами

Якщо вам потрібно умовно вибрати ітератор, зверніться до Умовно ітерації одного з декількох можливих ітераторів .


Дякую, це мені дуже допомогло. "Трюк", щоб дозволити компілятору керувати вами, є досить корисним, я його обов'язково буду використовувати в майбутньому. ... і так, це серйозно потворно! Я сподіваюся, що RFC зробить це кандидатом у звільнення.
forgemo

8
Незважаючи на те, що типи обгортки можуть приємно приховувати складність, я вважаю, що краще просто використовувати typeпсевдоніми, оскільки використання нового типу означає, що ваш Ітератор не реалізовуватиме такі ознаки, як RandomAccessIteratorнавіть якщо базовий Iterator.
reem

4
Так! Псевдоніми типу підтримують загальні параметри. Наприклад, багато бібліотек, type LibraryResult<T> = Result<T, LibraryError>як зручність, схожа на те IoResult<T>, що також є лише псевдонімом типу.
reem

1
Не могли б ви пояснити, чому до цього потрібно додати 'aжиття Box? Що це означає? Я завжди думав, що це стосується лише меж, щоб сказати, що "Т може залежати лише від чогось, що живе, принаймні, поки 'a".
torkleyy

1
@torkleyy можливо stackoverflow.com/q/27790168/155423 або stackoverflow.com/q/27675554/155423 відповісти б на ваше запитання? Якщо ні, то я б рекомендував шукати своє запитання, а якщо ви не можете його знайти, задайте нове.
Шепмайстер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.