Saya tidak tahu mengapa ini terjadi tetapi rovar dan XMPPwocky di IRC percaya itu adalah bug kompiler.
struct A {
a: i32
}
impl A {
fn one(&mut self) -> &i32{
self.a = 10;
&self.a
}
fn two(&mut self) -> &i32 {
loop {
let k = self.one();
if *k > 10i32 {
return k;
}
}
}
}
... memberikan kesalahan berikut ...
<anon>:12:21: 12:25 error: cannot borrow `*self` as mutable more than once at a time
<anon>:12 let k = self.one();
^~~~
<anon>:12:21: 12:25 note: previous borrow of `*self` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*self` until the borrow ends
<anon>:12 let k = self.one();
^~~~
<anon>:17:6: 17:6 note: previous borrow ends here
<anon>:10 fn two(&mut self) -> &i32 {
...
<anon>:17 }
^
error: aborting due to previous error
playpen: application terminated with error code 101
Menariknya, jika metode kedua diubah menjadi ...
fn two(&mut self) -> &i32 {
loop {
let k = self.one();
return k;
}
}
Ini akan mengkompilasi dengan baik.
boks: http://is.gd/mTkfw5
Saya yakin ini adalah perilaku yang benar. Untuk mengetahui alasannya, pertimbangkan kode dengan anotasi seumur hidup eksplisit:
struct A {
a: i32
}
impl A {
fn one<'a>(&'a mut self) -> &'a i32{
self.a = 10;
&self.a
}
fn two<'a>(&'a mut self) -> &'a i32 {
loop {
let k = self.one();
if *k > 10i32 {
return k;
}
}
}
}
two
perlu mengembalikan referensi dengan masa pakai yang sama dengan pinjaman yang bisa berubah yang diberikan padanya. Namun, karena mendapatnya. Ini berarti k
harus memiliki seumur hidup 'a
. Namun, untuk nilai yang dikembalikan dari one
agar seumur hidup 'a
, masukan ke one
juga harus memiliki masa pakai 'a
. Ini berarti Rust dipaksa untuk tidak "meminjam kembali" self
dan memindahkannya ke one
. Karena referensi yang dapat berubah bersifat linier, mereka hanya dapat dipindahkan sekali, tetapi loop
berarti bahwa one
mungkin perlu dipanggil dengan referensi yang dapat berubah yang sama berulang kali.
Contoh kedua hanya berfungsi karena Rust dapat melihat bahwa loop hanya akan berjalan sekali, jadi self
hanya akan dipindahkan satu kali.
Ini terlihat seperti masalah pinjaman non-leksikal. Anda bisa mendapatkan perilaku serupa tanpa loop sama sekali:
struct Foo { data: Option<i32> }
fn main() {
let mut x = Foo{data: Some(1)};
foo(&mut x);
}
fn foo(x: &mut Foo) -> Option<&mut i32> {
if let Some(y) = x.data.as_mut() {
return Some(y);
}
println!("{:?}", x.data);
None
}
<anon>:14:22: 14:28 error: cannot borrow `x.data` as immutable because it is also borrowed as mutable
<anon>:14 println!("{:?}", x.data);
^~~~~~
note: in expansion of format_args!
<std macros>:2:43: 2:76 note: expansion site
<std macros>:1:1: 2:78 note: in expansion of println!
<anon>:14:5: 14:30 note: expansion site
<anon>:10:22: 10:28 note: previous borrow of `x.data` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x.data` until the borrow ends
<anon>:10 if let Some(y) = x.data.as_mut() {
^~~~~~
<anon>:16:2: 16:2 note: previous borrow ends here
<anon>:9 fn foo(x: &mut Foo) -> Option<&mut i32> {
...
<anon>:16 }
^
error: aborting due to previous error
Rustc tidak bisa "menangani" dengan pengembalian pinjaman bersyarat.
CC @pcwal
Saya pikir masalahnya sebenarnya bukan mutabilitas nilai kembali yang digunakan tetapi mutabilitas argumen fungsi.
Kode berikut memanggil fungsi mutasi yang mengembalikan referensi tetap. Setelah itu saya tidak dapat mengambil referensi yang tidak dapat diubah.
struct Foo (u8);
impl Foo {
fn bar(&mut self) -> &u8 {
self.0 += 1;
&self.0
}
}
fn main() {
let mut x = Foo(42);
let a = x.bar(); // note: borrow of `x` occurs here
let b = x.0; // error: cannot use `x.0` because it was mutably borrowed
}
Saya masih mengalami masalah ini paling lambat setiap malam. Tampaknya ia memperlakukan pengembalian sebagai memiliki masa hidup hingga akhir fungsi, bahkan jika fungsi tanpa pengembalian melewati pemeriksa peminjam. Ini tampak agak aneh, mengingat bagaimana titik pengembaliannya mengakhiri fungsi lebih awal.
Jika Anda melihat pada contoh @Gankro , ini melewati pemeriksa peminjam jika Anda menghapus kata kunci return, meskipun fungsi yang dihasilkan tidak akan berfungsi dengan baik.
Ini diblokir pada masa hidup non-leksikal, yang pada gilirannya diblokir pada MIR. Idealnya ini akan diperbaiki pada akhir 2016, tetapi perbaikan tidak akan segera dilakukan.
Beberapa diskusi tentang internal: https://internals.rust-lang.org/t/relaxing-the-borrow-checker-for-fn-mut-self-t/3256
Diskusi fitur pinjaman non-leksikal: https://github.com/rust-lang/rfcs/issues/811
Jadi membaca [utas internal] membuat saya merasa ini tidak bisa diperbaiki (bahkan dengan NLL). Bisakah seseorang mengkonfirmasi? (@eddyb?)
cc @ istirahat-lama / lama
Ini memang NLL dan kalau tidak salah akan dibenahi dengan berbagai usulan yang sudah saya buat. Ini kira-kira sesuai dengan "kasus masalah # 3" dari posting blog pertama saya . Ide dasar (dalam hal formulasi dari posting blog terbaru ) mengapa ini akan berhasil adalah bahwa subtipe peminjaman hanya akan diminta untuk diperpanjang hingga akhir 'a
di cabang if
yang menyebabkan return
.
@nikomatsakis Bisakah Anda menjelaskan lebih lanjut tentang bagaimana NLL berinteraksi dengan masalah ini? Dalam model mental saya tentang referensi &mut
, mereka dapat dilalui baik dengan memindahkan atau meminjam kembali , dan masalah dalam masalah ini adalah bahwa pengembalian memerlukan mode bergerak , dan penggunaan kembali self
setelah itu membutuhkan peminjaman kembali . Dalam pemahaman saya, masa hidup referensi baru yang &mut
dibatasi oleh masa pakai variabel yang memegang &mut
-referensi - dalam hal ini, variabel self
, sehingga dibatasi oleh tubuh fungsi, sehingga tidak dapat meluas ke luar pemanggilan fungsi. Apakah NLL akan mengubah batasan reborrows (atau mungkin tidak ada batasan seperti itu)?
Juga saya bertanya-tanya apakah memperbaiki masalah ini adalah sesuatu yang secara inheren terkait dengan NLL atau mungkin ortogonal? Jika yang terakhir, mungkin ada baiknya melakukan perbaikan sebelum NLL?
Juga, jika NLL akan memperbaiki masalah ini, apakah itu berarti bahwa di bawah NLL Anda tidak perlu secara manual memilih antara pindah dan meminjam kembali?
Apakah ini akan diperbaiki dalam waktu dekat?
@tokopedia
Bisakah Anda menjelaskan lebih lanjut tentang bagaimana NLL berinteraksi dengan masalah ini?
Ringkasannya adalah, hari ini, jika nilai dikembalikan dari fungsi di jalur mana pun , pinjaman harus valid untuk sisa fungsi di semua jalur. Di bawah proposal NLL yang saya tulis, pembatasan itu dicabut.
Secara lebih rinci, jika Anda mengambil tanda tangan yang diuraikan di sini, Anda dapat melihat bahwa kita harus mengembalikan referensi dengan seumur hidup 'a
:
fn two<'a>(&'a mut self) -> &'a i32 { .. }
sekarang perhatikan bahwa kita memanggil let k = self.one()
dan kemudian mengembalikan k
. Ini berarti tipe k
harus &'a i32
. Ini berarti bahwa ketika kita memanggil self.one()
, kita harus memberikan pinjaman self
dengan tipe &'a mut Self
. Oleh karena itu, pada let k = self.one()
kami mengeluarkan pinjaman self
dengan seumur hidup 'a
. Masa pakai ini lebih besar dari loop (dan, memang, seluruh fungsi), jadi tetap ada saat kita berputar, mengarah ke laporan kesalahan. Di bawah aturan NLL yang saya usulkan, masa hidup k
hanya diperlukan untuk memperpanjang ke 'a
jika if
diambil. Oleh karena itu, jika if
tidak diambil, pinjaman dapat berakhir lebih awal.
Apakah itu membantu?
@ ocehan
Apakah ini akan diperbaiki dalam waktu dekat?
Masih ada beberapa langkah yang harus dilakukan sebelum kita dapat melakukan ini, tetapi ada pekerjaan aktif untuk melakukan pemfaktoran ulang yang diperlukan.
@nikomat
Terima kasih, itu agak membantu. Tetapi ada satu hal kecil yang saya tidak mengerti - apa yang sebenarnya terjadi ketika Anda menulis self
dalam kode Anda - cara kerja pinjaman kembali self
. Saya telah membaca eksaplanasi Anda dari "Kasus masalah 3" ( get_default
), di mana Anda memasukkan kode ke dalam pemanggil, tetapi di sana, Anda telah mengubah setiap penggunaan self
menjadi meminjam Variabel map
secara langsung, sehingga desugaring tidak menghapusnya untuk saya.
Di sinilah saya terjebak: Ketika kita menelepon let k = self.one()
, self
tidak dapat dipindahkan (karena dibutuhkan nanti), jadi dianggap sebagai pinjaman. Kemudian, kami secara bersyarat mengembalikan k
, sehingga peminjaman harus memiliki seumur hidup 'a
, yang hidup lebih lama dari pemanggilan fungsi. Tapi! Kami telah meminjam dari self
, yang hanya berfungsi sampai akhir fungsi. Batasan itu tampaknya mempersingkat 'a
, jadi dalam model mental saya, bahkan di bawah NLL, kita harus mendapatkan kesalahan "tidak hidup cukup lama".
@krdln kami sebenarnya telah meminjam dari *self
- yaitu, kami telah meminjam kembali apa yang dimaksud diri. Kami mengizinkan Anda untuk meminjam *self
seumur hidup melebihi self
itu sendiri karena jenis self
menyiratkan 'kunci' seumur hidup 'a
. Karenanya, selama kami dapat menjamin bahwa selama 'a
, self tidak lagi digunakan, hasilnya harus berupa referensi eksklusif - dalam hal ini, setelah fn kembali, self telah muncul, dan karenanya bisa tidak digunakan, jadi hanya menjamin bahwa diri tidak digunakan sampai akhir fn sudah cukup. (Setidaknya saya harap itu benar. =)
Ini diblokir pada masa hidup non-leksikal, yang pada gilirannya diblokir pada MIR. Idealnya ini akan diperbaiki pada akhir 2016, tetapi perbaikan tidak akan segera dilakukan.
Tidak terlalu mengejutkan, tetapi saya mengonfirmasi bahwa contoh awal memang dapat dikompilasi setiap malam jika Anda mengaktifkan fitur masa hidup non-leksikal.
Saya pikir saya mengalami masalah serupa dalam produksi di mana pinjaman yang tidak dapat solusi ).
Saya juga harus menyebutkan bahwa kode starwed tidak lagi dikompilasi pada malam terbaru. @oberien menyarankan regression-from-nightly-to-nightly
", dan juga disarankan agar saya memberi tag @nikomatsakis jika ini adalah masalah yang mendesak :)
Tersandung masalah ini mencoba melakukan sesuatu yang sangat mirip (yang tidak diizinkan oleh nll saat ini):
fn f(vec: &mut Vec<u8>) -> &u8 {
if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
*n = 10;
n
} else {
vec.push(10);
vec.last().unwrap()
}
}
fn main() {
let mut vec = vec![1, 2, 3];
f(&mut vec);
}
error[E0499]: cannot borrow `*vec` as mutable more than once at a time
--> src/main.rs:6:9
|
1 | fn f(vec: &mut Vec<u8>) -> &u8 {
| - let's call the lifetime of this reference `'1`
2 | if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
| --- first mutable borrow occurs here
3 | *n = 10;
4 | n
| - returning this value requires that `*vec` is borrowed for `'1`
5 | } else {
6 | vec.push(10);
| ^^^ second mutable borrow occurs here
error[E0502]: cannot borrow `*vec` as immutable because it is also borrowed as mutable
--> src/main.rs:7:9
|
1 | fn f(vec: &mut Vec<u8>) -> &u8 {
| - let's call the lifetime of this reference `'1`
2 | if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
| --- mutable borrow occurs here
3 | *n = 10;
4 | n
| - returning this value requires that `*vec` is borrowed for `'1`
...
7 | vec.last().unwrap()
| ^^^ immutable borrow occurs here
(kita seharusnya menghapus E-needstest ketika kita menghapus NLL-fixed-by-NLL)
Komentar yang paling membantu
Saya pikir saya mengalami masalah serupa dalam produksi di mana pinjaman yang tidak dapat solusi ).
Saya juga harus menyebutkan bahwa kode starwed tidak lagi dikompilasi pada malam terbaru. @oberien menyarankan
regression-from-nightly-to-nightly
", dan juga disarankan agar saya memberi tag @nikomatsakis jika ini adalah masalah yang mendesak :)