Что означает «не может быть заимствован как неизменный, потому что он также заимствован как изменяемый» в индексе вложенного массива?

16

Что означает ошибка в этом случае:

fn main() {
    let mut v: Vec<usize> = vec![1, 2, 3, 4, 5];
    v[v[1]] = 999;
}
error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:3:7
  |
3 |     v[v[1]] = 999;
  |     --^----
  |     | |
  |     | immutable borrow occurs here
  |     mutable borrow occurs here
  |     mutable borrow later used here

Я обнаружил , что индексация осуществляется через Indexи IndexMutчерт , и что v[1]является синтаксический *v.index(1). С этими знаниями я попытался запустить следующий код:

use std::ops::{Index, IndexMut};

fn main() {
    let mut v: Vec<usize> = vec![1, 2, 3, 4, 5];
    *v.index_mut(*v.index(1)) = 999;
}

К моему удивлению, это работает без нареканий! Почему первый фрагмент не работает, а второй работает? Как я понимаю документацию, они должны быть эквивалентны, но это, очевидно, не так.

Лукас Бук
источник
2
Изучение Rust с появлением кода? Добро пожаловать в StackOverflow и спасибо за отличный вопрос!
Свен Марнах
Точно ; ) Это мой третий год работы над этим (2 раза на Haskell до этого) ~> я подумал о том, чтобы дать Rust водоворот, так как я начал больше интересоваться вещами низкого уровня
Лукас Бук
@LucasBoucke Забавно, я обычно использую Rust для своего проекта, но я пишу этот AoC на Haskell. Они оба замечательные языки в своей области.
Boiethios

Ответы:

16

Версия desugared немного отличается от того, что у вас есть. Линия

v[v[1]] = 999;

на самом деле desugars к

*IndexMut::index_mut(&mut v, *Index::index(&v, 1)) = 999;

Это приводит к тому же сообщению об ошибке, но аннотации дают подсказку о том, что происходит:

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:48
  |
7 |     *IndexMut::index_mut(&mut v, *Index::index(&v, 1)) = 999;
  |      ------------------- ------                ^^ immutable borrow occurs here
  |      |                   |
  |      |                   mutable borrow occurs here
  |      mutable borrow later used by call

Важным отличием вашей исходной версии является порядок оценки. Аргументы вызова функции оцениваются слева направо в указанном порядке до фактического вызова функции. В этом случае это означает, что сначала&mut v оценивается, заимствуя заимствования v. Далее Index::index(&v, 1)следует оценить, но это невозможно - vуже заимствовано. Наконец, компилятор показывает, что изменяемая ссылка все еще необходима для вызова функции index_mut(), поэтому изменяемая ссылка все еще жива, когда делается попытка использования общей ссылки.

Версия, которая на самом деле компилируется, имеет немного другой порядок оценки.

*v.index_mut(*v.index(1)) = 999;

Во-первых, аргументы функции для вызовов метода оцениваются слева направо, т.е. *v.index(1)оцениваются первыми. Это приводит кusize , что временный общий заем vможет быть освобожден снова. Затем получатель index_mut()оценивается, то vесть заимствован. Это работает нормально, поскольку общий заем уже завершен, и все выражение проходит проверку заимствования.

Обратите внимание, что версия, которая компилируется, делает это только после введения «нелексических времен жизни». В более ранних версиях Rust общее заимствование сохранялось до конца выражения и приводило к аналогичной ошибке.

На мой взгляд, самое чистое решение - использовать временную переменную:

let i = v[1];
v[i] = 999;
Свен Марнах
источник
Ого! Здесь много чего происходит! Спасибо, что нашли время, чтобы объяснить! (Интересно, что такие "причуды" делают язык более интересным для меня ...). Не могли бы вы также дать подсказку о том, почему происходит *v.index_mut(*v.index_mut(1)) = 999;сбой с «невозможно одолжить v как изменяемый более одного раза» ~> не должен ли быть компилятор, как если бы он *v.index_mut(*v.index(1)) = 999;мог выяснить, что внутренний заем больше не нужен?
Лукас Бук
@LucasBoucke Rust имеет несколько причуд, которые иногда немного неудобны, но в большинстве случаев решение довольно простое, как в этом случае. Код по-прежнему довольно читабелен, просто немного отличается от того, что было у вас изначально, поэтому на практике это не имеет большого значения.
Свен Марнач
@ LucasBoucke Извините, я не видел ваши изменения до сих пор. Результатом *v.index(1)является значение, хранящееся в этом индексе, и это значение не требует сохранения заимствования v. Результатом *v.index_mut(1), с другой стороны, является изменяемое выражение места, которое теоретически может быть назначено, поэтому оно сохраняет заимствование. На первый взгляд, можно поучить средство проверки заимствования, что выражение места в контексте выражения значения можно рассматривать как выражение значения, поэтому возможно, что это скомпилируется в какой-то будущей версии Rust.
Свен Марнач
Как насчет RFC для десугара это:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; }
Boiethios
@FrenchBoiethios Я понятия не имею, как бы вы это формализовали, и я уверен, что летать не собирается. Если вы хотите решить эту проблему, единственный способ, который я вижу, - это усовершенствовать средство проверки заимствований, например, заставить его обнаруживать, что изменяемое заимствование может начаться позднее, поскольку на самом деле оно не нужно так рано. (Эта конкретная идея, вероятно, тоже не работает.)
Свен Марнач