Pertanyaan Mengapa menghubungkan masa berlaku hanya dengan referensi yang bisa berubah?


Beberapa hari yang lalu, ada pertanyaan di mana seseorang memiliki masalah dengan masa hidup terkait referensi yang bisa berubah menjadi tipe yang berisi data yang dipinjam itu sendiri. Masalahnya adalah menyediakan referensi ke tipe dengan meminjam masa hidup yang sama dengan data yang dipinjam di dalam jenis. Saya mencoba membuat ulang masalah:

struct VecRef<'a>(&'a Vec<u8>);

struct VecRefRef<'a>(&'a mut VecRef<'a>);

fn main() {
    let v = vec![8u8, 9, 10];
    let mut ref_v = VecRef(&v);
    create(&mut ref_v);
}

fn create<'b, 'a>(r: &'b mut VecRef<'a>) {
    VecRefRef(r);
}

Kode contoh

Saya secara eksplisit dianotasi 'b disini create(). Ini tidak dikompilasi:

error[E0623]: lifetime mismatch
  --> src/main.rs:12:15
   |
11 | fn create<'b, 'a>(r: &'b mut VecRef<'a>) {
   |                      ------------------
   |                      |
   |                      these two types are declared with different lifetimes...
12 |     VecRefRef(r);
   |               ^ ...but data from `r` flows into `r` here

Seumur hidup 'b adalah sesuatu seperti 'b < 'a dan karena itu melanggar batasan dalam VecRefRef<'a> untuk seumur hidup yang sama persis seperti yang disebut VecRef<'a>.

Saya menghubungkan seumur hidup referensi yang bisa berubah dengan data yang dipinjam di dalam VecRef<'a>:

fn create<'a>(r: &'a mut VecRef<'a>) {
    VecRefRef(r);
}

Sekarang berhasil. Tapi kenapa? Bagaimana saya bahkan bisa memberikan referensi seperti itu? Referensi yang bisa berubah r dalam create() memiliki masa hidup VecRef<'a> tidak 'a. Mengapa masalahnya tidak didorong ke sisi fungsi yang memanggil create()?

Saya melihat hal lain yang saya tidak mengerti. Jika saya menggunakan kekal referensi di dalam VecRefRef<'a> struct, entah bagaimana tidak masalah lagi ketika memasok referensi dengan seumur hidup yang berbeda 'a:

struct VecRef<'a>(&'a Vec<u8>);

struct VecRefRef<'a>(&'a VecRef<'a>); // now an immutable reference

fn main() {
    let v = vec![8u8, 9, 10];
    let mut ref_v = VecRef(&v);
    create(&mut ref_v);
}

fn create<'b, 'a>(r: &'b mut VecRef<'a>) {
    VecRefRef(r);
}

Kode contoh

Ini bekerja sebagai lawan contoh pertama di mana VecRefRef<'a> mengambil referensi yang bisa berubah menjadi a VecRef<'a>. Saya tahu bahwa referensi yang dapat berubah memiliki aturan aliasing yang berbeda (tidak ada aliasing sama sekali) tetapi apa hubungannya dengan masa hidup yang terhubung di sini?


12
2017-08-23 11:02


asal


Jawaban:


PERINGATAN: Saya berbicara dari tingkat keahlian yang tidak benar-benar saya miliki. Mengingat panjangnya posting ini, saya mungkin salah besar beberapa kali.

TL; DR: Masa hidup dari nilai-nilai tingkat atas adalah kontravarian. Nilai yang direferensikan seumur hidup adalah invarian.

Memperkenalkan masalah

Anda dapat menyederhanakan contoh Anda secara signifikan, dengan mengganti VecRef<'a> dengan &'a mut T.

Selanjutnya, yang harus dihapus main, karena itu lebih lengkap untuk dibicarakan umum perilaku fungsi dari beberapa instantiasi seumur hidup tertentu.

Dari pada VecRefRefKonstruktor, mari gunakan fungsi ini:

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

Sebelum melangkah lebih jauh, penting untuk memahami bagaimana kehidupan secara implisit dituangkan dalam Rust. Ketika seseorang memberikan pointer ke nama lain yang dianotasi secara eksplisit, pemaksaan seumur hidup terjadi. Hal yang paling jelas ini memungkinkan mengecilkan umur pointer tingkat atas. Dengan demikian, ini bukan langkah yang khas.

