Как я могу восстановить данные всякий раз, когда изменяемая ссылка на исходные данные отбрасывается? - PullRequest
1 голос
/ 31 мая 2019

У меня есть структура с именем 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());
}

Детская площадка

Я пытаюсь найти что-тос поведением типа Drop для изменяемых ссылок.

1 Ответ

2 голосов
/ 31 мая 2019

Существует как минимум два способа решения этой проблемы. Вместо прямого вызова 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, просто обновите значения напрямую.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...