返回对捕获的可变变量的引用

3
所以我有一个长这样的函数
fn foo() {
    let items = vec![0.2, 1.5, 0.22, 0.8, 0.7, 2.1];
    let mut groups: HashMap<u32, String> = HashMap::new();

    let mut group = |idx: f32| -> &mut String {
        let rounded = (idx / 0.2).floor() as u32;
        groups
            .entry(rounded)
            .or_insert_with(|| format!("{}:", rounded))
    };

    for item in items.iter() {
        group(*item).push_str(&format!(" {}", item))
    }
}

这段代码无法编译,会出现以下错误:

error: captured variable cannot escape `FnMut` closure body
  --> src/main.rs:9:9
   |
5  |       let mut groups: HashMap<u32, String> = HashMap::new();
   |           ---------- variable defined here
6  | 
7  |       let mut group = |idx: f32| -> &mut String {
   |                                     - inferred to be a `FnMut` closure
8  |           let rounded = (idx / 0.2).floor() as u32;
9  |           groups
   |           ^-----
   |           |
   |  _________variable captured here
   | |
10 | |             .entry(rounded)
11 | |             .or_insert_with(|| format!("{}:", rounded))
   | |_______________________________________________________^ returns a reference to a captured variable which escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

编辑

正如 @Sven Marnach 指出的那样,问题在于我可以创建指向同一对象的 2 个可变引用:

fn foo() {
    // ...    
    let ok = groups(0.1);
    let problem = groups(0.1);
}

Translated (corrected)

I think that Rust is warning me that the closure group is mutably capturing the variable groups and then returning a reference to an object owned by groups. The risk here is that the following code would return a dangling pointer (since groups is dropped when it goes out of scope after foo finishes).

fn foo() -> &String {
        /* ... */ return groups(0.1); }

Is there a way to return a reference from a captured mutable HashMap like this?


1
问题不在于你可能从 foo() 返回引用。问题在于你可能第二次调用 group() 并最终得到两个可变引用指向同一个值。正如错误信息所述 - FnMut 闭包不能返回对其捕获值的任何引用。 - Sven Marnach
1个回答

4
我认为Rust在告诉我,闭包组正在可变地捕获变量组,然后返回对由groups拥有的对象的引用。因此,这里的危险是,如果我写了以下内容:
那么我将返回一个悬空指针(因为在foo完成后,groups超出作用域时会被丢弃)。
不,如果是这种情况,Rust可以(而且会)警告那个。
问题在于,函数特征周围的生命周期存在问题,因为它们没有办法将结果的寿命与函数本身的寿命匹配起来。
因此,rust禁止从闭包中返回任何对捕获数据的引用
据我所知,解决方案有:
  1. 不要使用闭包,而是将groups作为参数传递给函数(匿名或命名)

  2. 使用某种共享所有权和内部可变性,例如将map存储和返回Rc<RefCell<String>>

  3. 将闭包转换为具有方法的结构体,这样生命周期就可以管理:

use std::collections::HashMap;

struct F<'a>(&'a mut HashMap<u32, String>);

impl F<'_> {
    fn call(&mut self, idx: f32) -> &mut String {
        let rounded = (idx / 0.2).floor() as u32;
        self.0
            .entry(rounded)
            .or_insert_with(|| format!("{}:", rounded))
    }
}

pub fn foo() {
    let items = vec![0.2, 1.5, 0.22, 0.8, 0.7, 2.1];
    let mut groups: HashMap<u32, String> = HashMap::new();

    let mut group = F(&mut groups);

    for item in items.iter() {
        group.call(*item).push_str(&format!(" {}", item))
    }
}

请注意,上述代码存储了匹配原始闭包的引用,但实际上我认为将哈希映射完全移入包装器中是没有问题的(并且结构体可以完全自行初始化,无需进行两步初始化):
use std::collections::HashMap;

struct F(HashMap<u32, String>);

impl F {
    fn new() -> Self { Self(HashMap::new()) }
    fn call(&mut self, idx: f32) -> &mut String {
        let rounded = (idx / 0.2).floor() as u32;
        self.0
            .entry(rounded)
            .or_insert_with(|| format!("{}:", rounded))
    }
}

pub fn foo() {
    let items = vec![0.2, 1.5, 0.22, 0.8, 0.7, 2.1];

    let mut group = F::new();

    for item in items.iter() {
        group.call(*item).push_str(&format!(" {}", item))
    }
}

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