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确实有生成器,但它们是高度实验性的。您需要引入一些不稳定的功能来创建一个。但是,它看起来非常接近 Python 示例,并添加了一些特定于 Rust 的内容:

// 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进入容器时进行优化。生成器没有任何此类信息。

于 2015-05-16T18:13:06.797 回答
28

Rust 1.34稳定版开始,您可以使用方便的std::iter::from_fn实用程序。它不是协程(即您仍然必须每次都返回),但至少它使您免于定义另一个结构。

from_fn接受一个闭包FnMut() -> Option<T>并反复调用它来创建一个Iterator<T>. 在伪 Python 中,def from_fn(f): while (val := f()) is not None: yield val.

// -> Box<dyn std::iter::Iterator<Item=u64>> in Rust 2015
fn firstn(n: u64) -> impl std::iter::Iterator<Item = u64> {
    let mut num = 0;
    std::iter::from_fn(move || {
        let result;
        if num < n {
            result = Some(num);
            num += 1
        } else {
            result = None
        }
        result
    })
}

fn main() {
  let sum_of_first_n = firstn(1000000).sum::<u64>();
  println!("sum(0 to 999999): {}", sum_of_first_n);
}

std::iter::successors也可用。它不太通用,但可能更容易使用,因为您只是明确地传递种子值。在伪 Python 中:def successors(seed, f): while seed is not None: yield seed; seed = f(seed).

fn firstn(n: u64) -> impl std::iter::Iterator<Item = u64> {
    std::iter::successors(
        Some(0),
        move |&num| {
            if num + 1 < n {
                Some(num + 1)
            } else {
                None
            }
        },
    )
}

但是,Shepmaster 的说明也适用于这些实用程序。(tldr:通常手动滚动Iterator的 s 内存效率更高)

有趣的是,它不如Iterator. 例如,迭代器有一个size_hint方法,它允许迭代器的使用者知道剩余多少元素。这允许在collect进入容器时进行优化。生成器没有任何此类信息。

(注意:returningimpl是 Rust 2018 的一个特性。详见版本指南

于 2019-11-03T18:10:16.683 回答
17

Rust 1.0 没有生成器函数,因此您必须使用显式迭代器手动完成。

首先,将您的 Python 示例重写为具有next()方法的类,因为这更接近您可能在 Rust 中获得的模型。然后你可以用实现Iteratortrait 的结构在 Rust 中重写它。

您也许还可以使用返回闭包的函数来实现类似的结果,但我认为不可能实现该Iterator特征(因为需要调用它来生成新结果)。

于 2013-05-07T14:12:26.243 回答
4

你可以使用我的支持稳定 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();
于 2017-12-18T05:28:44.027 回答