Выполните рефакторинг метода в Rust, не вызывая ошибки компиляции "невозможно заимствовать сам" - PullRequest
1 голос
/ 20 июня 2020

После выполнения упражнения на Rust (упражнение "React" на сайте Exercism, если оно актуально) код прошел все модульные тесты, но полагался на большие методы, каждый из которых отвечает за несколько вещей.

Значительно (намного) упрощенная версия одного из таких методов работы:

pub fn working_set_value(&'a mut self, id: InputCellID) {
    let original_values: HashMap<&'a ComputeCellID, Option<T>> = HashMap::new();
    let end_values: HashMap<&'a ComputeCellID, Option<T>> = HashMap::new();
    for (cell_id, original_value) in original_values.iter() {
        let end_value = end_values.get(cell_id).unwrap();
        if end_value != original_value && end_value.is_some() {
            // Fire callbacks for current cell_id.
        }
    }
}

Этот метод представляет собой большой объем кода (в его неупрощенной форме) и отвечает за две вещи: во-первых, обновление значений ячеек. и сохранение значений до / после в двух картах, и, во-вторых, итерация этих карт и запуск обратных вызовов ячеек для всех измененных ячеек.

* 1007 отвечает только за одну вещь) выглядит так (опять же, это сильно упрощенная версия):
pub fn broken_set_value(&'a mut self, id: InputCellID) {
    let (original_value_map, end_value_map) = self.update_listeners(&id);
    self.fire_callbacks(original_value_map, end_value_map);
}

fn update_listeners(
    &'a mut self,
    _id: &InputCellID,
) -> (
    HashMap<&'a ComputeCellID, Option<T>>,
    HashMap<&'a ComputeCellID, Option<T>>,
) {
    let original_values = HashMap::new();
    let end_values = HashMap::new();
    (original_values, end_values)
}

fn fire_callbacks(
    &self,
    original_value_map: HashMap<&'a ComputeCellID, Option<T>>,
    end_value_map: HashMap<&'a ComputeCellID, Option<T>>,
) {
    for (cell_id, original_value) in original_value_map.iter() {
        let end_value = end_value_map.get(cell_id).unwrap();
        if end_value != original_value && end_value.is_some() {
            // Fire callbacks for current cell_id.
        }
    }
}

Это приводит к тому, что компилятор выдает ошибку:

error[E0502]: cannot borrow `*self` as immutable because it is also borrowed as mutable
--> src/lib.rs:109:9
    |
79  | impl<'a, T: Copy + PartialEq + Debug> Reactor<'a, T> {
    |      -- lifetime `'a` defined here
...
108 |         let (original_value_map, end_value_map) = self.update_listeners(&id);
    |                                                   --------------------------
    |                                                   |
    |                                                   mutable borrow occurs here
    |                                                   argument requires that `*self` is borrowed for `'a`
109 |         self.fire_callbacks(original_value_map, end_value_map);
    |         ^^^^ immutable borrow occurs here

Обратите внимание, что эта ошибка не возникает, если тип возвращаемого значения update_listeners изменен так, что ссылки &'a ComputeCellID (со спецификаторами времени жизни или без них) заменяются собственными экземплярами: ComputeCellID. Также обратите внимание, что ошибка возникает независимо от того, передаются ли затем ссылки в метод fire_callbacks, и даже вызов drop для отмены ссылок не остановит сообщение об ошибке.

Объяснение?

Ниже приводится правильное резюме того, что расстраивает компилятор Rust: поскольку метод update_listeners создает ссылки, которые полагаются на изменяемое заимствование self, область видимости (не уверен, что это правильное слово) изменяемое заимствование (сделанное вызовом update_listeners) должно быть расширено до конца метода set_value. А поскольку область изменяемого заимствования теперь охватывает вызов fire_callbacks, дальнейшее заимствование self.

не разрешается, и если использовались собственные экземпляры (вместо ссылок, зависящих от self), тогда "область" изменяемого заимствования закроется сразу после вызова update_listeners, и поэтому мы могли бы позволить fire_callbacks сделать еще один заимствование self.

Обходной путь?

Если это понимание верно, то как можно реорганизовать такой большой метод в маленькие, сфокусированные методы, каждый из которых отвечает только за одну вещь? Я был впечатлен Rust до сих пор, и я подозреваю, что должен быть элегантный способ сделать это успешно.

...