I have an struct called Spire
that contains some data (elements
), and a cache of some result that can be calculated from that data. When elements
changes, I want to be able to automatically update the cache (e.g. without the user of the struct having to manually call update_height
in this case).
I'm trying to figure out how I can achieve that, or if there is a better way to do what I'm trying to do.
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());
}
I am trying to find something with behavior like the Drop
trait for mutable references.
There are at least two ways to tackle this problem. Instead of calling drop
directly, you should put your code which does the mutation in a new scope so that the scoping rules will automatically be applied to them and drop
will be called automatically for you:
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());
}
If you compile this, it will work as expected. Generally speaking, if you have to call drop
manually it usually means you are doing something that you shouldn't do.
That being said, the more interesting question is designing an API which is not leaking your abstraction. For example, you could protect your internal data structure representation by providing methods to manipulate it (which has several advantages, one of them is that you can freely change your mind later on what data structure you are using internally without effecting other parts of your code), e.g.
impl Spire {
pub fn push(&mut self, elem: i32) {
self.elements.push(elem);
self.update_internals();
}
}
This example invokes a private method called update_internals
which takes care of your internal data consistency after each update.
If you only want to update the internal values when all the additions and removals have happened, then you should implement a finalising method which you have to call every time you finished modifying your Spire
instance, e.g.
spire.pop();
spire.push(7);
spire.push(10);
spire.commit();
To achieve such a thing, you have at least another two options: you could do it like the above example or you could use a builder pattern where you are doing modifications throughout a series of calls which will then only have effect when you call the last finalising call on the chain. Something like:
spire.remove_last().add(7).add(10).finalise();
Another approach could be to have an internal flag (a simple bool
would do) which is changed to true
every time there is an insertion or deletion. Your height
method could cache the calculated data internally (e.g. using some Cell
type for interior mutability) and if the flag is true
then it will recalculate the value and set the flag back to false
. It will return the cached value on every subsequent call until you do another modification. Here's a possible implementation:
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());
}
If you don't mind borrowing self
mutably in the height
getter, then don't bother with the Cell
, just update the values directly.