Rust `Vec` - невозможно заимствовать` Vec` как неизменяемый внутри метода `impl` (ошибка [E0502])

Есть много ответов на вопросы о error[E0502] в Rust, но я не могу понять один конкретный случай. У меня есть struct, и это impl метод, который выглядит так:

struct Test {
  test_vec: Vec<i32>,
}

impl Test {
  // other methods...

  fn test(&mut self) -> i32 {
    self.test_vec.swap(0, self.test_vec.len() - 1);

    // other operations...
  }
}

Попытка скомпилировать немедленно приводит к ошибке:

ошибка [E0502]: невозможно заимствовать self.test_vec как неизменяемый, потому что он также заимствован как изменяемый

self.test_vec.swap(0, self.test_vec.len() - 1);
------------- ----    ^^^^^^^^^^^^^ immutable borrow occurs here
|             |
|             mutable borrow later used by call
mutable borrow occurs here

Кто-нибудь может объяснить, почему? На самом деле это не похоже на то, что я пытаюсь заимствовать self.test_vec там, я передаю результат типа usize вызова len(). С другой стороны:

fn test(&mut self) -> i32 {
  let last_index = self.test_vec.len() - 1;

  self.test_vec.swap(0, last_index);

  // other operations...
}

Используя временную переменную, она работает так, как ожидалось, заставляет меня думать, что вызов len() каким-то образом оценивается после, когда он попадает в swap, и, таким образом, заимствован? Я что-то не вижу из-за синтаксического сахара?


person Damaged Organic    schedule 09.07.2020    source источник


Ответы (1)


Вы должны думать об этом так, как это делает компилятор. Когда вы пишете:

self.test_vec.swap(0, self.test_vec.len() - 1);

Что видит компилятор:

let temp1 = &mut self.test_vec;      // Mutable borrow of self.test_vec
let temp2 = &self.test_vec;          // (ERROR!) Shared borrow of self.test_vec for use on getting the length
let temp3 = Vec::len(temp2) - 1; 
Vec::swap(temp1, 0, temp3);

Как видите, вы сначала заимствуете self.test_vec изменчиво, а затем пытаетесь получить длину, что является еще одним заимствованием. Поскольку первое заимствование является изменяемым и все еще действует, второе заимствование является незаконным.

Когда вы используете временную переменную, вы эффективно переупорядочиваете свои заимствования, и поскольку self.test_vec.len() завершает заимствование перед следующим изменяемым заимствованием, конфликтов нет.

Вы можете утверждать, что компилятор должен иметь возможность видеть, что ваш код может быть правильным (если он интерпретируется правильно), но компилятор явно недостаточно умен, чтобы сделать это.

person Emoun    schedule 09.07.2020
comment
Спасибо за объяснение. Но все же, почему во время self.test_vec.len() - 1 происходит заимствование собственно? Естественно, я ожидал, что порядок операций сначала вычислит длину вектора, а затем вызовет функцию как Vec::swap(&mut self.test_vec, 0, x - 1). - person Damaged Organic; 09.07.2020
comment
Эта проблема не имеет ничего общего с Vec::swap(). Сначала создается временная переменная (которую мы не видим) для хранения &mut self.test_vec, затем одна для 0, затем одна для self.test_vec.len() - 1. Это может быть не на 100% точным, но если вы подумаете об этом таким образом, очевидно, что первая временная переменная все еще находится в области видимости, когда создается третья. Я отредактирую свой ответ, чтобы показать это. - person Emoun; 09.07.2020
comment
Это проясняет. Я проверил, передав изменяемую ссылку на функцию, и компилятор справедливо пожаловался, что двойное изменяемое заимствование является незаконным, что и понятно. Спасибо, что нашли время описать такое поведение! - person Damaged Organic; 09.07.2020