Rust中的延迟序列生成

时间:2013-05-07 14:02:23

标签: rust lazy-sequences

如何创建其他语言称为延迟序列或“生成器”函数?

在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 个答案:

答案 0 :(得分:25)

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更喜欢显式,所以我们必须创建自己的状态并手动更新它。重要的部分是Iterator trait的实施。我们指定迭代器产生特定类型(type Item = u64)的值,然后处理每个迭代的步进以及如何判断我们已经到达迭代结束。

此示例不如使用泛型的真实Range强大,但展示了如何进行此操作的示例。

适用于夜间Rust

夜间生锈does have generators,但他们高度实验性。您需要引入一些不稳定的功能来创建一个。但是,它看起来漂亮接近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方法,该方法允许迭代器的使用者知道剩余的元素数量。这允许在collect进入容器时进行优化。发电机没有任何此类信息。

答案 1 :(得分:12)

Rust 1.0没有生成器功能,因此您必须使用explicit iterators手动执行此操作。

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

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

答案 2 :(得分:4)

Rust 1.34稳定版开始,您拥有便捷的std::iter::from_fn实用程序。它不是协程(也就是说,您每次仍然必须返回),但是至少它使您不必定义另一个结构。

from_fn接受闭包FnMut() -> Option<T>,并反复调用它以创建Iterator<T>

// -> 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也可用。这种方法不太通用,但使用起来可能会更容易一些,因为您只需显式传递种子值即可。

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的注释也适用于这些实用程序。

  

有趣的是,它不如Iterator的实现强大。例如,迭代器具有size_hint方法,该方法使迭代器的使用者可以了解剩余多少个元素。 collect放入容器时可以进行优化。生成器没有任何此类信息。

(注意:impl是Rust 2018的一项功能。有关详细信息,请参见Edition Guide

答案 3 :(得分:3)

You can use my stackful Rust generator library which supports stable 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);
}

or more simply:

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