Ke samping: Saya katakan "secara eksplisit dianotasikan" karena dalam kasus-kasus implisit seperti let x = y atau fn f<T>(_: T) {}, reborrowing sepertinya tidak terjadi. Tidak jelas apakah ini dimaksudkan.

Contoh lengkapnya adalah

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

yang memberikan kesalahan yang sama:

error[E0623]: lifetime mismatch
 --> src/main.rs:5:26
  |
4 |     fn use_ref_ref<'a, 'b>(reference: &'a mut &'b mut ()) {
  |                                       ------------------
  |                                       |
  |                                       these two types are declared with different lifetimes...
5 |         use_same_ref_ref(reference);
  |                          ^^^^^^^^^ ...but data from `reference` flows into `reference` here

Perbaikan sepele

Satu dapat memperbaikinya dengan melakukan

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a>(reference: &'a mut &'a mut ()) {
    use_same_ref_ref(reference);
}

karena tanda tangan sekarang secara logis sama. Namun, yang tidak jelas adalah alasannya

let mut val = ();
let mut reference = &mut val;
let ref_ref = &mut reference;

use_ref_ref(ref_ref);

mampu menghasilkan &'a mut &'a mut ().

Perbaikan yang kurang sepele

Seseorang dapat menegakkannya 'a: 'b

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

Ini berarti bahwa umur referensi luar adalah paling sedikit sebesar masa hidup bagian dalam.

Itu tidak jelas

  • Mengapa &'a mut &'b mut () tidak bisa di-cast &'c mut &'c mut (), atau

  • apakah ini lebih baik daripada &'a mut &'a mut ().

Saya berharap dapat menjawab pertanyaan-pertanyaan ini.

Tidak diperbaiki

Menegaskan 'b: 'a tidak memperbaiki masalah.

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a, 'b: 'a>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

Perbaikan lain yang lebih mengejutkan

Membuat referensi luar berubah memperbaiki masalah

fn use_same_ref_ref<'c>(reference: &'c &'c mut ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a &'b mut ()) {
    use_same_ref_ref(reference);
}

Dan yang lebih mengejutkan lagi!

Membuat sebuah batin referensi tidak berubah tidak membantu sama sekali!

fn use_same_ref_ref<'c>(reference: &'c mut &'c ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a mut &'b ()) {
    use_same_ref_ref(reference);
}

TAPI KENAPA??!

Dan alasannya adalah ...

Tunggu dulu, pertama kita tutupi variannya

Dua konsep yang sangat penting dalam ilmu komputer adalah kovarian dan kontravarian. Saya tidak akan menggunakan nama-nama ini (saya akan sangat eksplisit tentang cara saya mentransmisikan sesuatu) tetapi nama-nama itu masih sangat berguna mencari di internet.

Sangat penting untuk memahami konsep varians sebelum Anda dapat memahami perilaku di sini. Jika Anda telah mengambil kursus universitas yang mencakup ini, atau Anda dapat mengingatnya dari beberapa konteks lain, Anda berada dalam posisi yang baik. Anda mungkin masih menghargai bantuan yang menghubungkan ide itu dengan masa hidup.

Kasus sederhana - penunjuk normal

Pertimbangkan beberapa posisi tumpukan dengan penunjuk:

    ║ Name      │ Type                │ Value
 ───╫───────────┼─────────────────────┼───────
  1 ║ val       │ i32                 │ -1
 ───╫───────────┼─────────────────────┼───────
  2 ║ reference │ &'x mut i32         │ 0x1

Tumpukan tumbuh ke bawah, sehingga reference posisi stack dibuat setelah val, dan akan dihapus sebelumnya val aku s.

Pertimbangkan yang Anda lakukan

let new_ref = reference;

mendapatkan

    ║ Name      │ Type        │ Value  
 ───╫───────────┼─────────────┼─────── 
  1 ║ val       │ i32         │ -1     
 ───╫───────────┼─────────────┼─────── 
  2 ║ reference │ &'x mut i32 │ 0x1    
 ───╫───────────┼─────────────┼─────── 
  3 ║ new_ref   │ &'y mut i32 │ 0x1    

