Mengapa `std :: mem :: drop` tidak persis sama dengan penutupan | _ | () dalam batas sifat peringkat lebih tinggi?

13

Implementasi std::mem::dropdidokumentasikan sebagai berikut:

pub fn drop<T>(_x: T) { }

Karena itu, saya berharap penutupan |_| ()(bahasa sehari-hari dikenal sebagai penutupan toilet ) menjadi pengganti 1: 1 potensial drop, di kedua arah. Namun, kode di bawah ini menunjukkan bahwa droptidak kompatibel dengan sifat peringkat lebih tinggi yang terikat pada parameter fungsi, sedangkan penutupan toilet adalah.

fn foo<F, T>(f: F, x: T)
where
    for<'a> F: FnOnce(&'a T),
{
    dbg!(f(&x));
}

fn main() {
    foo(|_| (), "toilet closure"); // this compiles
    foo(drop, "drop"); // this does not!
}

Pesan kesalahan kompiler:

error[E0631]: type mismatch in function arguments
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^
   |     |
   |     expected signature of `for<'a> fn(&'a _) -> _`
   |     found signature of `fn(_) -> _`

error[E0271]: type mismatch resolving `for<'a> <fn(_) {std::mem::drop::<_>} as std::ops::FnOnce<(&'a _,)>>::Output == ()`
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^ expected bound lifetime parameter 'a, found concrete lifetime

Mempertimbangkan bahwa dropitu seharusnya generik berkenaan dengan ukuran berapa pun T, kedengarannya tidak masuk akal bahwa tanda tangan "yang lebih umum" fn(_) -> _tidak kompatibel dengannya for<'a> fn (&'a _) -> _. Mengapa kompiler tidak menerima tanda tangan di dropsini, dan apa yang membuatnya berbeda ketika penutupan toilet ditempatkan sebagai penggantinya?

E_net4 downvoter
sumber

Jawaban:

4

Inti dari masalah ini adalah bahwa dropbukan fungsi tunggal, melainkan set fungsi parameter yang masing-masing menjatuhkan beberapa jenis tertentu. Untuk memenuhi ikatan sifat peringkat lebih tinggi (selanjutnya hrtb), Anda memerlukan fungsi tunggal yang dapat secara bersamaan mengambil referensi ke suatu tipe dengan masa pakai yang diberikan.


Kami akan menggunakan dropsebagai contoh khas kami dari fungsi generik, tetapi semua ini berlaku lebih umum juga. Berikut kode untuk referensi: fn drop<T>(_: T) {}.

Secara konseptual, dropbukan fungsi tunggal, melainkan fungsi untuk setiap jenis yang mungkin T. Setiap instance dari drophanya mengambil argumen dari tipe tunggal. Ini disebut monomorphization . Jika berbeda Tdigunakan dengan drop, versi berbeda dropdikompilasi. Itu sebabnya Anda tidak bisa melewatkan fungsi umum sebagai argumen dan menggunakan fungsi itu secara umum penuh (lihat pertanyaan ini )

Di sisi lain, fungsi seperti fn pass(x: &i32) -> &i32 {x}memenuhi hrtb for<'a> Fn(&'a i32) -> &'a i32. Tidak seperti dropkami, kami memiliki satu fungsi yang secara simultan memuaskan Fn(&'a i32) -> &'a i32untuk setiap kehidupan 'a. Ini tercermin dari bagaimana passbisa digunakan.

fn pass(x: &i32) -> &i32 {
    x
}

fn two_uses<F>(f: F)
where
    for<'a> F: Fn(&'a i32) -> &'a i32, // By the way, this can simply be written
                                       // F: Fn(&i32) -> &i32 due to lifetime elision rules.
                                       // That applies to your original example too.
{
    {
        // x has some lifetime 'a
        let x = &22;
        println!("{}", f(x));
        // 'a ends around here
    }
    {
        // y has some lifetime 'b
        let y = &23;
        println!("{}", f(y));
        // 'b ends around here
    }
    // 'a and 'b are unrelated since they have no overlap
}

