在展开嵌套的迭代器时指定生命周期

3

我正在编写一个函数,它应该接收几个向量并产生它们的笛卡尔积(所有的配对组合)作为按行主序的向量。换句话说,如果我有:

let x_coords = vec![1, 2, 3];
let y_coords = vec![4, 5, 6];

我想要制作

vec![ [1,4], [1,5], [1,6], [2,4], [2,5], [2,6], [3,4], [3,5], [3,6] ]

这似乎是使用.flat_map()的完美工作:

fn main() {
    let x_coords = vec![1, 2, 3];
    let y_coords = vec![4, 5, 6];

    let coord_vec: Vec<[isize; 2]> =
        x_coords.iter().map(|&x_coord| {
            y_coords.iter().map(|&y_coord| {
                [x_coord, y_coord]
            })
        }).flat_map(|s| s).collect();

    // Expecting to get: vec![ [1,4], [1,5], [1,6], [2,4], [2,5], [2,6], [3,4], [3,5], [3,6] ]
    println!("{:?}", &coord_vec);
}

但这样是行不通的,因为&x_coord的生命周期不够长。根据编译器的说法,它最终会进入y_coords映射中,然后永远无法重新出来。
我尝试在闭包中使用 .clone() move ,但编译器以多个 Note: 行的形式给了我一个奇怪而又不清晰的讲解。
我对flat_map完全没有头绪,还是有救吗?
1个回答

6

解决方案

您已经非常接近了!这个是可行的:

let coord_vec: Vec<_> = x_coords.iter()
    .flat_map(|&x_coord| {
        y_coords.iter().map(move |&y_coord| {
        //                  ^^^^
            [x_coord, y_coord]
        })
    })
    .collect();

我添加的唯一内容是在内部闭包前面加上了move关键字。为什么?让我们试着理解编译器下面的想法!
不过,需要注意以下几点:
  • 我将map重命名为flat_map并删除了第二个flat_map调用...你让自己的生活变得比必要的更复杂了;-)
  • 我省略了coord_vec的类型注释的一部分,因为它不是必需的。

解释

类型x_coordi32(或任何其他整数)。不是引用或其他,而是直接的值。这意味着x_coord由封闭函数拥有,该函数恰好是一个闭包,特别是您传递给flat_map的“外部”闭包。因此,x_coord仅存在于闭包内部,而不再存在。这就是编译器告诉您的。到目前为止还好。

当您定义第二个闭包(“内部”闭包)时,您访问环境,特别是x_coord。现在重要的问题是:闭包如何访问其环境?它可以使用不可变引用、可变引用和值来访问。Rust编译器确定需要什么类型的环境访问,并选择“最不侵入性”的选项。让我们看看您的代码:编译器发现闭包只需要不可变地借用环境(因为i32Copy,因此闭包可以轻松地将其&i32转换为i32)。

但在这种情况下,编译器的推理是错误的!闭包借用其环境会导致有限的生命周期。而在这种情况下,我们需要闭包的寿命比它能够承受的时间更长,因此出现了错误。

通过添加move,我们强制编译器通过值(转移所有权)将环境传递给闭包。这样,闭包就不再借用任何东西,可以永远存在(满足'static)。


我感觉我的解释很糟糕,但是文字已经变得很长了,我再也想不出什么了。这是一个棘手的话题,在一个SO答案中解释它是很多的。也许我会写一篇关于这个的博客文章...对于“不是最好的”我很抱歉:/ - Lukas Kalbertodt
Rust编译器确定所需的环境访问类型,并选择符合要求的“最不侵入性”选项。move闭包始终以值方式进行捕获,非move闭包则总是通过引用进行捕获(如果必要,则使用可变借用,否则使用不可变借用)。 - Francis Gagné
1
@FrancisGagné 我也曾经这样想过很长时间。但是,闭包可以通过值获取它们的环境,而不需要 move请看这里 - Lukas Kalbertodt
我想我也没有真正理解flat_map应该如何工作。这是一个很棒的答案! - bright-star
@FrancisGagné 是的;如果闭包中的任何函数或方法通过值获取封闭变量,编译器将知道需要进行move操作。但是,如果一个方法/函数只需要通过引用获取它,那么它只会到达那里。move强制所有变量都被移入。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接