イテレーター(またはその他の特性)を返す正しい方法は何ですか?


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はスターターではありません。

インプリ特性

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

タイプエイリアス

Asをリームによって指摘

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

条件文の扱い

イテレータを条件付きで選択する必要がある場合は、可能なイテレータの1つ条件付きで反復するを参照してください。


ありがとう、これは私を助けてくれました。コンパイラーにガイドさせる「トリック」はかなり便利です。将来的には間違いなく使用します。...そして、はい、これは深刻に醜いです!RFCがリリース候補になることを願っています。
forgemo 2014

8
ラッパータイプは複雑さを隠すのに適していtypeますが、newtypeを使用すると、イテレーターがRandomAccessIterator基になるイテレーターのように特性を実装しないため、代わりにエイリアスを使用する方が良いと思います。
2014

4
うん!タイプエイリアスは、ジェネリックパラメーターをサポートします。たとえば、多くのライブラリはtype LibraryResult<T> = Result<T, LibraryError>と同様に便利ですがIoResult<T>、これも単なる型エイリアスです。
2014

1
'aライフタイムを追加する必要がある理由を明確にしていただけませんBoxか?どういう意味ですか?私はいつもこれは境界のためだけだと思っていました'a
torkleyy 2017

1
@torkleyyおそらくstackoverflow.com/q/27790168/155423またはstackoverflow.com/q/27675554/155423があなたの質問に答えますか?そうでない場合は、質問を検索することをお勧めします。見つからない場合は、新しい質問をしてください。
Shepmaster
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.