4
votes

Assume the following contrived example:

struct Board {
    squares: Vec<i32>,
}

struct Point<'a> {
    board: &'a Board,
    x: i32,
    y: i32,
}

impl<'a> Point<'a> {
    pub fn neighbors(&self) -> impl Iterator<Item = Point<'a>> {
        [(0, -1), (-1, 0), (1, 0), (1, 0)]
            .iter().map(|(dx, dy)| Point {
                board: self.board,
                x: self.x + dx,
                y: self.y + dy,
            })
    }
}

This doesn't compile because from what I understand the lifetime of the points created in the lambda isn't correct:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:14:25
   |
14 |               .iter().map(|(dx, dy)| Point {
   |  _________________________^
15 | |                 board: self.board,
16 | |                 x: self.x + dx,
17 | |                 y: self.y + dy,
18 | |             })
   | |_____________^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 12:5...
  --> src/main.rs:12:5
   |
12 | /     pub fn neighbors(&self) -> impl Iterator<Item = Point<'a>> {
13 | |         [(0, -1), (-1, 0), (1, 0), (1, 0)]
14 | |             .iter().map(|(dx, dy)| Point {
15 | |                 board: self.board,
...  |
18 | |             })
19 | |     }
   | |_____^
   = note: ...so that the types are compatible:
           expected &&Point<'_>
              found &&Point<'a>
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 11:1...
  --> src/main.rs:11:1
   |
11 | impl<'a> Point<'a> {
   | ^^^^^^^^^^^^^^^^^^
note: ...so that return value is valid for the call
  --> src/main.rs:12:32
   |
12 |     pub fn neighbors(&self) -> impl Iterator<Item = Point<'a>> {
   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I'm a bit lost as to why this is the case though, because it seems like the lifetimes here make sense. A Point's lifetime is caused by the lifetime of the reference to the Board. Thus, a Point<'a> has a reference to a board with lifetime 'a so it should be able to create more Point<'a>s because their board references will have the same lifetime ('a).

But, if I remove the lambda, it works:

impl<'a> Point<'a> {
    pub fn neighbors(&self) -> [Point<'a>; 4] {
        [
            Point { board: self.board, x: self.x    , y: self.y - 1},
            Point { board: self.board, x: self.x - 1, y: self.y    },
            Point { board: self.board, x: self.x + 1, y: self.y    },
            Point { board: self.board, x: self.x    , y: self.y + 1},
        ]
    }
}

So, I suspect the problem lies in the fact that the lambda may be run after the lifetime 'a ends. But, does this mean that I can't lazily produce these points?

tl;dr How do I make the borrow checker happy with a method that lazily creates new structs whose lifetimes are tied to the struct creating them?

3

3 Answers

7
votes

When you have this kind of issue in a method, a good thing to do is to add an explicit lifetime to &self:

pub fn neighbors(&'a self) -> impl Iterator<Item = Point<'a>> {
    [(0, -1), (-1, 0), (1, 0), (1, 0)]
        .iter().map(|(dx, dy)| Point {
            board: self.board,
            x: self.x + dx,
            y: self.y + dy,
        })
}

The error is now better

error[E0373]: closure may outlive the current function, but it borrows `self`, which is owned by the current function
  --> src/main.rs:14:30
   |
14 |             .iter().map(|(dx, dy)| Point {
   |                         ^^^^^^^^^^ may outlive borrowed value `self`
15 |                 board: self.board,
   |                        ---- `self` is borrowed here
help: to force the closure to take ownership of `self` (and any other referenced variables), use the `move` keyword
   |
14 |             .iter().map(move |(dx, dy)| Point {
   |                         ^^^^^^^^^^^^^^^

You then just need to add the move keyword as advised by the compiler, to say to it that you will not use &'a self again.

Note that the lifetime of self has not to be the same as the lifetime of Point. This is better to use this signature:

fn neighbors<'b>(&'b self) -> impl 'b + Iterator<Item = Point<'a>>
3
votes

Both the existing answers (Shepmaster, Boiethios) allow the Points returned by the iterator to outlive the iterator itself. However, it should be possible to build an iterator that even outlives the original Point it was created from, by moving the contents of the Point into it. This version retains the original function signature:

fn neighbors(&self) -> impl Iterator<Item = Point<'a>> {
    let Point { board, x, y } = *self;
    [(0, -1), (-1, 0), (1, 0), (1, 0)]
        .iter().map(move |(dx, dy)| Point {
            board: board,
            x: x + dx,
            y: y + dy,
        })
}

Copying the contents of *self into local variables which are moved into the closure makes it so the closure -- and therefore the returned iterator -- no longer contains any references to self.

Here's something you can do with this version that can't be done otherwise (playground):

let mut p = Point {
    board: &b,
    x: 10,
    y: 12,
};
for n in p.neighbors() {
    p = n;
}

One potential caveat is that if Point contains very large or non-Copy data that can't or shouldn't be moved into the closure, this won't work. In that case you should use the other solution:

fn neighbors<'b>(&'b self) -> impl 'b + Iterator<Item = Point<'a>>
2
votes

I would assign a lifetime to self that is distinct from the lifetime of the Board:

impl<'b> Point<'b> {
    fn neighbors<'a>(&'a self) -> impl Iterator<Item = Point<'b>> + 'a {
        [(0, -1), (-1, 0), (1, 0), (1, 0)]
            .iter().map(move |(dx, dy)| Point {
                board: self.board,
                x: self.x + dx,
                y: self.y + dy,
            })
    }
}

This also requires marking the closure as move to move the &Self value into the closure so that the closure can still access board, x and y when it is advanced. Since immutable references can be copied, this won't prevent the caller from doing anything.

Without the separate lifetimes, the lifetimes of the Points returned from the iterator are artificially limited to the lifetime of the Point that generated them. As an example, this code fails when the lifetimes are unified, but works when they are distinct:

fn example<'b>(board: &'b Board) {
    let _a = {
        let inner = Point { board, x: 0, y: 0 };
        let mut n = inner.neighbors();
        n.next().unwrap()
    };
}

See also: