嵌套的lambda表达式中变量未能存活导致借用检查错误

8

我在嵌套的lambda中遇到了一个错误。

let rows = vec![
    vec![3, 6, 2, 8, 9, 0],
    vec![0, 0, 1, 4, 5, 1],
];

let pair_sums = rows.iter()
    .flat_map(|row| {
        (0 ..= row.len()).map(|i| row[i] + row[i + 1])
    })
    .collect::<Vec<_>>();

println!("{:?}", pair_sums);

error[E0597]: `row` does not live long enough
  --> src/main.rs:9:40
   |
9  |             (0..row.len() - 1).map(|i| row[i] + row[i + 1])
   |                                    --- ^^^ does not live long enough
   |                                    |
   |                                    capture occurs here
10 |         })
   |         - borrowed value only lives until here
11 |         .collect::<Vec<_>>();
   |                            - borrowed value needs to live until here

我有点明白为什么会发生这种情况,可以通过将row的值传递到内部lambda函数来解决:

let pair_sums = rows.iter()
    .flat_map(|row| { 
        (0 ..= row.len()).zip(iter::repeat(row))
            .map(|(i, row)| row[i] + row[i + 1])
    })
    .collect::<Vec<_>>();

这很糟糕,不是最好的解决方案。我如何在不显式传递它们的情况下引用父作用域中的变量?

1个回答

10

这里的关键在于闭包如何捕获变量:如果闭包内容允许,它们将对变量采取引用方式进行捕获,而不考虑变量的使用方式,以使推断局限于闭包表达式并且可预测。在这种情况下,row 变量仅被作为引用使用,因此可以通过引用进行捕获;也就是说,传递给 map 的闭包对象包含一个对 row 的引用。因此,该对象不能离开声明 row 变量的作用域(即 flat_map 的闭包),因为该引用会指向无效的内存。返回 .map(closure) 将违反此规则,因为 .map 创建一个惰性迭代器,它存储了闭包,并且只有在请求元素时才调用它。

修复方法是强制不通过引用捕获 row 变量,以便闭包可以离开作用域。这可以使用 move 关键字来实现:

let pair_sums = rows.iter()
    .flat_map(|row| { 
        (0..row.len() - 1)
            .map(move |i| row[i] + row[i + 1])
    })
    .collect::<Vec<_>>();

换句话说,原始代码相当于以下内容:

let pair_sums = rows.iter()
    .flat_map(|row: &Vec<i32>| { 
        let row_ref: &&Vec<i32> = &row;
        (0..row.len() - 1)
            .map(move |i| (*row_ref)[i] + (*row_ref)[i + 1])
    })
    .collect::<Vec<_>>();

(我的“在Rust中寻找闭包”一文更详细地探讨了闭包,《Rust书籍》也有相应的讲解。)


谢谢。几个月后,我重新阅读了这个答案。不太显而易见的是懒惰的映射迭代器部分。将其与按引用捕获联系起来,使一切都讲得通了。 - Peter Hall

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