56

他の言語が遅延シーケンスまたは「ジェネレーター」関数と呼ぶものを作成するにはどうすればよいですか?

Python ではyield、次の例 (Python のドキュメントから) のように使用して、中間リストのメモリを使用しない方法で反復可能なシーケンスを遅延生成できます。

# a generator that yields items instead of returning a list
def firstn(n):
    num = 0
    while num < n:
        yield num
        num += 1

sum_of_first_n = sum(firstn(1000000))

Rustで同様のことを行うにはどうすればよいですか?

4

4 に答える 4

52

Rustにはジェネレーターがありますが、それらは非常に実験的なものであり、現在安定版の Rust では利用できません。

安定した Rust 1.0 以降で動作します

Range具体的な例を処理します。次の構文シュガーで使用できます..

fn main() {
    let sum: u64 = (0..1_000_000).sum();
    println!("{}", sum)
}

Range存在しなかったら?それをモデル化するイテレータを作成できます!

struct MyRange {
    start: u64,
    end: u64,
}

impl MyRange {
    fn new(start: u64, end: u64) -> MyRange {
        MyRange {
            start: start,
            end: end,
        }
    }
}

impl Iterator for MyRange {
    type Item = u64;

    fn next(&mut self) -> Option<u64> {
        if self.start == self.end {
            None
        } else {
            let result = Some(self.start);
            self.start += 1;
            result
        }
    }
}

fn main() {
    let sum: u64 = MyRange::new(0, 1_000_000).sum();
    println!("{}", sum)
}

内臓は同じですが、Python バージョンよりも明確です。特に、Python のジェネレーターは状態を追跡します。Rust は明示性を好むため、独自の状態を作成して手動で更新する必要があります。重要な部分はIteratortraitの実装です。イテレータが特定の型 ( ) の値を生成することを指定し、type Item = u64各反復のステップ実行と、反復の終わりに達したことを伝える方法を処理します。

この例は、ジェネリックを使用する実際の ほど強力ではありませんがRange、その方法の例を示しています。

ナイトリーRustで動作

Nightly Rustにはジェネレーターがありますが、非常に実験的なものです。作成するには、いくつかの不安定な機能を取り込む必要があります。ただし、Rust 固有の追加がいくつか加えられているため、Python の例にかなり近いように見えます。

// 1.43.0-nightly (2020-02-09 71c7e149e42cb0fc78a8)
#![feature(generators, generator_trait)]

use std::{
    ops::{Generator, GeneratorState},
    pin::Pin,
};

fn firstn(n: u64) -> impl Generator<Yield = u64, Return = ()> {
    move || {
        let mut num = 0;
        while num < n {
            yield num;
            num += 1;
        }
    }
}

現在の Rust のすべてがイテレーターで動作するため、より広いエコシステムで遊ぶために、ジェネレーターをイテレーターに変換するアダプターを作成します。最終的には、そのようなアダプターが標準ライブラリに存在することを期待しています。

struct GeneratorIteratorAdapter<G>(Pin<Box<G>>);

impl<G> GeneratorIteratorAdapter<G>
where
    G: Generator<Return = ()>,
{
    fn new(gen: G) -> Self {
        Self(Box::pin(gen))
    }
}

impl<G> Iterator for GeneratorIteratorAdapter<G>
where
    G: Generator<Return = ()>,
{
    type Item = G::Yield;

    fn next(&mut self) -> Option<Self::Item> {
        match self.0.as_mut().resume(()) {
            GeneratorState::Yielded(x) => Some(x),
            GeneratorState::Complete(_) => None,
        }
    }
}

これで使用できます。

fn main() {
    let generator_iterator = GeneratorIteratorAdapter::new(firstn(1_000_000));
    let sum: u64 = generator_iterator.sum();
    println!("{}", sum);
}

これについて興味深いのは、 の実装よりも強力ではないということですIterator。たとえば、反復子にはsize_hintメソッドがあり、反復子の消費者は残りの要素数を把握できます。collectこれにより、コンテナーに ing する際の最適化が可能になります。ジェネレーターにはそのような情報はありません。

于 2015-05-16T18:13:06.797 に答える
17

Rust 1.0 does not have generator functions, so you'd have to do it manually with explicit iterators.

First, rewrite your Python example as a class with a next() method, since that is closer to the model you're likely to get in Rust. Then you can rewrite it in Rust with a struct that implements the Iterator trait.

You might also be able to use a function that returns a closure to achieve a similar result, but I don't think it would be possible to have that implement the Iterator trait (since it would require being called to generate a new result).

于 2013-05-07T14:12:26.243 に答える