Lazy sequence generation in Rust
如何创建其他语言称为惰性序列或"生成器"函数的语言?
在Python中,如下面的示例(来自Python的文档)所示,我可以使用
1 2 3 4 5 6 7 8 | # 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中做类似的事情?
Rust确实有生成器,但是它们是高度实验性的,目前尚无法在稳定的Rust中使用。
在稳定的Rust 1.0及更高版本中工作
1 2 3 4 | fn main() { let sum: u64 = (0..1_000_000).sum(); println!("{}", sum) } |
如果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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更喜欢显式,因此我们必须创建自己的状态并手动更新。重要的部分是
该示例不如使用泛型的真正
可在每晚Rust中使用
每晚Rust都有发电机,但是它们是高度实验性的。您需要引入一些不稳定的功能才能创建一个。但是,它看起来非常类似于Python示例,其中包含一些Rust特定的附加功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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中的所有内容都在迭代器上运行,因此我们创建了一个适配器,可将生成器转换为迭代器,以便与更广泛的生态系统一起使用。我希望这样的适配器最终会出现在标准库中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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, } } } |
现在我们可以使用它:
1 2 3 4 5 | fn main() { let generator_iterator = GeneratorIteratorAdapter::new(firstn(1_000_000)); let sum: u64 = generator_iterator.sum(); println!("{}", sum); } |
有趣的是,它没有
Rust 1.0没有生成器功能,因此您必须使用显式迭代器手动进行操作。
首先,使用
您也许还可以使用返回闭包的函数来实现类似的结果,但我认为不可能实现
从Rust 1.34稳定版开始,您具有方便的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // -> 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); } |
1 2 3 4 5 6 7 8 9 10 11 12 | 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:通常,手动滚动
What's interesting about this is that it's less powerful than an implementation of
Iterator . For example, iterators have thesize_hint method, which allows consumers of the iterator to have an idea of how many elements are remaining. This allows optimizations whencollect ing into a container. Generators do not have any such information.
(注意:返回
您可以使用支持稳定Rust的堆栈式Rust生成器库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #[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); } |
或更简单地说:
1 2 3 4 5 6 7 8 9 10 11 | 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(); |