Леновая генерация последовательности в ржавчине
Как я могу создать то, что другие языки называют ленивой последовательностью или функцией генератора?
В 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?
Ответы
Ответ 1
Rust 1.0 не имеет генераторных функций, поэтому вам придется делать это вручную с помощью явных итераторов.
Сначала перепишите свой пример Python как класс с помощью метода next()
, так как это ближе к модели, которую вы, вероятно, получите в Rust. Затем вы можете переписать его в Rust со структурой, которая реализует черту Iterator
.
Вы также можете использовать функцию, которая возвращает замыкание, чтобы достичь аналогичного результата, но я не думаю, что было бы возможно реализовать этот признак Iterator
(так как это потребовало бы вызова для генерации новый результат).
Ответ 2
У ржавчины есть генераторы, но они высоко экспериментальные и в настоящее время недоступны в стабильной 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 отслеживают состояние для вас. Ржавчина предпочитает очевидность, поэтому мы должны создать собственное состояние и обновить его вручную. Важной частью является реализация Iterator
trait. Мы указываем, что итератор дает значения определенного типа (type Item = u64
), а затем обрабатывает каждую итерацию каждого шага и как сказать, что мы достигли конца итерации.
Этот пример не такой мощный, как реальный Range
, который использует generics, но показывает пример того, как это сделать.
Работает в ночной ржавчине
Ночная ржавчина имеет генераторы, но они очень экспериментальные. Для его создания нужно создать несколько неустойчивых функций. Тем не менее, он выглядит довольно близко к примеру Python, с некоторыми дополнениями, зависящими от ржавчины:
#![feature(generators, generator_trait, conservative_impl_trait)]
use std::ops::{Generator, GeneratorState};
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> {
match self.0.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
входит в контейнер. Генераторы не имеют такой информации.
Ответ 3
Вы можете использовать мою стекю 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);
}
или более просто:
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();