繁体   English   中英

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

[英]Lifetimes for method returning iterator of structs with same lifetime

假设以下人为的例子:

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,
            })
    }
}

这不能编译,因为根据我的理解,在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>> {
   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

我有点迷失为什么会出现这种情况,因为看起来这里的生命有意义。 Point的生命周期是由Board提交的有效期引起的。 因此, Point<'a>引用具有生命周期'a的板,因此它应该能够创建更多的Point<'a>因为它们的板引用将具有相同的生命周期( 'a )。

但是,如果我删除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},
        ]
    }
}

所以,我怀疑问题出在事实拉姆达一生可以后运行'a结束。 但是,这是否意味着我不能懒洋洋地产生这些观点?

tl; dr如何让借用检查器满足于一种懒惰地创建新结构的方法,这些结构的生命周期与创建它们的结构相关联?

当你在一个方法中遇到这种问题时,一件好事就是为&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,
        })
}

错误现在更好

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 {
   |                         ^^^^^^^^^^^^^^^

然后你只需要按照编译器的建议添加move关键字,就可以说你不会再使用&'a self

请注意, self的生命周期不能与Point的生命周期相同。 最好使用此签名:

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

现有的答案( ShepmasterBoiethios )都允许迭代器返回的Point比迭代器本身更长。 但是,应该可以通过将Point的内容移动到其中来构建迭代器,该迭代器甚至比创建它的原始Point更长。 此版本保留原始功能签名:

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,
        })
}

*self的内容复制到移动到闭包中的局部变量使得闭包 - 因此返回的迭代器 - 不再包含对self任何引用。

这是你可以用这个版本做的事情,否则无法做到( 游乐场 ):

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

一个潜在的警告是,如果Point包含无法或不应该移入闭包的非常大或非Copy数据,这将无效。 在这种情况下,您应该使用其他解决方案:

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

我会为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,
            })
    }
}

这也需要将闭合标记为move以将&Self值移动到闭合中,以便闭合仍然可以在它前进时接近board xy 由于可以复制不可变引用,因此不会阻止调用者执行任何操作。

没有单独的生命周期,从迭代器返回的Point的生命周期被人为地限制为生成它们的Point的生命周期。 例如,此代码在生命周期统一时失败,但在它们不同时起作用:

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

也可以看看:

暂无
暂无

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

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