0

In my Rust application, I'd like to store a Vector of structs containing closures which are called later.

So far I have something like this (for a Tetris game):

pub struct TimestepTimer {
    pub frameForExecution: i32,
    pub func: Box<dyn Fn() -> ()>
}

pub struct Game {
    pub frame: i32,
    pub timers: Vec<TimestepTimer>
}

impl Game {
    fn add_timer (&mut self, framesFromNow: i32, func: Box<dyn Fn() -> ()>) {
        self.timers.push(TimestepTimer {
            frameForExecution: self.frame + framesFromNow,
            func
        });
    }

    /* Example of a function that would use add_timer */
    pub fn begin_gravity(&mut self) {
        self.add_timer(30, Box::new(|| {
            self.move_down();
            // Gravity happens repeatedly
            self.begin_gravity();
        }));
    }

    // Later on would be a function that checks the frame no.
    // and calls the timers' closures which are due
}

This doesn't work at the moment due to E0495, saying the reference created by Box::new cannot outlive the begin_gravity function call, but also 'must be valid for the static lifetime'

I'm quite new to Rust, so this might not at all be the idiomatic solution.

Every different solution I try seems to be met by a different compiler error (though I'm aware that's my fault, not the compiler's)

deeBo
  • 836
  • 11
  • 24
  • This is basically a duplicate of [this question](https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct): your closure keeps a reference to `self` (so that it can call `self.move_down()` and `self.begin_gravity()`) and you're trying to store it inside `self` → that's impossible. – Jmb Apr 06 '20 at 12:49
  • You can get a similar effect if you change your closure to take a `&mut Game` argument and operate on that: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=54f05bb7f9d9f2ab6cb1b7b9b461a6e0) – Jmb Apr 06 '20 at 12:52
  • @Jmb that would be an appropriate answer for this use case. – mcarton Apr 06 '20 at 13:07
  • Thanks for your help so far. I attempted a similar solution to what Jmb explained before I posted the question, but [my implementation](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=aa8805b143871be9c9920f591f198df9) of actually calling the closures caused E0502 – deeBo Apr 06 '20 at 13:46

1 Answers1

2

This is basically a duplicate of this question: your closure keeps a reference to self (so that it can call self.move_down() and self.begin_gravity()) and you're trying to store it inside self → that's impossible.

You can get a similar effect if you change your closure to take a &mut Game argument and operate on that:

pub struct TimestepTimer {
    pub frameForExecution: i32,
    pub func: Box<dyn Fn(&mut Game) -> ()>
}

pub struct Game {
    pub frame: i32,
    pub timers: Vec<TimestepTimer>
}

impl Game {
    fn add_timer (&mut self, framesFromNow: i32, func: Box<dyn Fn(&mut Game) -> ()>) {
        self.timers.push(TimestepTimer {
            frameForExecution: self.frame + framesFromNow,
            func
        });
    }

    /* Example of a function that would use add_timer */
    pub fn begin_gravity(&mut self) {
        self.add_timer(30, Box::new(|s: &mut Game| {
            // self.move_down();
            // Gravity happens repeatedly
            s.begin_gravity();
        }));
    }

    // Later on would be a function that checks the frame no.
    // and calls the timers' closures which are due
}

Playground

But now you will get into trouble when calling the timers because you will need a reference to self.timers to iterate over it, and at the same time you will need to pass a mutable reference to self into the closures, but the compiler won't allow you to have an immutable reference to self.timers and a mutable reference to self at the same time. If you really want to keep your timers in a Vec, the easiest way to solve that will be to swap out timers with an empty vector and repopulate as you go:

pub fn step (&mut self) {
    self.frame += 1;
    let mut timers = vec![];
    std::mem::swap (&mut self.timers, &mut timers);
    for t in timers {
        if self.frame > t.frameForExecution {
            (t.func)(self);
        } else {
            self.timers.push (t);
        }
    }
}

Playground

However it would probably be better to store the timers in a BinaryHeap which allows a much cleaner and more efficient execution loop:

pub fn step (&mut self) {
    self.frame += 1;
    while let Some (t) = self.timers.peek() {
        if t.frameForExecution >= self.frame { break; }
        // `unwrap` is ok here because we know from the `peek` that
        // there is at least one timer in `timers`
        let t = self.timers.pop().unwrap();
        (t.func)(self);
    }
}

Playground

This will require implementing Ord along with a few other traits on TimestepTimer:

impl Ord for TimestepTimer {
    fn cmp (&self, other: &Self) -> Ordering {
        // Note the swapped order for `self` and `other` so that the `BinaryHeap`
        // will return the earlier timers first.
        other.frameForExecution.cmp (&self.frameForExecution)
    }
}

impl PartialOrd for TimestepTimer {
    fn partial_cmp (&self, other: &Self) -> Option<Ordering> {
        Some (self.cmp (other))
    }
}

impl PartialEq for TimestepTimer {
    fn eq (&self, other: &Self) -> bool {
        self.frameForExecution == other.frameForExecution
    }
}

impl Eq for TimestepTimer {}
Jmb
  • 18,893
  • 2
  • 28
  • 55