Существует как минимум два способа решения этой проблемы. Вместо прямого вызова 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
, который заботится о согласованности ваших внутренних данных после каждого обновления.
Если вы хотите обновлять внутренние значения только тогда, когда произошли все добавления и удаления, вам следует реализовать финализирующий метод, который вы должны вызывать каждый раз, когда заканчиваете модифицировать свой экземпляр 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
. Он будет возвращать кэшированное значение при каждом последующем вызове, пока вы не сделаете другую модификацию. Вот возможная реализация:
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());
}
Если вы не возражаете заимствовать self
изменчиво в height
геттере, то не беспокойтесь о Cell
, просто обновите значения напрямую.