每当删除对源数据的可变引用时,如何重新缓存数据?

How can I recache data whenever a mutable reference to the source data is dropped?

我有一个名为 Spire 的结构,其中包含一些数据 (elements),以及可以根据该数据计算出的一些结果的缓存。当 elements 更改时,我希望能够自动更新缓存(例如,在这种情况下,结构的用户不必手动调用 update_height)。

我正在努力弄清楚如何实现这一目标,或者是否有更好的方法来完成我正在尝试做的事情。

struct Spire {
    elements: Vec<i32>,
    height: i32,
}

impl Spire {
    pub fn new(elements: Vec<i32>) -> Spire {
        let mut out = Spire {
            elements: elements,
            height: 0,
        };
        out.update_height();
        out
    }

    pub fn get_elems_mut(&mut self) -> &mut Vec<i32> {
        &mut self.elements
    }

    pub fn update_height(&mut self) {
        self.height = self.elements.iter().sum();
    }

    pub fn height(&self) -> i32 {
        self.height
    }
}

fn main() {
    let mut spire = Spire::new(vec![1, 2, 3, 1]);

    // Get a mutable reference to the internal elements
    let spire_elems = spire.get_elems_mut();

    // Do some stuff with the elements
    spire_elems.pop();
    spire_elems.push(7);
    spire_elems.push(10);

    // The compiler won't allow you to get height
    // without dropping the mutable reference first
    // dbg!(spire.height());

    // When finished, drop the reference to the elements.
    drop(spire_elems);
    // I want to automatically run update_height() here somehow

    dbg!(spire.height());
}

Playground

我正在尝试为可变引用找到具有 Drop 特征之类的行为。

至少有两种方法可以解决这个问题。不要直接调用 drop,你应该把你的代码放在一个新的范围内,这样范围规则会自动应用到它们,drop 会自动为你调用:

fn main() {
    let mut spire = Spire::new(vec![1, 2, 3, 1]);

    {
        let spire_elems = spire.get_elems_mut();
        spire_elems.pop();
        spire_elems.push(7);
        spire_elems.push(10);
    }

    spire.update_height();
    dbg!(spire.height());
}

如果你编译它,它将按预期工作。一般来说,如果你必须手动调用drop,通常意味着你正在做一些你不应该做的事情。

也就是说,更有趣的问题是设计一个 API 不会 泄漏你的抽象 。例如,您可以通过提供操作方法来保护您的内部数据结构表示(这有几个优点,其中之一是您以后可以自由地改变您对哪种数据结构的想法在内部使用而不影响代码的其他部分),例如

impl Spire {
    pub fn push(&mut self, elem: i32) {
        self.elements.push(elem);
        self.update_internals();
    }
}

此示例调用一个名为 update_internals 的私有方法,它会在每次更新后处理您的内部数据一致性。

如果你只想在所有添加和删除发生时更新内部值,那么你应该实现一个 finalising 方法,你必须在每次完成修改时调用该方法您的 Spire 实例,例如

spire.pop();
spire.push(7);
spire.push(10);
spire.commit();

要实现这样的目标,你至少有另外两个选择:你可以像上面的例子那样做,或者你可以使用构建器模式,你在一系列调用中进行修改,然后才会生效当您调用链上的最后一个终结调用时。类似于:

spire.remove_last().add(7).add(10).finalise();

另一种方法可能是有一个内部标志(一个简单的 bool 就可以),每次插入或删除时都会更改为 true。您的 height 方法可以在内部缓存计算的数据(例如,使用一些 Cell 类型来实现内部可变性),如果标志是 true 那么它将重新计算该值并将标志设置回 false。它将 return 每次后续调用的缓存值,直到您进行另一次修改。这是一个可能的实现:

use std::cell::Cell;

struct Spire {
    elements: Vec<i32>,
    height: Cell<i32>,
    updated: Cell<bool>,
}

impl Spire {
    fn calc_height(elements: &[i32]) -> i32 {
        elements.iter().sum()
    }

    pub fn new(elements: Vec<i32>) -> Self {
        Self {
            height: Cell::new(Self::calc_height(&elements)),
            elements,
            updated: Cell::new(false),
        }
    }

    pub fn push(&mut self, elem: i32) {
        self.updated.set(true);
        self.elements.push(elem);
    }

    pub fn pop(&mut self) -> Option<i32> {
        self.updated.set(true);
        self.elements.pop()
    }

    pub fn height(&self) -> i32 {
        if self.updated.get() {
            self.height.set(Self::calc_height(&self.elements));
            self.updated.set(false);
        }
        self.height.get()
    }
}

fn main() {
    let mut spire = Spire::new(vec![1, 2, 3, 1]);
    spire.pop();
    spire.push(7);
    spire.push(10);
    dbg!(spire.height());
}

如果您不介意在 height getter 中可变地借用 self,那么请不要理会 Cell,直接更新值即可。