如何允许不可变和可变借用在具有显式生命周期的函数中共存?

How to allow immutable and mutable borrows to coexist in a function with explicit lifetimes?

对于上下文:我正在实现一个实体,基本上是一个围绕异构映射的薄包装器,并尝试实现一个 update 方法:

struct Entity {
    pub id: u64,
    data: HashMap<TypeId, Box<dyn Any>>,
}

impl Entity {

    pub fn new(id: u64) -> Self {
        Entity { id, data: HashMap::new() }
    }

    pub fn get_atom<T: Any>(&self) -> Option<&T> {
        self.data.get(&TypeId::of::<T>())?.downcast_ref()
    }

    pub fn set_atom<T: Any>(&mut self, value: T) {
        self.data.insert(TypeId::of::<T>(), Box::new(value));
    }

    pub fn update<T: Any, R: Any>(&mut self, f: impl Fn(&T) -> R) 
    {
        if let Some(val) = self.get_atom() {
            self.set_atom(f(val));
        }
    }
}

这有效,但会发出警告:

warning: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/entity.rs:71:13
   |
70 |         if let Some(val) = self.get_atom() {
   |                            --------------- immutable borrow occurs here
71 |             self.set_atom(f(val));
   |             ^^^^^^^^^^^^^^^^---^^
   |             |               |
   |             |               immutable borrow later used here
   |             mutable borrow occurs here

好的,可以像这样修复,删除警告。

    pub fn update<T: Any, R: Any>(&mut self, f: impl Fn(&T) -> R) 
    {
        let maybe : Option<R> = self.get_atom().map(f);
        if let Some(val) = maybe {
            self.set_atom(val);
        }
    }

我遇到的问题是当我引入一个特征来抽象而不是从实体中获取东西时(这允许我对元数进行抽象:如果每个组件都在实体):

trait GetComponent<'a, T> {
    fn get_component(entity: &'a Entity) -> Option<T>;
}

// sample atom impl
impl <'a> GetComponent<'a, &'a u32> for &'a u32 {
    fn get_component(entity: &'a Entity) -> Option<&'a u32> {
        entity.get_atom()
    }
}

// sample composite impl
impl <'a, A, B> GetComponent<'a, (A, B)> for (A, B) where 
    A: GetComponent<'a, A>, 
    B: GetComponent<'a, B>,
{
    fn get_component(entity: &'a Entity) -> Option<(A, B)> {
        Some((A::get_component(entity)?, B::get_component(entity)?))
    }
}

impl Entity {
    <in addition to the above>

    pub fn get<'a, T: GetComponent<'a, T>>(&'a self) -> Option<T> {
        T::get_component(self)
    }

    pub fn update<'a, T: 'a, R: Any>(&'a mut self, f: impl Fn(&T) -> R) 
        where &'a T: GetComponent<'a, &'a T>
    {
        let maybe : Option<R> = self.get().map(f);
        if let Some(val) = maybe {
            self.set_atom(val);
        }
    }
}

该特性要求我明确说明生命周期,到目前为止我尝试对该实体所做的所有其他事情似乎都很好。但是这个更新方法给出了这个编译错误(这次不是警告):

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/entity.rs:56:13
   |
51 |     pub fn update<'a, T: 'a, R: Any>(&'a mut self, f: impl Fn(&T) -> R) 
   |                   -- lifetime `'a` defined here
...
54 |         let maybe : Option<R> = self.get().map(f);
   |                                 ----------
   |                                 |
   |                                 immutable borrow occurs here
   |                                 argument requires that `*self` is borrowed for `'a`
55 |         if let Some(val) = maybe {
56 |             self.set_atom(val);
   |             ^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

现在,据我所知,我没有理由不能在较短的时间内不可变地借用,所以在我进行突变之前不可变的借用“到期”——就像在版本中一样在特性之前没有使用明确的生命周期。

但我一辈子都弄不明白如何让它发挥作用。

你可以用 higher-rank trait bound (HRTB) 解决这个问题:

    pub fn update<T, R: Any>(&mut self, f: impl Fn(&T) -> R) 
        where for <'a> &'a T: GetComponent<'a, &'a T>

这表示对于 任何可能的 生命周期 'aT 必须满足给定的界限。这允许您指定类型 T 的各种生命周期如何相互关联,而无需将它们绑定到特定的生命周期,例如 &mut self.

的生命周期。

(Playground)