如何创建其他语言称为延迟序列或“生成器”函数?
在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中做类似的事情?
答案 0 :(得分:25)
Rust 有生成器,但它们高度实验性,目前在稳定的Rust中不可用。
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
强大,但展示了如何进行此操作的示例。
夜间生锈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();