Rust中的延迟序列生成

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.

适用于稳定的Rust 1.0及以上版本

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进入容器时进行优化.发电机没有任何此类信息.

  • “发电机没有任何此类信息。” 这就是重点。生成器的主要用例之一是能够对无限序列进行操作,而无需将无限序列保存在内存中。这种惰性计算有很多应用。那么有什么方法可以避免将其转换回迭代器呢? (2认同)
  • 值得注意的是,新的 Rust 版本具有不稳定的 [`std::iter::from_generator()`](https://doc.rust-lang.org/stable/std/iter/fn.from_generator.html),所以不需要自己编写迭代器适配器。https://play.rust-lang.org/?version=nightly&amp;mode=debug&amp;edition=2021&amp;gist=142447451f94ed2435c35da3e73c9026。 (2认同)

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特征(因为它需要被调用以生成新的结果).

  • 根据确切的顺序,有几个可以使用的内置迭代器,例如[`Unfoldr`](http://static.rust-lang.org/doc/core/iterator.html#struct-unfoldriterator),或者[`Counter`](http://static.rust-lang.org/doc/core/iterator.html#struct-counter)加上[`scan`](http://static.rust-lang.org/ doc/core/iterator.html#method-scan)(和/或任何其他组合函数.(不幸的是,除了类型之外没有其他文档.) (3认同)

小智 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)