Подтвердить что ты не робот

Леновая генерация последовательности в ржавчине

Как я могу создать то, что другие языки называют ленивой последовательностью или функцией генератора?

В 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?

4b9b3361

Ответ 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();