简体   繁体   English

返回具有相同生命周期的结构的迭代器的方法的生命周期

[英]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: 这不能编译,因为根据我的理解,在lambda中创建的点的生命周期是不正确的:

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 . Point的生命周期是由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 ). 因此, Point<'a>引用具有生命周期'a的板,因此它应该能够创建更多的Point<'a>因为它们的板引用将具有相同的生命周期( 'a )。

But, if I remove the lambda, it works: 但是,如果我删除lambda,它的工作原理是:

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. 所以,我怀疑问题出在事实拉姆达一生可以后运行'a结束。 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? tl; dr如何让借用检查器满足于一种懒惰地创建新结构的方法,这些结构的生命周期与创建它们的结构相关联?

When you have this kind of issue in a method, a good thing to do is to add an explicit lifetime to &self : 当你在一个方法中遇到这种问题时,一件好事就是为&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. 然后你只需要按照编译器的建议添加move关键字,就可以说你不会再使用&'a self

Note that the lifetime of self has not to be the same as the lifetime of Point . 请注意, self的生命周期不能与Point的生命周期相同。 This is better to use this signature: 最好使用此签名:

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

Both the existing answers ( Shepmaster , Boiethios ) allow the Point s returned by the iterator to outlive the iterator itself. 现有的答案( ShepmasterBoiethios )都允许迭代器返回的Point比迭代器本身更长。 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. 但是,应该可以通过将Point的内容移动到其中来构建迭代器,该迭代器甚至比创建它的原始Point更长。 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 . *self的内容复制到移动到闭包中的局部变量使得闭包 - 因此返回的迭代器 - 不再包含对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. 一个潜在的警告是,如果Point包含无法或不应该移入闭包的非常大或非Copy数据,这将无效。 In that case you should use the other solution: 在这种情况下,您应该使用其他解决方案:

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

I would assign a lifetime to self that is distinct from the lifetime of the Board : 我会为self分配一个与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. 这也需要将闭合标记为move以将&Self值移动到闭合中,以便闭合仍然可以在它前进时接近board xy Since immutable references can be copied, this won't prevent the caller from doing anything. 由于可以复制不可变引用,因此不会阻止调用者执行任何操作。

Without the separate lifetimes, the lifetimes of the Point s returned from the iterator are artificially limited to the lifetime of the Point that generated them. 没有单独的生命周期,从迭代器返回的Point的生命周期被人为地限制为生成它们的Point的生命周期。 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: 也可以看看:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM