如何在Rust中将i64复制到闭包中而不是借用?

5
我有以下代码的最小示例:
fn main()
{
    let names : Vec<Vec<String>> = vec![
        vec!["Foo1".to_string(), "Foo2".to_string()],
        vec!["Bar1".to_string(), "Bar2".to_string()]
    ];
    let ids : Vec<i64> = vec![10, 20];

    names.iter().enumerate().flat_map(|(i,v)| {
        let id : i64 = ids[i];
        v.iter().map(|n| 
            (n.clone(), id)
        )
    });
}

现在,当我使用rustc编译它时,会出现以下错误信息:
error[E0597]: `id` does not live long enough
  --> main.rs:12:16
   |
11 |         v.iter().map(|n| 
   |                      --- capture occurs here
12 |             (n.clone(), id)
   |                         ^^ borrowed value does not live long enough
13 |         )
14 |     });
   |     -- borrowed value needs to live until here
   |     |
   |     borrowed value only lives until here

但据我了解,idi64 类型,因此应该可以复制到捕获中,这正是我所需要的。

我还尝试过将 id 变量内联,但没有成功:

error[E0597]: `i` does not live long enough
  --> main.rs:11:21
   |
10 |             v.iter().map(|n| 
   |                          --- capture occurs here
11 |                 (n.clone(), ids[i])
   |                                 ^ borrowed value does not live long enough
12 |             )
13 |         });
   |         -- borrowed value needs to live until here
   |         |
   |         borrowed value only lives until here

那么我该如何将我的整数复制到闭包中而不是借用它?

我尝试使用 move,但 rustc 也不喜欢:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
  --> main.rs:10:17
   |
7  |         let ids : Vec<i64> = vec![10, 20];
   |             --- captured outer variable
...
10 |             v.iter().map(move |n| 
   |                          ^^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure

所以我需要让rustc只移动/复制其中一些变量而不是其他变量?


2
使用 move。可复制的物品不会被移动,而是被复制。让我找到重复的内容。 - Boiethios
@Boiethios 如果我将两个闭包都添加到其中,会出现相同的错误。第一个版本像AkinerAlkan的答案那样工作,但我也可以让第二个版本以同样的方式工作吗? - msrd0
根据您的错误信息,您仍在使用Rust 2015。我强烈建议您切换到2018以获得改进的错误信息。 - Shepmaster
显示剩余6条评论
2个回答

4
当你在Rust中创建闭包时,它会通过值或引用来捕获变量,两者的混合是不可能的。默认情况下,它通过引用进行捕获,但使用move关键字可以通过值进行捕获(即将捕获的变量移动到闭包内部)。因此,在你的第一段代码中,你需要将id移动到闭包内部:
fn main() {
    let names: Vec<Vec<String>> = vec![
        vec!["Foo1".to_string(), "Foo2".to_string()],
        vec!["Bar1".to_string(), "Bar2".to_string()],
    ];
    let ids: Vec<i64> = vec![10, 20];

    names.iter().enumerate().flat_map(|(i, v)| {
        let id: i64 = ids[i];
        v.iter().map(move |n| (n.clone(), id))
    });
}

接下来,您询问是否可以“内联”ids

fn main() {
    let names: Vec<Vec<String>> = vec![
        vec!["Foo1".to_string(), "Foo2".to_string()],
        vec!["Bar1".to_string(), "Bar2".to_string()],
    ];
    let ids: Vec<i64> = vec![10, 20];

    names.iter().enumerate().flat_map(|(i, v)| {
        v.iter().map(|n| (n.clone(), ids[i]))
    });
}

在内部闭包中,您不能完全使用ids,因为您已经在一个需要独占访问的FnMut闭包中。因此,您不能借用或移动ids,因为它已经被FnMut闭包借用了。最小复现代码:

fn main() {
    let mut i = 0;

    let mut closure = || {
        i = 2;
        || {
            println!("i = {}", i);
        }
    };

    closure()();
}

@msrd0 这是另一个问题:您尝试返回一个持有对i的引用的东西,但是i是从可变闭包内部声明的。 - Boiethios
好的,我明白了。因为map获取了闭包的所有权,编译器无法保证在i超出作用域之前执行它。这一点从编译器的错误信息中并不是很清楚。谢谢。 - msrd0
2
我认为你的最后一段不准确。在内部闭包中,你可以借用ids,但是i不能被借用(因为它属于外部闭包)。另一方面,你可以移动 i(因为它是Copy),但是你不能移动ids,因为外部闭包只借用它。你需要在移动i时借用ids,这可以通过在外部闭包中获取对ids的引用并使用move来同时移动两者。 - trent
2
“混合两者是不可能的”- 从技术上讲这是准确的,但正如trentcl所示,您可以采用显式引用,然后将引用移入,从而有效地实现混合。 - Shepmaster
@Shepmaster,你理解我的意思了。你必须添加一个步骤来仅使用可移动类型(可复制类型或引用)。我回答这个问题:为什么无法内联索引操作。 - Boiethios
显示剩余6条评论

2
你可以使用move关键字将变量移入闭包中。在这里,你需要更改闭包的写法为:

使用move关键字将变量移入闭包中:

v.iter().map(move |n|  // move is the keyword for moving variables into closure scope.
    (n.clone(), id)
)

Playground


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