Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I access a Rust Iterator from Python using PyO3?

I'm quite new with Rust, and my first 'serious' project has involved writing a Python wrapper for a small Rust library using PyO3. This has mostly been quite painless, but I'm struggling to work out how to expose lazy iterators over Rust Vecs to Python code.

So far, I have been collecting the values produced by the iterator and returning a list, which obviously isn't the best solution. Here's some code which illustrates my problem:

use pyo3::prelude::*;

// The Rust Iterator, from the library I'm wrapping.
pub struct RustIterator<'a> {
    position: usize,
    view: &'a Vec<isize>
}

impl<'a> Iterator for RustIterator<'a> {
    type Item = &'a isize;

    fn next(&mut self) -> Option<Self::Item> {
        let result = self.view.get(self.position);
        if let Some(_) = result { self.position += 1 };
        result
    }
}

// The Rust struct, from the library I'm wrapping.
struct RustStruct {
    v: Vec<isize>
}

impl RustStruct {
    fn iter(&self) -> RustIterator {
        RustIterator{ position: 0, view: &self.v }
    }
}

// The Python wrapper class, which exposes the 
// functions of RustStruct in a Python-friendly way.
#[pyclass]
struct PyClass {
    rust_struct: RustStruct,
}

#[pymethods]
impl PyClass {
    #[new]
    fn new(v: Vec<isize>) -> Self {
        let rust_struct = RustStruct { v };
        Self{ rust_struct }
    }

    // This is what I'm doing so far, which works
    // but doesn't iterate lazily.
    fn iter(&self) -> Vec<isize> {
        let mut output_v = Vec::new();
        for item in self.rust_struct.iter() {
            output_v.push(*item);
        }
        output_v
    }
}

I've tried to wrap the RustIterator class with a Python wrapper, but I can't use PyO3's #[pyclass] proc. macro with lifetime parameters. I looked into pyo3::types::PyIterator but this looks like a way to access a Python iterator from Rust rather than the other way around.

How can I access a lazy iterator over RustStruct.v in Python? It's safe to assume that the type contained in the Vec always derives Copy and Clone, and answers which require some code on the Python end are okay (but less ideal).

like image 1000
thesketh Avatar asked May 22 '20 12:05

thesketh


People also ask

What does ITER () do in Rust?

The iter method produces an iterator over immutable references. If we want to create an iterator that takes ownership of v1 and returns owned values, we can call into_iter instead of iter . Similarly, if we want to iterate over mutable references, we can call iter_mut instead of iter .

Does Rust use iterators?

Iterators in Rust You might be familiar with loops like “for loop,” “while loop,” and “for each loop.” In Rust, iterators help us achieve the process of looping. In other languages, you can just start looping through an array of values. In Rust, however, you have to declare an iterator first.

How do you know if an iterator is empty in Rust?

You can make your iterator peekable and peek the first item; if it's None , then the iterator is empty. peek doesn't consume the item1, so the next call to next will return it.


1 Answers

You can make your RustIterator a pyclass and then implement the proper trait (PyIterProtocol) using the rust iter itself.

Not tested, but something like:

#[pyclass]
pub struct RustIterator<'a> {
    position: usize,
    view: &'a Vec<isize>
}

impl<'a> Iterator for RustIterator<'a> {
    type Item = &'a isize;

    fn next(&mut self) -> Option<Self::Item> {
        let result = self.view.get(self.position);
        if let Some(_) = result { self.position += 1 };
        result
    }
}

#[pyproto]
impl PyIterProtocol for Iter {
    fn __next__(mut slf: PyRefMut<Self>) -> IterNextOutput<usize, &'static str> {
        match self.next() {
            Some(value) => IterNextOutput::Yield(value),
            None => IterNextOutput::Return("Ended")
        }
    }
}

like image 72
Netwave Avatar answered Oct 21 '22 11:10

Netwave