Masa aktif apa yang valid untuk 'y?

Pertimbangkan dua operasi penunjuk yang bisa berubah:

  • Baca baca
  • Menulis

Baca baca mencegah 'y dari tumbuh, karena a 'x referensi hanya menjamin objek tetap hidup selama ruang lingkup 'x. Namun, Baca baca tidak mencegah 'y dari menyusut karena apa pun yang dibaca ketika nilai runcing menjadi hidup akan menghasilkan nilai independen seumur hidup 'y.

Menulis mencegah 'y dari tumbuh juga, karena seseorang tidak dapat menulis ke penunjuk yang tidak valid. Namun, menulis tidak mencegah 'y dari menyusut karena setiap menulis ke pointer menyalin nilai dalam, yang membuatnya tetap dari seumur hidup 'y.

Casing keras - penunjuk penunjuk

Pertimbangkan beberapa posisi tumpukan dengan penunjuk penunjuk:

    ║ Name      │ Type                │ Value  
 ───╫───────────┼─────────────────────┼─────── 
  1 ║ val       │ i32                 │ -1     
 ───╫───────────┼─────────────────────┼─────── 
  2 ║ reference │ &'a mut i32         │ 0x1    
 ───╫───────────┼─────────────────────┼─────── 
  3 ║ ref_ref   │ &'x mut &'a mut i32 │ 0x2    

Pertimbangkan yang Anda lakukan

let new_ref_ref = ref_ref;

mendapatkan

    ║ Name        │ Type                │ Value  
 ───╫─────────────┼─────────────────────┼─────── 
  1 ║ val         │ i32                 │ -1     
 ───╫─────────────┼─────────────────────┼─────── 
  2 ║ reference   │ &'a mut i32         │ 0x1    
 ───╫─────────────┼─────────────────────┼─────── 
  3 ║ ref_ref     │ &'x mut &'a mut i32 │ 0x2    
 ───╫─────────────┼─────────────────────┼─────── 
  4 ║ new_ref_ref │ &'y mut &'b mut i32 │ 0x2    

Sekarang ada dua pertanyaan:

  1. Masa aktif apa yang valid untuk 'y?

  2. Masa aktif apa yang valid untuk 'b?

Pertama mari kita pertimbangkan y dengan dua operasi penunjuk yang bisa berubah:

  • Baca baca
  • Menulis

Baca baca mencegah 'y dari tumbuh, karena a 'x referensi hanya menjamin objek tetap hidup selama ruang lingkup 'x. Namun, Baca baca tidak mencegah 'y dari menyusut karena apa pun yang dibaca ketika nilai runcing menjadi hidup akan menghasilkan nilai independen seumur hidup 'y.

Menulis mencegah 'y dari tumbuh juga, karena seseorang tidak dapat menulis ke penunjuk yang tidak valid. Namun, menulis tidak mencegah 'y dari menyusut karena setiap menulis ke pointer menyalin nilai dalam, yang membuatnya tetap dari seumur hidup 'y.

Ini sama seperti sebelumnya.

Sekarang, pertimbangkan 'b dengan dua operasi penunjuk yang bisa berubah

Baca baca mencegah 'b dari tumbuh, karena jika salah satu adalah untuk mengekstrak pointer batin dari pointer luar Anda akan bisa membacanya setelah 'a telah kadaluwarsa.

Menulis mencegah 'b dari tumbuh juga, karena jika salah satu adalah untuk mengekstrak pointer batin dari pointer luar Anda akan dapat menulis setelahnya 'a telah kadaluwarsa.

Baca baca dan menulis bersama-sama juga mencegah 'b dari menyusut, karena skenario ini:

let ref_ref: &'x mut &'a mut i32 = ...;

{
    // Has lifetime 'b, which is smaller than 'a
    let new_val: i32 = 123;

    // Shrink 'a to 'b
    let new_ref_ref: &'x mut &'b mut i32 = ref_ref;

    *new_ref_ref = &mut new_val;
}

// new_ref_ref is out of scope, so ref_ref is usable again
let ref_ref: &'a mut i32 = *ref_ref;
// Oops, we have an &'a mut i32 pointer to a dropped value!

