BitTickler
BitTickler

Reputation: 11875

Lifetime of a closure stored in a struct

I tried to apply the answers of this question to my situation, but failed to do so. I assume, what I try to do is much simpler.

Before I explain, first a bit of code:

type Coord = (usize, usize);

pub struct SquareIterator {
    upper_bounds: Coord,
    direction: Coord,
    predicate: Box<dyn Fn(Coord) -> bool>,
    current: Coord,
}

impl SquareIterator {
    pub fn new<P>(
        start_square: Coord,
        direction: Coord,
        board_size: Coord,
        predicate: P,
    ) -> SquareIterator
    where
        P: Fn(Coord) -> bool,
    {
        SquareIterator {
            upper_bounds: board_size,
            direction,
            predicate: Box::new(predicate),
            current: start_square,
        }
    }
}

impl Iterator for SquareIterator {
    type Item = Coord;

    fn next(&mut self) -> Option<Self::Item> {
        let next = (
            self.current.0 + self.direction.0,
            self.current.1 + self.direction.1,
        );
        if next.0 >= self.upper_bounds.0 || next.1 >= self.upper_bounds.1 {
            None
        } else if (self.predicate)(next) {
            self.current = next;
            Some(self.current)
        } else {
            None
        }
    }
}

It is about the predicate closure, which is supposed to live as long as the containing struct SquareIterator lives.

Application code, using this looks along the lines of:

struct Board {
    // ... some members ...
}
impl Board {
    pub fn queen_moves(&self, queen_coord: (u8, Coord)) -> SquareIterator {
        SquareIterator::new(queen_coord.1, (0, 1), self.board_size(), |coord| {
            self.squares[coord.1][coord.0] == sv::EMPTY
        })
    }
}

After fiddling for two hours, reading up the lifetimes chapter in the rust book a couple of times, looking at the above mentioned other stackoverflow question, I am still at a loss as to how to teach Rust, that my closure will be alive as long as the iterator...

While I am aware, that there is lifetime stuff missing, the current ccode yields the error:

> error[E0310]: the parameter type `P` may not live long enough
  --> amazons-engine/src/lib.rs:33:18
   |
33 |       predicate: Box::new(predicate),
   |                  ^^^^^^^^^^^^^^^^^^^ ...so that the type `P` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound...
   |
29 |     P: Fn(Coord) -> bool + 'static, {
   |                          +++++++++

For more information about this error, try `rustc --explain E0310`.

So, how to do it?

Upvotes: 0

Views: 428

Answers (1)

cdhowie
cdhowie

Reputation: 168998

You need to tell the compiler that SquareIterator might be bound to a specific lifetime. Closures that borrow from their environment aren't 'static, but a struct without a lifetime parameter is assumed to reference nothing (e.g. be 'static) itself. This makes your closure that borrows self incompatible with the implied-static lifetime.

Step one is to tell the compiler that SquareIterator might refer to an external lifetime, and that this lifetime belongs to the closure type:

pub struct SquareIterator<'a> {
    upper_bounds: Coord,
    direction: Coord,
    predicate: Box<dyn 'a + Fn(Coord) -> bool>,
    current: Coord,
}

Step two is to adjust the constructor function similarly, which binds together the lifetime of the supplied closure with that of the returned SquareIterator, allowing the compiler to infer 'a for you:

impl SquareIterator<'_> {
    pub fn new<'a, P>(
        start_square: Coord,
        direction: Coord,
        board_size: Coord,
        predicate: P,
    ) -> SquareIterator<'a>
    where
        P: 'a + Fn(Coord) -> bool,
    { ... }
}

Finally, the impl Iterator block needs to include a lifetime, but we can use the "please infer me" lifetime '_ since the implementation doesn't depend on it:

impl Iterator for SquareIterator<'_> { ... }

(Playground, which has some placeholder types/methods to allow it to compile.)

Upvotes: 2

Related Questions