简体   繁体   English

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

[英]Returning a reference to a captured mutable variable

So I have a function which looks like this所以我有一个 function 看起来像这样

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

and this code does not compile, with the following error:并且此代码无法编译,并出现以下错误:

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

Edit编辑

As @Sven Marnach pointed out, the problem here is that I could create 2 mutable references to the same object:正如@Sven Marnach指出的那样,这里的问题是我可以为同一个 object 创建 2 个可变引用:

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

Original (incorrect)原文(错误)

I think that Rust is telling me, the closure group is mutably capturing the variable groups and then returning a reference to an object owned by groups .我认为 Rust 告诉我,闭包group正在可变地捕获变量groups ,然后返回对 object 拥有的引用groups So the danger here is that the following code would return a dangling pointer (since groups is dropped when it goes out of scope after foo finishes).所以这里的危险是下面的代码会返回一个悬空指针(因为在foo完成后它从 scope 中消失时groups被丢弃)。

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

So is there any way to return a reference from a captured mutable HashMap like this?那么有没有办法像这样从捕获的可变HashMap返回引用?

I think that Rust is telling me, the closure group is mutably capturing the variable groups and then returning a reference to an object owned by groups.我认为 Rust 告诉我,闭包组可变地捕获变量组,然后返回对组拥有的 object 的引用。 So the danger here is that if I were to write:所以这里的危险是,如果我要写:

then I would be returning a dangling pointer (since groups is dropped when it goes out of scope after foo finishes).然后我将返回一个悬空指针(因为在 foo 完成后它从 scope 中消失时组被丢弃)。

No. If that were the case Rust could (and would) warn about that .不会。如果是这种情况,Rust 可以(并且会)对此发出警告

The problem is that the lifetimes around function traits are problematic, because they don't have a way to match the lifetime of the result to the lifetime of the function itself .问题是 function 特征周围的生命周期是有问题的,因为它们没有办法将结果的生命周期与 function 本身的生命周期相匹配。

As a result, rust blanket bans returning any reference to captured data from a closure.因此,rust 全面禁止返回对从闭包中捕获的数据的任何引用

As far as I can tell the solutions are:据我所知,解决方案是:

  1. don't use a closure, instead pass in groups as an argument into the function (anonymous or named)不要使用闭包,而是将groups作为参数传递给 function(匿名或命名)

  2. use some sort of shared ownership and interior mutability eg have the map store and return Rc<RefCell<String>>使用某种共享所有权和内部可变性,例如存储 map 并返回Rc<RefCell<String>>

  3. desugar the closure to a structure with a method, that way the lifetimes become manageable:使用方法将闭包脱糖到结构中,这样生命周期就变得易于管理:

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

Note that the above stores a reference to match the original closure, but in reality I see no reason not to move the hashmap into the wrapper entirely (and the struct can init itself fully without the need for a two-step initialisation as well):请注意,上面存储了一个引用以匹配原始闭包,但实际上我认为没有理由不将 hashmap 完全移动到包装器中(并且结构可以完全初始化自身而无需两步初始化):

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

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

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