Rust: & mut self pinjam bertentangan dengan dirinya sendiri.

Dibuat pada 3 Feb 2015  ·  20Komentar  ·  Sumber: rust-lang/rust

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

A-NLL A-borrow-checker A-typesystem C-bug NLL-polonius T-compiler

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 :)

Semua 20 komentar

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
}

Boks

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.

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)

Apakah halaman ini membantu?
0 / 5 - 0 peringkat