Apa artinya “tidak dapat meminjam sebagai tidak dapat diubah karena juga dipinjam sebagai dapat berubah” artinya dalam indeks array bersarang?

16

Apa artinya kesalahan dalam hal ini:

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

Saya menemukan bahwa pengindeksan diimplementasikan melalui Indexdan IndexMutsifat-sifat dan bahwa v[1]adalah sintaksis gula untuk *v.index(1). Dilengkapi dengan pengetahuan ini, saya mencoba menjalankan kode berikut:

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;
}

Yang mengejutkan saya, ini bekerja dengan sempurna! Mengapa potongan pertama tidak berfungsi, tetapi yang kedua tidak? Cara saya memahami dokumentasi, mereka harus setara, tetapi ini jelas tidak terjadi.

Lucas Boucke
sumber
2
Belajar Karat dengan Advent of Code? Selamat datang di StackOverflow, dan terima kasih atas pertanyaannya!
Sven Marnach
Tepatnya; ) Ini tahun ketiga saya melakukannya (2x Haskell sebelum itu) ~> berpikir untuk memberi Rust pusaran karena saya mulai lebih tertarik pada hal
Lucas Boucke
@LucasBoucke Itu lucu, saya biasanya menggunakan Rust untuk proyek saya, tapi saya menulis AoC ini di Haskell. Mereka berdua adalah bahasa yang hebat di domain mereka.
Boiethios

Jawaban:

16

Versi yang diinginkan sedikit berbeda dari yang Anda miliki. Garis

v[v[1]] = 999;

sebenarnya keinginan untuk

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

Ini menghasilkan pesan kesalahan yang sama, tetapi anotasi memberikan petunjuk tentang apa yang terjadi:

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

Perbedaan penting dengan versi yang Anda inginkan adalah urutan evaluasi. Argumen panggilan fungsi dievaluasi dari kiri ke kanan dalam urutan yang tercantum, sebelum benar-benar membuat panggilan fungsi. Dalam hal ini ini berarti bahwa pertama &mut vdievaluasi, saling meminjam v. Selanjutnya, Index::index(&v, 1)harus dievaluasi, tetapi ini tidak mungkin - vsudah dipinjam secara gonta-ganti. Akhirnya, kompiler menunjukkan bahwa referensi yang dapat diubah masih diperlukan untuk pemanggilan fungsi index_mut(), sehingga referensi yang dapat diubah masih hidup ketika referensi bersama dicoba.

Versi yang sebenarnya mengkompilasi memiliki urutan evaluasi yang sedikit berbeda.

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

Pertama, argumen fungsi untuk panggilan metode dievaluasi dari kiri ke kanan, yaitu *v.index(1)dievaluasi terlebih dahulu. Ini menghasilkan usize, dan pinjaman sementara sementara vdapat dirilis kembali. Kemudian, penerima index_mut()dievaluasi, yaitu vdipinjam bersama. Ini berfungsi dengan baik, karena pinjaman bersama telah diselesaikan, dan seluruh ekspresi melewati pemeriksa pinjaman.

Perhatikan bahwa versi yang mengkompilasi hanya melakukannya sejak diperkenalkannya "masa hidup non-leksikal". Dalam versi Rust yang lebih lama, pinjaman bersama akan hidup sampai akhir ekspresi dan menghasilkan kesalahan yang sama.

Solusi terbersih menurut saya adalah menggunakan variabel sementara:

let i = v[1];
v[i] = 999;
Sven Marnach
sumber
Wow! Ada banyak hal yang terjadi di sini! Terima kasih telah meluangkan waktu untuk menjelaskan! (yang menarik "quirks" semacam itu membuat bahasa lebih menarik bagi saya ...). Bisakah Anda juga memberikan petunjuk mengapa *v.index_mut(*v.index_mut(1)) = 999;gagal dengan "tidak bisa meminjam v sebagai bisa berubah lebih dari sekali" ~> seharusnya tidak menjadi kompiler, karena dengan *v.index_mut(*v.index(1)) = 999;dapat mengetahui bahwa pinjaman batin tidak lagi diperlukan?
Lucas Boucke
@LucasBoucke Rust memang memiliki beberapa kebiasaan yang kadang-kadang sedikit tidak nyaman, tetapi dalam kebanyakan kasus solusinya agak sederhana, seperti dalam kasus ini. Kode ini masih cukup mudah dibaca, hanya sedikit berbeda dari yang Anda miliki, jadi dalam praktiknya itu bukan masalah besar.
Sven Marnach
@LucasBoucke Maaf, saya tidak melihat hasil edit Anda sampai sekarang. Hasil dari *v.index(1)adalah nilai yang disimpan di indeks itu, dan nilai itu tidak perlu untuk menjaga pinjaman vtetap hidup. Hasil dari *v.index_mut(1), di sisi lain, adalah ekspresi tempat yang bisa berubah yang secara teori dapat ditugaskan, sehingga tetap membuat pinjaman tetap hidup. Di permukaan, harus mungkin untuk mengajarkan pemeriksa pinjaman bahwa ekspresi tempat dalam konteks ekspresi nilai dapat diperlakukan sebagai ekspresi nilai, jadi mungkin ini akan dikompilasi di beberapa versi Rust yang akan datang.
Sven Marnach
Bagaimana dengan RFC yang akan membatalkan ini ke:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; }
Boiethios
@FrenchBoiethios Saya tidak tahu bagaimana Anda akan meresmikannya, dan saya yakin itu tidak pernah membuatku terbang. Jika Anda ingin mengatasinya, satu-satunya cara yang saya lihat adalah dengan perbaikan pemeriksa pinjaman, mis. Membuatnya mendeteksi bahwa pinjaman yang dapat ditukar dapat mulai di lain waktu, karena itu tidak terlalu dibutuhkan sedini mungkin. (Gagasan khusus ini mungkin juga tidak berhasil.)
Sven Marnach