Jadi, 'b tidak bisa menyusut dan tidak bisa tumbuh 'a, jadi 'a == 'bpersis.

Oke, apakah ini menyelesaikan pertanyaan kita?

Ingat kodenya?

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

Ketika Anda menelepon use_same_ref_ref, pemain dicoba

&'a mut &'b mut ()  →  &'c mut &'c mut ()

Sekarang perhatikan itu 'b == 'c karena diskusi kita tentang perbedaan. Jadi kami benar-benar casting

&'a mut &'b mut ()  →  &'b mut &'b mut ()

Luar &'a hanya bisa menyusut. Untuk melakukan ini, kompilator perlu tahu

'a: 'b

Compiler tidak tahu ini, dan gagal kompilasi.

Bagaimana dengan contoh kami yang lain?

Yang pertama adalah

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a>(reference: &'a mut &'a mut ()) {
    use_same_ref_ref(reference);
}

Dari pada 'a: 'b, kompilator sekarang membutuhkan 'a: 'a, yang sepele benar.

Yang kedua langsung menegaskan 'a: 'b

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

Yang ketiga menegaskan 'b: 'a

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a, 'b: 'a>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

Ini tidak berhasil, karena ini bukan pernyataan yang diperlukan.

Bagaimana dengan kekekalan?

Kami punya dua kasus di sini. Yang pertama adalah membuat referensi luar berubah.

fn use_same_ref_ref<'c>(reference: &'c &'c mut ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a &'b mut ()) {
    use_same_ref_ref(reference);
}

Yang ini berhasil. Mengapa?

Nah, pertimbangkan masalah kita dengan menyusut &'b dari sebelumnya:

Baca baca dan menulis bersama-sama juga mencegah 'b dari menyusut, karena skenario ini:

let ref_ref: &'x mut &'a mut i32 = ...;

{
    // Has lifetime 'b, which is smaller than 'a
    let new_val: i32 = 123;

    // Shrink 'a to 'b
    let new_ref_ref: &'x mut &'b mut i32 = ref_ref;

    *new_ref_ref = &mut new_val;
}

// new_ref_ref is out of scope, so ref_ref is usable again
let ref_ref: &'a mut i32 = *ref_ref;
// Oops, we have an &'a mut i32 pointer to a dropped value!

Jadi, 'b tidak bisa menyusut dan tidak bisa tumbuh 'a, jadi 'a == 'bpersis.

Ini hanya dapat terjadi karena kita dapat menukar referensi internal untuk referensi baru yang tidak cukup panjang. Jika kami tidak dapat menukar referensi, ini bukan masalah. Jadi, menyusutkan usia referensi batin adalah mungkin.

Dan yang gagal?

Membuat referensi batin berubah tidak membantu:

fn use_same_ref_ref<'c>(reference: &'c mut &'c ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a mut &'b ()) {
    use_same_ref_ref(reference);
}

Ini masuk akal ketika Anda menganggap bahwa masalah yang disebutkan sebelumnya tidak pernah melibatkan pembacaan apa pun dari referensi dalam. Sebenarnya, inilah kode bermasalah yang dimodifikasi untuk menunjukkan bahwa:

let ref_ref: &'x mut &'a i32 = ...;

{
    // Has lifetime 'b, which is smaller than 'a
    let new_val: i32 = 123;

    // Shrink 'a to 'b
    let new_ref_ref: &'x mut &'b i32 = ref_ref;

    *new_ref_ref = &new_val;
}

// new_ref_ref is out of scope, so ref_ref is usable again
let ref_ref: &'a i32 = *ref_ref;
// Oops, we have an &'a i32 pointer to a dropped value!

Ada pertanyaan lain

Sudah cukup lama, tetapi pikirkan kembali:

Seseorang dapat menegakkannya 'a: 'b

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

Ini berarti bahwa umur referensi luar adalah paling sedikit sebesar masa hidup bagian dalam.

Itu tidak jelas

  • Mengapa &'a mut &'b mut () tidak bisa di-cast &'c mut &'c mut (), atau

  • apakah ini lebih baik daripada &'a mut &'a mut ().

Saya berharap dapat menjawab pertanyaan-pertanyaan ini.

