Как правильно вернуть итератор (или любую другую черту)?


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потому что черта не имеет размера. Это означает, что Rust не знает, сколько места выделить для типа. Вы также не можете вернуть ссылку на локальную переменную , поэтому возврат не &dyn Iteratorявляется запуском.

Impl черта

Начиная с Rust 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вместо этого просто использовать псевдонимы, поскольку использование newtype означает, что ваш Iterator не будет реализовывать такие черты, как, RandomAccessIteratorдаже если базовый Iterator это делает.
reem

4
Ага! Псевдонимы типов поддерживают общие параметры. Например, многие библиотеки type LibraryResult<T> = Result<T, LibraryError>по удобству похожи на IoResult<T>, который также является псевдонимом типа.
reem

1
Не могли бы вы пояснить, почему нужно прибавлять к 'aжизни Box? Что это значит? Я всегда думал, что это было чисто условным, чтобы сказать: «T может зависеть только от чего-то живущего по крайней мере столько же 'a».
торклей 03

1
@torkleyy, возможно, stackoverflow.com/q/27790168/155423 или stackoverflow.com/q/27675554/155423 ответит на ваш вопрос? Если нет, я бы посоветовал вам поискать свой вопрос, а если вы не можете его найти, задайте новый.
Shepmaster 03
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.