web-dev-qa-db-ja.com

Rustでのレイジーシーケンス生成

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

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

# 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で同様のことを行うにはどうすればよいですか?

27
NathanD

Rust 1.0にはジェネレーター関数がないため、 明示的なイテレーター を使用して手動で行う必要があります。

まず、Pythonの例をnext()メソッドを持つクラスとして書き直します。これは、Rustで取得する可能性のあるモデルに近いためです。次に、書き直すことができます。 in Rust Iterator特性を実装する構造体を使用します。

同様の結果を達成するためにクロージャを返す関数を使用することもできるかもしれませんが、それをIteratorトレイトに実装させることは不可能だと思います(生成するために呼び出される必要があるため)新しい結果)。

12
Lucian

Rust doesジェネレーターはありますが、非常に実験的であり、現在安定した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は明示性を優先するため、独自の状態を作成して手動で更新する必要があります。重要な部分は、 Iterator trait の実装です。反復子が特定のタイプの値を生成するように指定します(type Item = u64)次に、各反復のステップと、反復の終わりに到達したことを確認する方法を扱います。

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

毎晩の錆で動作します

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

#![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>(G);

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

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

これで使用できます。

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

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

25
Shepmaster

安定したRustをサポートする私のスタックフルRust ジェネレータライブラリ を使用できます:

#[macro_use]
extern crate generator;
use generator::{Generator, Gn};

fn firstn(n: usize) -> Generator<'static, (), usize> {
    Gn::new_scoped(move |mut s| {
        let mut num = 0;
        while num < n {
            s.yield_(num);
            num += 1;
        }
        done!();
    })
}

fn main() {
    let sum_of_first_n: usize = firstn(1000000).sum();
    println!("sum ={}", sum_of_first_n);
}

またはもっと簡単に:

let n = 100000;
let range = Gn::new_scoped(move |mut s| {
    let mut num = 0;
    while num < n {
        s.yield_(num);
        num += 1;
    }
    done!();
});

let sum: usize = range.sum();
3
Xudong Huang