4

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?

Boiethios
  • 38,438
  • 19
  • 134
  • 183
Bailey Parker
  • 15,599
  • 5
  • 53
  • 91

3 Answers3

7

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>>
Boiethios
  • 38,438
  • 19
  • 134
  • 183
  • Indeed! It's a shame that the error message for both isn't the same (because the one you provoked is definitely a lot more explicit that "other referenced variables" could be the problem). Thanks so much! (Also as a note, I was able to get this working with an array by only adding the `move` and the lifetime to `self` in 1.26; I'm unsure if you were implying that I needed to keep the vector) – Bailey Parker May 15 '18 at 06:25
  • @BaileyParker I was implying that; I cannot explain why this compiles. My answer is still incomplete, then. – Boiethios May 15 '18 at 06:41
  • @BaileyParker I removed the stuff about the array – Boiethios May 15 '18 at 07:25
  • @BaileyParker FYI, I asked a question about this https://stackoverflow.com/questions/50345139/do-this-code-returns-a-reference-to-a-local-variable – Boiethios May 15 '18 at 07:59
3

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>>
trent
  • 25,033
  • 7
  • 51
  • 90
  • @Boiethios There are cases where this won't work and one might have to fall back to a more restrictive signature. I've added a disclaimer – trent May 15 '18 at 14:44
  • This is a good point. I hadn't much considered usage context. For my particular case, it may make more sense to not make point `Copy`, but if it was it looks like you're correct in that I should store the fields on `self` that I need in locals. Bioethios had the root cause right, but this is a good point. Wish I could give the check to both :) – Bailey Parker May 15 '18 at 16:40
2

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Interesting, I hadn't considered this. Just to be sure, the reason why your example fails is because board has lifetime `'b` but `inner`'s lifetime is tied to the scope assigned to `_a` (which are distinct)? – Bailey Parker May 15 '18 at 16:44
  • @BaileyParker it will fail because the unified lifetimes (`fn neighbors(&'a self) -> impl Iterator>`) means that the returned `Point`s might contain a reference to `self` / the origin `Point`. – Shepmaster May 15 '18 at 16:58