首页 > 解决方案 > 如何绕过可变和不可变引用?

问题描述

我正在创建一个库,该库试图成为一个 Cache 结构,该结构包装了一些结构,该结构将是检索“新鲜数据”的组件,例如通过一些 HTTP 请求;还有一个后端字段,它是通过包装组件检索的数据的实际缓存后端。

在检索一些新数据后尝试“刷新”缓存中的数据时,我遇到了一些问题。

use std::collections::HashMap;

pub trait Cacheable<K, V>
    where K: std::cmp::Eq + std::hash::Hash
{
    fn get(&self, k: &K) -> Option<&V>;
    fn set(&mut self, k: K, v: V);
    fn del(&mut self, k: &K);
}

pub struct Cache<'a, K, V>
    where K: std::cmp::Eq + std::hash::Hash
{
    wrapped: &'a dyn Cacheable<K, V>,
    backend: Box<dyn Cacheable<K, V>>,
}

impl<'a, K, V> Cache<'a, K, V>
    where K: std::cmp::Eq + std::hash::Hash + 'static,
          V: 'static,
{
    fn New(wrapped: &'a dyn Cacheable<K, V>, backend_type: BackendType) -> Self {
        let backend = match backend_type {
            BackendType::Memory => MemoryBackend::<K, V>::New(),
        };

        Cache {
            wrapped: wrapped,
            backend: Box::new(backend),
        }
    }

    fn get(&mut self, k: &K) -> Option<&V> {
        // PROBLEM 1:
        // Borrow backend here as immutable
        if let Some(v) = self.backend.get(k) {
            return Some(v)
        }
        
        if let Some(v) = self.wrapped.get(k) {
            let cache_k = *k.clone(); // PROBLEM 2: How to clone reference value?
            let cache_v = *v.clone(); // PROBLEM 2: How to clone reference value?

            // PROBLEM 1:
            // Borrow backend here as mutable
            self.backend.set(cache_k, cache_v);
            return Some(v)
        }

        None
    }
}

pub enum BackendType {
    Memory
} 

pub struct MemoryBackend<K, V>
    where K: std::cmp::Eq + std::hash::Hash
{
    data: HashMap<K, V>,
}

impl<K, V> Cacheable<K, V> for MemoryBackend<K, V>
    where K: std::cmp::Eq + std::hash::Hash
{
    fn get(&self, k: &K) -> Option<&V> {
        self.data.get(k)
    }

    fn set(&mut self, k: K, v: V) {
        self.data.insert(k, v);
    }

    fn del(&mut self, k: &K) {
        self.data.remove(k);
    }
}

impl<K, V> MemoryBackend<K, V>
    where K: std::cmp::Eq + std::hash::Hash
{
    fn New() -> Self {
        MemoryBackend {
            data: HashMap::<K, V>::new(),
        }
    }
}
问题 1

可变和不可变引用可能共存。这里最好的选择是什么?

我尝试更改get方法签名以返回 anOption<V>而不是,Option<&V>但这也导致我遇到问题 2,我似乎无法克隆共享引用的值。

问题 2
move occurs because value has type `K`, which does not implement the `Copy` trait
help: consider borrowing here: `&*k.clone()`"

AFAIK 我不想将Copytrait 设置为 bound ofV因为它可能不是一个简单的类型,并且宁愿克隆它的值而不是将其设置为 API 限制。我似乎无法解决这个问题。

标签: rust

解决方案


  1. 问题 1:可变引用和不可变引用可能共存。这里最好的选择是什么?我尝试更改get方法签名以返回 anOption<V>而不是,Option<&V>但这也导致我遇到问题 2,我似乎无法克隆共享引用的值。

    您可以向 trait 添加一个方法,并使用APIget_or_insert实现它:MemoryBackendhash_map::Entry

    fn get_or_insert(&mut self, k: K, v: Option<V>) -> Option<&V> {
        match self.data.entry(k) {
            Entry::Occupied(entry) => Some(entry.into_mut()),
            Entry::Vacant(entry) => v.map(|v| &*entry.insert(v)),
        }
    }
    

    或者,为了避免评估v,除非它是实际需要的(如果上面是内联的,优化过程可能已经做了),你可以传入一个返回的函数Option<V>

    fn get_or_insert_with(&mut self, k: K, f: &dyn Fn() -> Option<V>) -> Option<&V> {
        match self.data.entry(k) {
            Entry::Occupied(entry) => Some(entry.into_mut()),
            Entry::Vacant(entry) => f().map(|v| &*entry.insert(v)),
        }
    }
    

    请注意,为了Cacheable对象安全,我们必须在此处使用函数特征对象的间接寻址(同样,如果上述内容被内联,则优化传递可能会消除)。

  2. 问题 2“移动发生是因为 value 具有 type K,它没有实现Copytrait 帮助:考虑在这里借用:&*k.clone() AFAIK 我不想将Copytrait 设置为 bound ofV因为它可能不是一个简单的类型,而是更愿意克隆它值而不是将其设置为 API 限制。所以我似乎无法解决这个问题。

    您可以在实现中添加K: CloneV: Clone约束:

    impl<'a, K, V> Cache<'a, K, V>
    where
        K: Clone + std::cmp::Eq + std::hash::Hash + 'static,
        V: Clone + 'static,
    {
        // etc
    }
    

游乐场


推荐阅读