fn main() {
    two_uses(pass);
}

(tempat bermain)

Dalam contoh, masa hidup 'adan 'btidak memiliki hubungan satu sama lain: tidak sepenuhnya mencakup yang lain. Jadi tidak ada hal subtipe yang terjadi di sini. Satu contoh passbenar-benar digunakan dengan dua masa hidup yang berbeda dan tidak terkait.

Inilah sebabnya mengapa droptidak memuaskan for<'a> FnOnce(&'a T). Contoh khusus apa droppun hanya dapat mencakup satu masa pakai (mengabaikan subtipe). Jika kami melewati dropke two_usesdari contoh di atas (dengan perubahan tanda tangan sedikit dan dengan asumsi compiler mari kita), itu akan harus memilih beberapa seumur hidup tertentu 'adan contoh dropdalam lingkup two_usesakan Fn(&'a i32)untuk beberapa beton seumur hidup 'a. Karena fungsi hanya akan berlaku untuk masa pakai tunggal 'a, tidak mungkin untuk menggunakannya dengan dua masa hidup yang tidak terkait.

Jadi mengapa penutupan toilet mendapat hrtb? Ketika menyimpulkan tipe untuk penutupan, jika tipe yang diharapkan mengisyaratkan bahwa diperlukan ikatan sifat yang berperingkat lebih tinggi, kompiler akan mencoba membuat satu cocok . Dalam hal ini, berhasil.


Masalah # 41078 terkait erat dengan ini dan khususnya, komentar eddyb di sini pada dasarnya memberikan penjelasan di atas (meskipun dalam konteks penutupan, bukan fungsi biasa). Masalahnya sendiri tidak membahas masalah saat ini. Alih-alih membahas apa yang terjadi jika Anda menetapkan penutupan toilet ke variabel sebelum menggunakannya (coba saja!).

Mungkin saja situasinya akan berubah di masa mendatang, tetapi akan membutuhkan perubahan yang cukup besar dalam bagaimana fungsi generik di-monomorfasikan.

SCappella
sumber
4

Singkatnya, kedua garis harus gagal. Tetapi karena satu langkah dalam cara lama menangani masa pakai hrtb, yaitu pemeriksaan kebocoran , saat ini memiliki beberapa masalah kesehatan, rustcakhirnya (salah) menerima satu dan meninggalkan yang lain dengan pesan kesalahan yang sangat buruk.

Jika Anda menonaktifkan pemeriksaan kebocoran dengan rustc +nightly -Zno-leak-check, Anda akan dapat melihat pesan kesalahan yang lebih masuk akal:

error[E0308]: mismatched types
  --> src/main.rs:10:5
   |
10 |     foo(drop, "drop");
   |     ^^^ one type is more general than the other
   |
   = note: expected type `std::ops::FnOnce<(&'a &str,)>`
              found type `std::ops::FnOnce<(&&str,)>`

Interpretasi saya terhadap kesalahan ini adalah bahwa &xdi dalam tubuh foofungsi hanya memiliki rentang hidup terbatas pada tubuh tersebut, sehingga f(&x)juga memiliki rentang hidup yang sama yang tidak mungkin memenuhi for<'a>kuantifikasi universal yang diperlukan oleh sifat terikat.

Pertanyaan yang Anda sajikan di sini hampir identik dengan masalah # 57642 , yang juga memiliki dua bagian yang kontras.

Cara baru untuk memproses masa hidup hrtb adalah dengan menggunakan apa yang disebut alam semesta . Niko memiliki WIP untuk menangani pemeriksaan kebocoran dengan alam semesta. Di bawah rezim baru ini, kedua bagian masalah # 57642 yang dikaitkan di atas dikatakan semuanya gagal dengan diagnosis yang jauh lebih jelas. Saya kira kompiler harus dapat menangani kode contoh Anda dengan benar saat itu juga.

edwardw
sumber