如何在Rust中延迟创建其构造使用self的地图条目 [英] How to lazily create map entry whose construction uses self in Rust

查看:100
本文介绍了如何在Rust中延迟创建其构造使用self的地图条目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在Rust中实现惰性构造/记忆化评估/缓存习惯.

I'm trying to implement a lazy-construction / memoized evaluation / caching idiom in Rust.

有一个外部类型,它具有大量数据和访问器方法.访问器方法需要返回一个缓存的计算(如果有的话),或者对其进行计算并将返回值存储在映射中以供以后使用.缓存的值不需要引用外部值,因此不存在循环引用问题;但是它确实需要访问外部值的数据才能构造自身.

There's an outer type which has a bunch of data and an accessor method. The accessor method needs to either return a cached calculation (if it has one) or calculate it and store the return value in a map for later use. The cached value doesn't need to reference the outer value so there's no circular reference problem; but it does need access to the outer value's data in order to construct itself.

这是一个完整的示例,没有通过Rust的借阅检查器:

Here's a full example which doesn't pass Rust's borrow checker:

use std::collections::HashMap;

pub struct ContainedThing {
    count: usize,
}

impl ContainedThing {
    fn create(thing: &Thing) -> ContainedThing {
        // create uses an arbitrary number of attributes from Thing
        // it doesn't keep any references after returning though
        let count = thing.map.len();
        ContainedThing { count: count }
    }
}

struct Thing {
    map: HashMap<i32, ContainedThing>,
}

impl Thing {
    pub fn get(&mut self, key: i32) -> &ContainedThing {
        self.map
            .entry(key)
            .or_insert_with(|| ContainedThing::create(&self))
    }
}

fn main() {}

具体错误是:

error[E0502]: cannot borrow `self` as immutable because `self.map` is also borrowed as mutable
  --> src/main.rs:24:29
   |
22 |         self.map
   |         -------- mutable borrow occurs here
23 |             .entry(key)
24 |             .or_insert_with(|| ContainedThing::create(&self))
   |                             ^^                         ---- borrow occurs due to use of `self` in closure
   |                             |
   |                             immutable borrow occurs here
25 |     }
   |     - mutable borrow ends here

我很难找到一种实现此习语的好方法.我尝试过使用模式匹配get()而不是entry() API的返回值,但是仍然与借用检查器相抵触:match表达式也最终借用了self.

I'm having a really hard time figuring out a good way to implement this idiom. I tried pattern matching the return value of get() instead of the entry() API, but still fell foul of the borrow checker: the match expression also ends up borrowing self.

我可以这样重写get:

pub fn get(&mut self, key: i32) -> &ContainedThing {
    if !self.map.contains_key(&key) {
        let thing = ContainedThing::create(&self);
        self.map.insert(key, thing);
    }
    self.map.get(&key).unwrap()
}

但这很丑陋(请参阅unwrap),似乎需要的查找和副本数量超出了应有的数量.理想情况下,我想要

but this is pretty ugly (look at that unwrap) and seems to require more lookups and copies than ought to be necessary. Ideally, I would like

  1. 仅需支付一次查找哈希条目的费用. entry(),正确完成,应该在找不到时跟踪插入位置.
  2. 减少新构建的值的副本数.这可能是不可行的,理想情况下,我将采用就地构造.
  3. 避免使用unwrap;没有无意义的模式匹配,那就是.
  1. to only pay the cost of finding the hash entry once. entry(), done right, ought to track the insertion location when not found.
  2. reduce the number of copies of the freshly constructed value. This may be infeasible, ideally I'd have an in-place construction.
  3. to avoid using unwrap; without a pointless pattern match, that is.

我笨拙的代码是可以实现的最好的代码吗?

Is my clumsy code the best that can be achieved?

推荐答案

答案是,它具体取决于您需要在or_insert_with闭包中访问的状态.问题是or_insert_with绝对不能访问地图本身,因为条目api会可变地借用地图.

The answer is that it depends on specifically which state you need access to in the or_insert_with closure. The problem is that or_insert_with definitely cannot have access to the map itself because the entry api takes a mutable borrow of the map.

如果ContainedThing::create所需的只是地图的大小,那么您只需要提前计算地图的大小即可.

If all you need for ContainedThing::create is just the size of the map, then you'll just need to calculate the map size ahead of time.

impl Thing {
    pub fn get(&mut self, key: i32) -> &ContainedThing {
        let map_size = self.map.len();
        self.map.entry(&key).or_insert_with(|| {
            // The call to entry takes a mutable reference to the map,
            // so you cannot borrow map again in here
            ContainedThing::create(map_size)
        })
    }
}

尽管如此,我认为问题的精神更多地是关于一般策略,所以我们假设在Thing中还存在其他一些状态,这些状态也需要创建ContainedThing.

I think the spirit of the question was more about general strategies, though, so let's assume there's some other state within Thing that is also required to create ContainedThing.

struct Thing {
    map: HashMap<i32, ContainedThing>,
    some_other_stuff: AnotherType, //assume that this other state is also required in order to create ContainedThing
}

impl Thing {
    pub fn get(&mut self, key: i32) -> &ContainedThing {
        //this is the borrow of self
        let Thing {
            ref mut map,
            ref mut some_other_stuff,
        } = *self;

        let map_size = map.len();
        map.entry(key).or_insert_with(|| {
            // map.entry now only borrows map instead of self
            // Here you're free to borrow any other members of Thing apart from map
            ContainedThing::create(map_size, some_other_stuff)
        })
    }
}

是否真的比您手动检查if self.map.contains_key(&key)的其他解决方案还干净?不过,我倾向于采用销毁策略来允许借用self的特定成员而不是整个结构.

Whether that's really cleaner than your other solution of manually checking if self.map.contains_key(&key) is up for debate. Destructuring tends to be the strategy that I go for, though, to allow borrowing specific members of self instead of the entire struct.

这篇关于如何在Rust中延迟创建其构造使用self的地图条目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