在 Rust 中存储包含闭包的结构向量

Storing a Vector of structs containing closures in Rust

在我的 Rust 应用程序中,我想存储一个 Vector 结构,其中包含稍后调用的闭包。

到目前为止我有这样的东西(对于俄罗斯方块游戏):

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
}

由于 E0495,目前这不起作用,表示 Box::new 创建的引用不能超过 begin_gravity 函数调用,而且 'must be valid for the static lifetime'

我对 Rust 很陌生,所以这可能根本不是惯用的解决方案。

我尝试的每个不同的解决方案似乎都遇到了不同的编译器错误(尽管我知道这是我的错,而不是编译器的错)

这基本上是 的副本:您的闭包保留对 self 的引用(以便它可以调用 self.move_down()self.begin_gravity())并且您是试图将它存储在自己内部 → 那是不可能的。

如果您将闭包更改为采用 &mut Game 参数并对其进行操作,您可以获得类似的效果:

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

但是现在你在调用定时器时会遇到麻烦,因为你需要引用 self.timers 来迭代它,同时你需要传递一个可变引用给 self 到闭包中,但编译器不允许您同时拥有对 self.timers 的不可变引用和对 self 的可变引用。如果你真的想把你的计时器保持在 Vec 中,解决这个问题的最简单方法是用一个空向量换出 timers 并在你去的时候重新填充:

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

然而,将计时器存储在 BinaryHeap 中可能会更好,这样可以实现更清晰、更高效的执行循环:

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

这将需要在 TimestepTimer 上实施 Ord 以及一些其他特征:

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 {}