Що означає "не може запозичити як незмінний, оскільки він також запозичений як змінний" означає в індексі вкладеного масиву?


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
Навчання іржі з появою коду? Ласкаво просимо до StackOverflow, і дякую за чудове запитання!
Свен Марнах

Точно; ) Це мій третій рік займатися цим (2x Haskell до цього) ~> думав дати Русту крутитись, оскільки мене почали більше цікавити речі низького рівня
Лукас Букк

@LucasBoucke Це смішно, я зазвичай використовую Rust для свого проекту, але я пишу цей AoC в Haskell. Вони обидва чудові мови у своїй галузі.
Boiethios

Відповіді:


16

Дезертирована версія трохи відрізняється від тієї, що у вас є. Лінія

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є мутантно запозиченим. Це прекрасно працює, оскільки спільний позик вже завершений, і весь вираз передає позику перевірки.

Зауважимо, що версія, яка складається, робить це лише з моменту введення "нелексичних життєвих ресурсів". У попередніх версіях Руста спільне запозичення збережеться до кінця виразу і призведе до подібної помилки.

Найчистішим рішенням, на мою думку, є використання тимчасової змінної:

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 Я поняття не маю, як ти це формалізуєш, і я впевнений, що він ніколи не летить в літ. Якщо ви хочете вирішити це, єдиний спосіб, який я бачу, - це вдосконалення перевірки позик, наприклад, змусити її виявити, що змінна позика може початись у більш пізній час, оскільки вона не дуже потрібна настільки рано. (Мабуть, ця ідея теж не працює.)
Свен Марнах
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.