Kami telah menjawab pertanyaan pertama, tapi bagaimana dengan yang kedua? Apakah 'a: 'b mengizinkan lebih dari 'a == 'b?

Pertimbangkan beberapa pemanggil dengan tipe &'x mut &'y mut (). Jika 'x : 'y, maka akan secara otomatis dilemparkan ke &'y mut &'y mut (). Sebaliknya, jika 'x == 'y, kemudian 'x : 'y sudah ada! Perbedaannya hanya penting jika Anda ingin mengembalikan tipe yang mengandung 'x kepada pemanggil, yang merupakan satu-satunya yang dapat membedakan keduanya. Karena ini tidak terjadi di sini, keduanya setara.

Satu hal lagi

Jika Anda menulis

let mut val = ();
let mut reference = &mut val;
let ref_ref = &mut reference;

use_ref_ref(ref_ref);

dimana use_ref_ref didefinisikan

fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

bagaimana kode tersebut dapat diberlakukan 'a: 'b? Terlihat pada inspeksi seperti sebaliknya benar!

Yah, ingat itu

let reference = &mut val;

mampu mengecilkan umurnya, karena itu adalah masa hidup luar pada titik ini. Dengan demikian, itu bisa merujuk ke seumur hidup lebih kecil dari kehidupan nyata val, bahkan ketika penunjuk berada di luar masa hidup itu!


13
2017-08-23 23:06



Referensi yang bisa berubah r dalam create() memiliki masa hidup VecRef<'a> tidak 'a

Ini adalah sumber kebingungan yang umum. Periksa definisi fungsi ini:

fn identity<'a, T>(val: &'a T) -> &'a T { val }

Dalam definisi fungsi, 'a adalah umum parameter seumur hidup, yang sejajar dengan parameter tipe generik (T). Ketika fungsi itu dipanggil, si penelepon memutuskan apa nilai-nilai konkretnya 'a dan T akan. Mari kita lihat kembali Anda main:

fn main() {
    let v = vec![8u8, 9, 10];   // 1 |-lifetime of `v`
    let mut ref_v = VecRef(&v); // 2 |  |-lifetime of `ref_v` 
    create(&mut ref_v);         // 3 |  |
}

v akan hidup untuk seluruh proses main (1-3), tapi ref_v hanya hidup untuk dua pernyataan akhir (2-3). Perhatikan itu ref_v  mengacu pada nilai yang bertahan lama. Jika Anda kemudian mengambil referensi ke ref_v, Anda memiliki referensi untuk sesuatu yang hidup dari (2-3) yang itu sendiri memiliki referensi untuk sesuatu yang hidup dari (1-3).

Lihat metode tetap Anda:

fn create<'a>(r: &'a mut VecRef<'a>)

Ini mengatakan itu untuk panggilan fungsi ini, referensi ke VecRef dan referensi yang dikandungnya harus sama. Ada seumur hidup yang bisa dipetik yang memuaskan ini - (2-3).

Perhatikan bahwa definisi struktur Anda saat ini mengharuskan dua masa kehidupan menjadi sama. Anda dapat membiarkan mereka berbeda:

struct VecRefRef<'a, 'b: 'a>(&'a mut VecRef<'b>);
fn create<'a, 'b>(r: &'a mut VecRef<'b>)

Perhatikan bahwa Anda harus menggunakan sintaks 'b: 'a untuk menunjukkan bahwa seumur hidup 'b akan hidup lebih lama 'a.

Jika saya menggunakan referensi yang tidak dapat diubah [...], entah bagaimana itu tidak menjadi masalah lagi

Ini saya kurang yakin tentang. Saya percaya bahwa apa yang terjadi adalah karena Anda memiliki pinjaman yang tidak dapat diubah, tidak apa-apa bagi compiler untuk melakukan reborrow pada lingkup yang lebih kecil untuk Anda secara otomatis. Ini memungkinkan masa hidup untuk dicocokkan. Seperti yang Anda tunjukkan, referensi yang bisa berubah tidak boleh memiliki alias, bahkan yang memiliki lingkup lebih kecil, sehingga compiler tidak dapat membantu dalam kasus itu.


4
2017-08-23 13:19