Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetimes for method returning iterator of structs with same lifetime

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?

like image 408
Bailey Parker Avatar asked Dec 14 '22 16:12

Bailey Parker


2 Answers

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>>
like image 124
Boiethios Avatar answered Dec 19 '22 11:12

Boiethios


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>>
like image 22
trent Avatar answered Dec 19 '22 10:12

trent