Help using Futures to get behavior from Python example


#1

Hi there,

I’m working on a project on stable Rust, and I’m in a situation where I need something like generators / coroutines. But, being on stable, I’d like to do this with just vanilla futures. I think it will involve implementing my own Executor, and I’d love some help on how to get started.

Here’s some Python that shows the kind of thing I’d like to do:

from itertools import zip_longest

class Thing:
    def __init__(self, name):
        self.name = name

    def go(self, ticker): # needs &mut Ticker
        # will call various mut method of ticker
        ticker.tick(self.name)
        yield
        ticker.tick(self.name)
        yield
        ticker.tick(self.name)
        yield

class Ticker:
    def __init__(self):
        self.num = 0

    def tick(self, name):
        print("{}: {}".format(name, self.num))

    def cycle(self, things):
        iters = [thing.go(self) for thing in things]
        for _ in zip_longest(*iters):
            self.num += 1

Ticker().cycle([
    Thing("a"),
    Thing("b"),
    Thing("c"),
])

This will print the following:

a: 0
b: 0
c: 0
a: 1
b: 1
c: 1
a: 2
b: 2
c: 2

Ticker is sort of serving as the Executor, and it’s cycling through the Things it has, doing one unit of work each time. The go method of Thing requires exclusive access to the “Context” (something like &mut Ticker), but only while it’s running. While yielded, other Things can do part of their go work.

Any help on how to go about this would be appreciated!


#2

Why do you want to impement it as a future? Wouldn’t an iterator or stream be more appropriate?

In any case, you can do something like this:


struct Thing {
    num: u32
}

impl Future for Thing {
    type Item = ();
    type Error = ();

    fn poll(&mut self, _ctx: &mut task::Context) -> Result<Async<()>, ()> {
        println!("{}", self.num);
        self.num += 1;
        if self.num > 2 {
            Ok(Async::Ready(()))
        } else {
            Ok(Async::Pending)
        }
    }
}

#3

Thanks for the reply!

I neglected to show in my example that I’d really want Thing to be trait. So many types would implement the go method, potentially requiring different numbers of ticks.

I think a stream might be what I’m looking for. It seems that you’d define the go method (of type Stream<&mut Ticker> -> Future) by using combinators on the input stream to produce a future that does what you want to the ticker. This way, you don’t have to rely on the number of times the executor polled the future.

I don’t see how an iterator would help, as the next function would block. The key thing that I’m not sure how to do is to set up the executor such that it ticks off the Things in a round-robin fashion.