Nat*_*anD 24 lazy-sequences rust
如何创建其他语言称为惰性序列或"生成器"函数?
在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))
Run Code Online (Sandbox Code Playgroud)
我怎样才能在Rust中做类似的事情?
She*_*ter 26
Rust 确实有生成器,但它们具有很高的实验性,目前还没有稳定的Rust.
Range处理你的具体例子.你可以使用它的语法糖..:
fn main() {
let sum: u64 = (0..1_000_000).sum();
println!("{}", sum)
}
Run Code Online (Sandbox Code Playgroud)
如果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)
}
Run Code Online (Sandbox Code Playgroud)
胆量是相同的,但比Python版本更明确.值得注意的是,Python的生成器会为您跟踪状态.Rust更喜欢显式,所以我们必须创建自己的状态并手动更新它.重要的部分是Iterator特质的实施.我们指定迭代器产生特定类型(type Item = u64)的值,然后处理每个迭代的步进以及如何告诉我们已经到达迭代的末尾.
这个例子不像Range使用泛型的真实那样强大,但是展示了如何去做它的例子.
Nightly Rust 确实有发电机,但它们具有很高的实验性.您需要引入一些不稳定的功能来创建一个.但是,它看起来非常接近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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
由于当前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,
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以使用它:
fn main() {
let generator_iterator = GeneratorIteratorAdapter(firstn(1_000_000));
let sum: u64 = generator_iterator.sum();
println!("{}", sum);
}
Run Code Online (Sandbox Code Playgroud)
有趣的是,它的功能不如实现Iterator.例如,迭代器具有该size_hint方法,该方法允许迭代器的使用者知道剩余的元素数量.这允许在collect进入容器时进行优化.发电机没有任何此类信息.
sni*_*nip 19
从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);
}
Run Code Online (Sandbox Code Playgroud)
std::iter::successors也可用。它不太通用,但可能更容易使用,因为您只需显式传递种子值。(即它需要一个初始值T和一个后继函数FnMut(&T) -> Option<T>来创建一个Iterator<T>)
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
}
},
)
}
Run Code Online (Sandbox Code Playgroud)
但是,Shepmaster 的说明也适用于这些实用程序。(tldr:经常手动滚动Iterator的内存效率更高)
有趣的是,它不如
Iterator. 例如,迭代器有size_hint方法,它允许迭代器的使用者知道剩余多少元素。这允许在collect进入容器时进行优化。生成器没有任何此类信息。
(注意:返回impl是 Rust 2018 的一项功能。详细信息请参阅版本指南)
Luc*_*ian 12
Rust 1.0没有生成器函数,因此您必须使用显式迭代器手动执行此操作.
首先,重写你的Python举例来说,如一个类next()的方法,因为这是更接近你很可能会生锈获取模型.然后,您可以使用实现Iterator特征的结构在Rust中重写它.
您也可以使用一个返回闭包的函数来实现类似的结果,但我认为不可能实现该Iterator特征(因为它需要被调用以生成新的结果).
小智 5
您可以使用我的 stackful 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);
}
Run Code Online (Sandbox Code Playgroud)
或者更简单地说:
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();
Run Code Online (Sandbox Code Playgroud)