Rust: Masalah pelacakan untuk RFC 2342, "Izinkan `jika` dan `cocok` dalam konstanta"

Dibuat pada 18 Mar 2018  ·  83Komentar  ·  Sumber: rust-lang/rust

Ini adalah masalah pelacakan untuk RFC "Izinkan if dan match dalam konstanta" (rust-lang/rfcs#2342).

Harap alihkan batasan fungsi atau masalah tertentu yang ingin Anda laporkan ke masalah baru dan beri label yang sesuai dengan F-const_if_match sehingga masalah ini tidak dibanjiri komentar singkat yang mengaburkan perkembangan penting.

Langkah:

  • [x] Menerapkan RFC
  • [ ] Sesuaikan dokumentasi ( lihat instruksi di bengkel )
  • [x] Stabilisasi PR ( lihat petunjuk di bengkel )
  • [x] let binding dalam konstanta yang menggunakan operasi hubung singkat && dan || . Ini diperlakukan sebagai item & dan | di dalam const dan static sekarang.

Pertanyaan yang belum terselesaikan:

Tidak ada

A-const-eval A-const-fn B-RFC-approved C-tracking-issue F-const_if_match T-lang disposition-merge finished-final-comment-period

Komentar yang paling membantu

Sekarang #64470 dan #63812 telah digabungkan, semua alat yang diperlukan untuk ini ada di kompiler. Saya masih perlu membuat beberapa perubahan pada sistem kueri seputar kualifikasi const untuk memastikan bahwa itu tidak perlu tidak efisien dengan fitur ini diaktifkan. Kami membuat kemajuan di sini, dan saya yakin implementasi eksperimental ini akan tersedia setiap malam dalam beberapa minggu, bukan bulan (kata-kata terakhir yang terkenal :smile:).

Semua 83 komentar

  1. tambahkan gerbang fitur untuk itu
  2. Terminator switch dan switchInt di https://github.com/rust-lang/rust/blob/master/src/librustc_mir/transform/qualify_consts.rs#L347 harus memiliki kode khusus di jika gerbang fitur aktif
  3. alih-alih memiliki satu blok dasar saat ini (https://github.com/rust-lang/rust/blob/master/src/librustc_mir/transform/qualify_consts.rs#L328) ini perlu beberapa wadah yang memiliki daftar blok dasar itu masih harus diproses.

@oli-obk Ini sedikit lebih rumit karena aliran kontrol yang kompleks berarti analisis aliran data perlu digunakan. Saya perlu kembali ke @alexreg dan mencari cara untuk mengintegrasikan perubahannya.

@eddyb Titik awal yang baik mungkin adalah dengan mengambil cabang const-qualif (dikurangi komit teratas), rebase ke master (tidak akan menyenangkan), dan kemudian tambahkan hal-hal anotasi data, bukan?

Ada berita tentang ini?

@mark-im Sayangnya tidak. Saya pikir @eddyb memang sangat sibuk, karena saya bahkan tidak bisa

thread 'main' panicked at 'assertion failed: position <= slice.len()', libserialize/leb128.rs:97:1
note: Run with `RUST_BACKTRACE=1` for a backtrace.
error: Could not compile `rustc_llvm`.

Caused by:
  process didn't exit successfully: `/Users/alex/Software/rust/build/bootstrap/debug/rustc --crate-name build_script_build librustc_llvm/build.rs --error-format json --crate-type bin --emit=dep-info,link -C opt-level=2 -C metadata=74f2a810ad96be1d -C extra-filename=-74f2a810ad96be1d --out-dir /Users/alex/Software/rust/build/x86_64-apple-darwin/stage1-rustc/release/build/rustc_llvm-74f2a810ad96be1d -L dependency=/Users/alex/Software/rust/build/x86_64-apple-darwin/stage1-rustc/release/deps --extern build_helper=/Users/alex/Software/rust/build/x86_64-apple-darwin/stage1-rustc/release/deps/libbuild_helper-89aaac40d3077cd7.rlib --extern cc=/Users/alex/Software/rust/build/x86_64-apple-darwin/stage1-rustc/release/deps/libcc-ead7d4af4a69e776.rlib` (exit code: 101)
warning: build failed, waiting for other jobs to finish...
error: build failed
command did not execute successfully: "/Users/alex/Software/rust/build/x86_64-apple-darwin/stage0/bin/cargo" "build" "--target" "x86_64-apple-darwin" "-j" "8" "--release" "--manifest-path" "/Users/alex/Software/rust/src/librustc_trans/Cargo.toml" "--features" " jemalloc" "--message-format" "json"
expected success, got: exit code: 101
thread 'main' panicked at 'cargo must succeed', bootstrap/compile.rs:1085:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.
failed to run: /Users/alex/Software/rust/build/bootstrap/debug/bootstrap -i build

Oke, lucunya, saya melakukan rebase lagi hari ini dan sepertinya semuanya baik-baik saja sekarang! Sepertinya ada regresi, dan itu baru saja diperbaiki. Semua ke @eddyb sekarang.

@alexreg Maaf, saya telah beralih ke jadwal tidur lokal dan saya melihat Anda telah mem-ping saya ketika saya bangun tetapi kemudian Anda offline sepanjang hari ketika saya bangun (zona waktu ugh).
Haruskah saya membuat PR dari cabang Anda? Aku lupa apa yang harus kita lakukan dengan itu?

@eddyb Tidak apa-apa heh. Anda harus tidur lebih awal, karena saya biasanya bangun dari jam 20:00 GMT, tapi semuanya baik-baik saja! :-)

Maaf, saya perlu beberapa saat untuk menyadari bahwa rangkaian tambalan yang dimaksud memerlukan penghapusan Qualif::STATIC{,_REF} , yaitu kesalahan tentang mengakses statika pada waktu kompilasi. OTOH, ini sudah rusak dalam hal const fn s dan akses ke static s:

#![feature(const_fn)]
const fn read<T: Copy>(x: &T) -> T { *x }
static FOO: u32 = read(&BAR);
static BAR: u32 = 5;
fn main() {
    println!("{}", FOO);
}

Ini tidak terdeteksi secara statis, sebagai gantinya miri mengeluh bahwa "pointer yang menggantung telah direferensikan" (yang seharusnya benar-benar mengatakan sesuatu tentang static s alih-alih "pointer yang menggantung").

Jadi saya pikir membaca static s pada waktu kompilasi seharusnya baik-baik saja, tetapi beberapa orang ingin const fn menjadi "murni" (yaitu "transparan referensial" atau sekitar itu) saat runtime, yang berarti bahwa const fn membaca dari balik referensi yang didapatnya sebagai argumen tidak masalah, tetapi const fn seharusnya tidak pernah bisa mendapatkan referensi ke static tiba-tiba (termasuk dari const d).

Saya pikir kita dapat terus menyangkal secara statis menyebutkan static s (bahkan jika hanya untuk mengambil referensi mereka) dalam const s, const fn , dan konteks konstan lainnya (termasuk dipromosikan).
Tapi kita masih harus menghapus STATIC_REF hack yang memungkinkan static s untuk mengambil referensi dari static s lain tetapi (dengan buruk mencoba dan gagal) menolak membaca dari belakang referensi tersebut .

Apakah kita memerlukan RFC untuk ini?

Kedengarannya adil membaca dari statika. Keraguan itu membutuhkan RFC, mungkin hanya kawah, tapi saya mungkin bukan yang terbaik untuk mengatakannya.

Perhatikan bahwa kami tidak akan membatasi apa pun, kami akan melonggarkan pembatasan yang sudah dilanggar.

Ah, aku salah membaca. Jadi evaluasi const akan tetap masuk akal, hanya saja tidak transparan secara referensial?

Paragraf terakhir menjelaskan pendekatan transparan referensial (tetapi kita kehilangan properti itu jika kita mulai mengizinkan penyebutan static s dalam const s dan const fn s). Saya tidak berpikir kesehatan benar-benar sedang dibahas.

Yah, "penunjuk yang menggantung" memang terdengar seperti masalah kesehatan, tapi saya percaya Anda akan hal ini!

"menggantung pointer" adalah pesan kesalahan yang buruk, itu hanya miri melarang membaca dari static s. Satu-satunya konteks konstan yang bahkan dapat merujuk ke static s adalah static s lainnya, jadi kami dapat "hanya" mengizinkan pembacaan tersebut, karena semua kode itu selalu berjalan sekali, pada waktu kompilasi.

(dari IRC) Singkatnya, const fn transparan referensial hanya dapat mencapai alokasi beku, tanpa melalui argumen, yang berarti const memerlukan batasan yang sama, dan alokasi non-beku hanya dapat berasal dari static s.

Saya suka menjaga transparansi referensial sehingga ide @eddyb terdengar fantastis!

Ya, saya juga pro membuat const fns murni.

Harap dicatat bahwa rencana tertentu yang tampaknya tidak berbahaya dapat merusak transparansi referensial, misalnya:

let x = 0;
let non_deterministic = &x as *const _ as usize;
if non_deterministic.count_ones() % 2 == 0 {
    // do one thing
} else {
    // do a completely different thing
}

Ini akan gagal dengan kesalahan miri pada waktu kompilasi, tetapi akan menjadi non-deterministik saat runtime (karena kami tidak dapat menandai alamat memori itu sebagai "abstrak" seperti yang dapat dilakukan miri).

EDIT : @Centril memiliki ide untuk membuat operasi pointer mentah tertentu (seperti perbandingan dan gips ke bilangan bulat) unsafe dalam const fn (yang dapat kita lakukan hingga kita menstabilkan const fn ), dan nyatakan bahwa mereka hanya dapat digunakan dengan cara yang diizinkan miri pada waktu kompilasi.
Misalnya, mengurangi dua pointer ke lokal yang sama seharusnya baik-baik saja (Anda mendapatkan jarak relatif yang hanya bergantung pada tipe tata letak, indeks array, dll.), tetapi memformat alamat referensi (melalui {:p} ) adalah penggunaan yang salah dan oleh karena itu fmt::Pointer::fmt tidak dapat ditandai const fn .
Juga tidak satu pun dari impls sifat Ord / Eq untuk pointer mentah dapat ditandai sebagai const (setiap kali kita mendapatkan kemampuan untuk membubuhi keterangan seperti itu), karena mereka aman tetapi operasinya adalah unsafe di const fn .

Tergantung apa yang Anda maksud dengan "tidak berbahaya"... Saya tentu dapat melihat alasan mengapa kami ingin melarang perilaku non-deterministik seperti itu.

Akan luar biasa jika pekerjaan dilanjutkan dalam hal ini.

@lachlansneff Ini bergerak... tidak secepat yang kita inginkan, tetapi pekerjaan sedang dilakukan. Saat ini kami sedang menunggu https://github.com/rust-lang/rust/pull/51110 sebagai pemblokir.

@alexreg Ah, terima kasih. Akan sangat berguna untuk dapat menandai kecocokan atau jika sebagai const meskipun tidak dalam const fn.

ada pembaruan status sekarang setelah #51110 digabungkan?

@programmerjake Saya menunggu umpan balik dari @eddyb di https://github.com/rust-lang/rust/pull/52518 sebelum bisa digabung (semoga segera). Dia sangat sibuk akhir-akhir ini (selalu dalam permintaan tinggi), tetapi dia kembali ke ulasan dan yang lainnya dalam beberapa hari terakhir, jadi saya berharap. Setelah itu, saya kira akan membutuhkan beberapa pekerjaan olehnya secara pribadi, karena menambahkan analisis aliran data yang tepat adalah urusan yang rumit. Kita lihat saja.

Di suatu tempat ke daftar TODO di posting pertama, itu harus ditambahkan untuk menghapus peretasan mengerikan saat ini yang menerjemahkan && dan || menjadi & dan | di dalam konstanta.

@RalfJung Bukankah itu bagian dari evaluasi lama, yang sudah selesai sekarang karena MIRI CTFE sudah ada?

AFAIK kami melakukan terjemahan itu di suatu tempat di penurunan HIR, karena kami memiliki kode di const_qualify yang menolak terminator SwitchInt yang jika tidak akan dihasilkan oleh || / && .

Juga, poin lain: @oli-obk mengatakan di suatu tempat (tetapi saya tidak dapat menemukan di mana) bahwa persyaratannya entah bagaimana lebih rumit daripada yang dipikirkan orang secara naif ... apakah itu "hanya" tentang analisis untuk penurunan/perubahan interior?

apakah itu "hanya" tentang analisis untuk mutabilitas drop/interior?

Saat ini saya sedang mencoba untuk menjernihkannya. Akan kembali kepada Anda ketika saya memiliki semua informasi

Ini statusnya apa? Apakah ini membutuhkan tenaga kerja atau terhalang untuk memecahkan beberapa masalah?

@mark-im Ini diblokir dalam menerapkan analisis aliran data yang tepat untuk kualifikasi const. @eddyb adalah yang paling berpengetahuan luas di bidang ini, dan dia sebelumnya telah melakukan beberapa pekerjaan dalam hal ini. (Aku juga, tapi itu stagnasi...) Jika @eddyb masih tidak punya waktu, mungkin @oli-obk atau @RalfJung bisa segera mengatasi ini. :-)

58403 adalah langkah kecil menuju kualifikasi berbasis aliran data.

@eddyb Anda menyebutkan menjaga transparansi referensial di const fn , yang menurut saya adalah ide yang bagus. Bagaimana jika Anda mencegah penggunaan pointer di const fn ? Jadi sampel kode Anda sebelumnya tidak akan lagi dikompilasi:

let x = 0;
// compile time error: cannot cast reference to pointer in `const fun`
let non_deterministic = &x as *const _ as usize;
if non_deterministic.count_ones() % 2 == 0 {
    // do one thing
} else {
    // do a completely different thing
}

Referensi akan tetap diizinkan tetapi Anda tidak akan diizinkan untuk mengintrospeksi mereka:

let x = 0;
let p = &x;
if *p != 0 {  // this is fine
    // do one thing
} else {
    // do a completely different thing
}

Beri tahu saya jika saya benar-benar tidak masuk akal, saya hanya berpikir ini akan menjadi cara yang baik untuk membuat deterministik ini.

@ jyn514 yang sudah ditutupi oleh pembuatan sebagai uSize gips tidak stabil (https://github.com/rust-lang/rust/issues/51910), namun pengguna juga dapat membandingkan pointer mentah (https://github.com/rust- lang/rust/issues/53020) yang sama buruknya dan dengan demikian juga tidak stabil. Kami dapat menangani ini secara independen dari aliran kontrol.

Ada yang baru tentang ini?

Ada beberapa diskusi di https://rust-lang.zulipchat.com/#narrow/stream/146212 -t-compiler.2Fconst-eval/topic/dataflow-based.20const.20qualification.20MVP

@oli-obk tautan Anda tidak berfungsi. Apa yang dikatakan?

Ini bekerja untuk saya... Anda harus masuk ke Zulip.

@alexreg hmm ya saya kira itu tentang pekerjaan kualifikasi const berdasarkan aliran data. @alexreg apakah Anda tahu mengapa itu diperlukan untuk if dan match dalam konstanta?

jika kami tidak memiliki versi berbasis aliran data, kami secara tidak sengaja mengizinkan &Cell<T> di dalam konstanta atau secara tidak sengaja melarang None::<&Cell<T>> (yang berfungsi pada stabil. Pada dasarnya tidak mungkin untuk mengimplementasikan dengan benar tanpa aliran data (atau implementasi apa pun akan menjadi versi aliran data ad-hoc rusak yang buruk)

@est31 Yah, @oli-obk memahami ini jauh lebih baik daripada saya, tetapi dari tingkat tinggi pada dasarnya apa pun yang melibatkan percabangan akan menjadi predikat analisis aliran data kecuali jika Anda menginginkan banyak kasus tepi. Bagaimanapun, sepertinya orang di Zulip ini sedang mencoba untuk mengerjakannya, dan jika tidak, saya tahu oli-obk dan eddyb memiliki niat untuk, mungkin bulan ini atau berikutnya (sejak terakhir saya berbicara dengan mereka tentang hal itu), meskipun saya bisa 't/tidak akan membuat janji atas nama mereka.

@alexreg @mark-im @est31 @oli-obk Saya seharusnya dapat mempublikasikan implementasi WIP saya dari kualifikasi const berbasis aliran data sekitar minggu ini. Ada banyak bahaya kompatibilitas di sini, jadi mungkin perlu beberapa saat untuk benar-benar menggabungkannya.

Super; menantikannya.

(menyalin dari #57563 per permintaan)

Apakah mungkin untuk kasus khusus bool && bool , bool || bool , dll.? Mereka saat ini dapat dilakukan dalam const fn , tetapi melakukannya memerlukan operator bitwise, yang terkadang tidak diinginkan.

Mereka sudah menjadi kasus khusus dalam item const dan static -- dengan menerjemahkannya ke operasi bitwise. Tetapi casing khusus itu adalah peretasan besar dan sangat sulit untuk memastikan bahwa ini benar. Seperti yang Anda katakan, terkadang juga tidak diinginkan. Jadi kami lebih suka tidak melakukan ini lebih sering.

Melakukan hal yang benar akan memakan waktu sedikit, tetapi itu akan terjadi. Jika kita menumpuk terlalu banyak peretasan dalam waktu yang bersamaan, kita mungkin menyakiti diri sendiri ke sudut yang tidak dapat kita hindari (jika beberapa peretasan itu akhirnya berinteraksi dengan cara yang salah, sehingga secara tidak sengaja menstabilkan perilaku yang tidak kita inginkan).

Sekarang #64470 dan #63812 telah digabungkan, semua alat yang diperlukan untuk ini ada di kompiler. Saya masih perlu membuat beberapa perubahan pada sistem kueri seputar kualifikasi const untuk memastikan bahwa itu tidak perlu tidak efisien dengan fitur ini diaktifkan. Kami membuat kemajuan di sini, dan saya yakin implementasi eksperimental ini akan tersedia setiap malam dalam beberapa minggu, bukan bulan (kata-kata terakhir yang terkenal :smile:).

@ecstatic-morse Senang mendengarnya! Terima kasih atas upaya bersama Anda untuk menyelesaikan ini; Saya pribadi telah tertarik pada fitur ini untuk sementara waktu sekarang.

Akan senang melihat dukungan alokasi tumpukan untuk CTFE setelah ini selesai. Saya tidak tahu apakah Anda atau orang lain tertarik untuk mengerjakan ini, tetapi jika tidak, mungkin saya dapat membantu.

@alexreg Terima kasih!

Diskusi tentang alokasi tumpukan pada waktu kompilasi selesai di rust-rfcs/const-eval#20. AFAIK, perkembangan terbaru sekitar ConstSafe / ConstRefSafe paradigma untuk menentukan apa yang dapat diamati secara langsung/di belakang referensi dalam nilai akhir dari const . Saya pikir ada lebih banyak pekerjaan desain yang dibutuhkan.

Bagi mereka yang mengikuti, #65949 (yang bergantung pada beberapa PR yang lebih kecil) adalah pemblokir berikutnya untuk ini. Meskipun tampaknya hanya terkait secara tangensial, fakta bahwa pemeriksaan/kualifikasi sangat erat digabungkan dengan promosi adalah bagian dari alasan mengapa fitur ini diblokir begitu lama. Saya berencana untuk membuka PR berikutnya yang akan menghapus const-checker lama seluruhnya (saat ini kami menjalankan kedua checker secara paralel). Ini akan menghindari inefisiensi yang saya sebutkan sebelumnya.

Setelah kedua PR yang disebutkan di atas digabungkan, if dan match dalam konstanta akan menjadi beberapa peningkatan diagnostik dan fitur ditandai! Oh, dan juga ujian, begitu banyak ujian...

Jika Anda membutuhkan tes, saya tidak yakin bagaimana memulainya tetapi saya lebih dari bersedia untuk berkontribusi! Beri tahu saya di mana tes harus pergi/seperti apa seharusnya/cabang apa yang harus saya gunakan untuk membuat kode :)

PR berikutnya yang harus diperhatikan adalah #66385. Ini menghapus logika kualifikasi const lama (yang tidak dapat menangani percabangan) sepenuhnya demi versi berbasis aliran data baru.

@ jyn514 Itu akan sangat bagus! Saya akan melakukan ping ketika saya mulai menyusun implementasi. Juga akan sangat membantu bagi orang-orang untuk mencoba melanggar keamanan const (terutama bagian HasMutInterior ) setelah if dan match tersedia pada malam hari.

66507 berisi implementasi awal RFC 2342.

Saya berharap perlu beberapa saat untuk menghilangkan tepi kasar, terutama sehubungan dengan diagnostik, dan cakupan pengujian cukup jarang ( @ jyn514 kita harus berkoordinasi tentang masalah itu). Namun demikian, saya berharap kami dapat merilis ini di balik bendera fitur dalam beberapa minggu ke depan.

Ini diterapkan di #66507 dan sekarang dapat digunakan di nightly terbaru . Ada juga posting blog Inside Rust yang merinci operasi yang baru tersedia serta beberapa masalah yang mungkin Anda temui dengan implementasi yang ada seputar tipe dengan mutabilitas interior atau impl Drop kustom.

Maju dan konstifikasi!

Tampaknya kesetaraan bukan const ? Atau saya salah:

error[E0019]: constant function contains unimplemented expression type
  --> src/liballoc/raw_vec.rs:55:22
   |
55 |         let cap = if mem::size_of::<T>() == 0 { !0 } else { 0 };
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^

error[E0019]: constant function contains unimplemented expression type
  --> src/liballoc/raw_vec.rs:55:19
   |
55 |         let cap = if mem::size_of::<T>() == 0 { !0 } else { 0 };
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

@mark-im Itu memang harus

Tidak yakin apakah ini disengaja, tetapi mencoba mencocokkan pada enum memberikan kesalahan

const fn dengan kode yang tidak dapat dijangkau tidak stabil

terlepas dari kenyataan bahwa enum itu lengkap dan didefinisikan dalam peti yang sama.

@jhpratt dapatkah Anda memposting kode? Saya dapat mencocokkan pada enum sederhana tanpa masalah: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=585e9c2823afcb49c6682f69569c97ea

@jhpratt dapatkah Anda memposting kode? Saya dapat mencocokkan pada enum sederhana tanpa masalah:

di sini:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=13a9fbc4251d7db80f5d63b1dc35a98b

Kalahkan aku beberapa detik. Itu contoh minimal yang menunjukkan kasus saya yang sebenarnya.

@jhpratt Jelas tidak disengaja. Bisakah Anda membuka masalah?

Harap alihkan batasan fungsi atau masalah tertentu yang ingin Anda laporkan ke masalah baru dan beri label yang sesuai dengan F-const_if_match sehingga masalah ini tidak dibanjiri komentar singkat yang mengaburkan perkembangan penting.

@Centril Bukan hal yang buruk untuk dimasukkan ke dalam komentar teratas sehingga komentar Anda tidak terkubur.

Pembaruan status:

Ini siap untuk stabilisasi dari perspektif implementasi, tetapi ada pertanyaan apakah kita ingin mempertahankan aliran data berbasis nilai yang kita miliki saat ini alih-alih yang berbasis tipe (tetapi kurang kuat). Aliran data berbasis nilai sedikit lebih mahal (lebih jauh ke bawah), dan kami membutuhkannya untuk fungsi seperti

const fn foo<T>() {
    let x = Option::<T>::None;
    {x};
}

yang akan ditolak oleh analisis berbasis tipe, karena Option<T> mungkin memiliki destruktor yang sekarang akan mencoba dijalankan dan dengan demikian dapat mengeksekusi kode non-const.

Kita bisa kembali ke analisis berbasis tipe saat ada cabang, tapi itu berarti kita akan menolak

const fn foo<T>(b: bool) {
    let x = Option::<T>::None;
    assert!(b);
    {x};
}

yang mungkin akan sangat mengejutkan pengguna.

@ecstatic-morse menjalankan analisis pada semua fungsi, bukan hanya const fn dan melihat perlambatan hingga 5% (https://perf.rust-lang.org/compare.html?start=93dc97a85381cc52eb872d27e50e4d518926a27c&end=51cf313c7946365d5be38113950703c Perhatikan bahwa ini adalah versi pesimis, karena ini berarti ini juga dijalankan pada fungsi yang tidak akan dan sering kali tidak dapat menjadi const fn .

Ini berarti bahwa jika kita membuat banyak fungsi const fn, kita mungkin melihat beberapa perlambatan kompilasi karena analisis berbasis nilai ini.

Jalan tengah bisa jadi hanya menjalankan analisis berbasis nilai jika analisis berbasis tipe gagal. Ini berarti bahwa jika tidak ada destruktor, kita tidak perlu menjalankan analisis berbasis nilai untuk mengetahui apakah destruktor yang tidak ada tidak akan dijalankan (ya saya tahu, banyak negasi di sini). Untuk mengungkapkannya secara berbeda: kami hanya menjalankan analisis berbasis nilai jika ada destruktor.

Saya menominasikan ini untuk diskusi @rust-lang/lang sehingga kami dapat mengetahui apakah kami ingin ikut

  • opsi berbasis tipe di hadapan loop atau cabang (memberikan perilaku aneh kepada pengguna)
  • analisis berbasis nilai penuh (lebih mahal, tetapi ekspresi penuh bagi pengguna)
  • skema campuran, masih ekspresif penuh untuk pengguna, beberapa kompleksitas impl tambahan, tetapi harus mengurangi masalah waktu kompilasi ke kasus yang membutuhkannya.

@oli-obk

opsi berbasis tipe di hadapan loop atau cabang (memberikan perilaku aneh kepada pengguna)

Hanya untuk memeriksa ini: apakah ini bukan pilihan untuk memiliki analisis berbasis tipe bahkan dalam kode garis lurus? Saya membayangkan itu agak tidak kompatibel, mengingat kami sudah menerima yang berikut ( taman bermain ):

struct Foo { }

impl Drop for Foo {
    fn drop(&mut self) { }
}

const T: Option<Foo> = None;

fn main() { }

Secara pribadi, saya cenderung berpikir kita harus mendorong pengalaman yang lebih konsisten dan lebih baik bagi pengguna. Sepertinya kami dapat mengoptimalkan sesuai kebutuhan, dan bagaimanapun juga biayanya tidak terlalu buruk. Tetapi saya ingin memahami sedikit lebih baik apa yang sebenarnya terjadi dalam analisis yang lebih mahal ini: apakah gagasan bahwa kita pada dasarnya melakukan "propagasi konstan", sehingga setiap kali sesuatu dijatuhkan, kita menganalisis nilai pasti yang dijatuhkan untuk menentukan apakah itu mungkin berisi nilai yang perlu menjalankan destruktor? (yaitu, jika None , gunakan contoh umum Option<T> )

Hanya untuk memeriksa ini: apakah ini bukan pilihan untuk memiliki analisis berbasis tipe bahkan dalam kode garis lurus? Saya membayangkan itu agak tidak kompatibel, mengingat kami sudah menerima yang berikut (taman bermain):

Ya, itulah alasan mengapa kami tidak bisa sepenuhnya beralih ke analisis berbasis tipe.

apakah gagasan bahwa kita pada dasarnya melakukan "propagasi konstan", sehingga setiap kali sesuatu dijatuhkan, kita menganalisis nilai pasti yang dijatuhkan untuk menentukan apakah itu mungkin berisi nilai yang perlu menjalankan destruktor? (yaitu, jika Tidak Ada, gunakan contoh umum Opsi)

Kami hanya menyebarkan daftar flag ( Drop dan Freeze , saya baru saja menunjukkan Drop sini karena lebih mudah untuk dijelaskan). Ketika kita mencapai terminator Drop tanpa menyetel flag Drop , kita mengabaikan terminator Drop . Ini memungkinkan kode seperti berikut:

{
    let mut x = None;
    // Drop flag for x: false
    let y = Some(Foo);
    // Drop flag for y: true
    x = y; // Dropping x is fine, because Drop flag for x is false
    // Drop flag for y: false, Drop flag for x: true
    x
    // Dropping y is fine, because Drop flag for y is false
}

Ini tidak terjadi pada waktu evaluasi, jadi berikut ini tidak baik:

{
    let mut x = Some(Foo);
    if false {
        x = None;
    }
    x
}

Kami memeriksa bahwa semua kemungkinan jalur eksekusi tidak menyebabkan Drop .

Propagasi konstan adalah analogi yang baik. Ini adalah masalah aliran data lain yang fungsi transfernya tidak dapat diekspresikan dengan gen/kill set, yang tidak menangani penyalinan status antar variabel. Namun, propagasi konstan perlu menyimpan nilai aktual dari setiap variabel, tetapi pemeriksaan konstan hanya perlu menyimpan satu bit yang menunjukkan apakah variabel tersebut memiliki impl Drop kustom atau tidak Freeze membuatnya sedikit lebih murah daripada propagasi konstan.

Untuk lebih jelasnya, contoh pertama @oli-obk dikompilasi pada stable hari ini, dan sejak 1.38.0 , yang tidak menyertakan #64470.

Selanjutnya, const X: Option<Foo> = None; dikompilasi sejak 1.0, yang lainnya hanyalah ekstensi alami dari itu dengan fitur-fitur baru yang diperoleh const eval.

Oke, saya percaya masuk akal untuk mengadopsi opsi berbasis nilai murni.

Saya kira kita bisa menutupinya dalam rapat dan melaporkan kembali =)

Ringkasan

Saya mengusulkan agar kita menstabilkan #![feature(const_if_match)] dengan semantik saat ini.

Secara khusus, ekspresi if dan match serta operator logika hubungan pendek && dan || akan menjadi legal di semua konteks const . Konteks const adalah salah satu dari berikut ini:

  • Inisialisasi const , static , static mut atau enum diskriminan.
  • Tubuh const fn .
  • Nilai generik const (khusus malam).
  • Panjang tipe array ( [u8; 3] ) atau ekspresi pengulangan array ( [0u8; 3] ).

Selanjutnya, operator logika hubungan pendek tidak akan lagi diturunkan ke setara bitwise mereka ( & dan | masing-masing) di const dan static initializers (lihat #57175). Akibatnya, let binding dapat digunakan bersama logika hubungan pendek di inisialisasi tersebut.

Masalah pelacakan: #49146
Target versi: 1,45 (2020-06-16)

Sejarah Implementasi

64470 menerapkan analisis statis berbasis nilai yang mendukung aliran kontrol bersyarat dan berdasarkan aliran data. Ini, bersama dengan #63812, memungkinkan kami untuk mengganti kode pemeriksaan const lama dengan kode yang bekerja pada grafik aliran kontrol yang kompleks. Pemeriksa const lama dijalankan secara paralel dengan yang berbasis aliran data untuk sementara waktu untuk memastikan bahwa mereka menyetujui program dengan aliran kontrol sederhana. #66385 menghapus const-checker lama demi yang berbasis dataflow.

66507 mengimplementasikan gerbang fitur #![feature(const_if_match)] dengan semantik yang sekarang diusulkan untuk stabilisasi.

Kualifikasi Konst

Latar belakang

[Miri] telah mendukung evaluasi fungsi waktu kompilasi (CTFE) di rustc selama beberapa tahun sekarang, dan telah mampu mengevaluasi pernyataan bersyarat setidaknya selama itu. Selama CTFE, kita harus menghindari operasi tertentu, seperti memanggil custom Drop impls atau mengambil referensi ke nilai dengan interior mutability . Secara kolektif, properti yang didiskualifikasi ini dikenal sebagai "kualifikasi", dan proses menentukan apakah suatu nilai memiliki kualifikasi pada titik tertentu dalam program ini dikenal sebagai "kualifikasi const".

Miri sangat mampu memancarkan kesalahan ketika menghadapi operasi ilegal pada nilai yang memenuhi syarat, dan dapat melakukannya tanpa kesalahan positif. Namun, CTFE terjadi pasca-monomorfisasi, yang berarti ia tidak dapat mengetahui apakah konstanta yang didefinisikan dalam konteks generik valid sampai mereka dipakai, yang dapat terjadi di peti lain. Untuk mendapatkan kesalahan pra-monomorfisasi, kita harus menerapkan analisis statis yang melakukan kualifikasi const. Dalam kasus umum, kualifikasi const tidak dapat ditentukan (lihat Teorema Rice ), jadi setiap analisis statis hanya dapat memperkirakan pemeriksaan yang dilakukan Miri selama CTFE.

Analisis statis kami harus melarang referensi ke tipe dengan mutabilitas interior (misalnya &Cell<i32> ) muncul di nilai akhir dari const . Jika ini diizinkan, const dapat dimodifikasi saat run-time.

const X: &std::cell::Cell<i32> = std::cell::Cell::new(0);

fn main() {
  X.get(); // 0
  X.set(42);
  X.get(); // 42
}

Namun, kami memungkinkan pengguna untuk menentukan const yang tipe memiliki berubah-ubah interior ( !Freeze ) selama kita dapat membuktikan bahwa nilai akhir yang const tidak. Sebagai contoh, berikut ini telah dikompilasi sejak edisi pertama stable rust :

const _X: Option<&'static std::cell::Cell<i32>> = None;

Pendekatan analisis statis ini, yang akan saya sebut berbasis nilai sebagai lawan dari berbasis tipe, juga digunakan untuk memeriksa kode yang dapat mengakibatkan panggilan Drop kustom. Memanggil impls Drop bermasalah karena tidak diperiksa const dan dengan demikian dapat berisi kode yang tidak diizinkan dalam konteks const. Penalaran berbasis nilai diperluas untuk mendukung pernyataan let , yang berarti kompilasi berikut

const _: Option<Vec<i32>> = {
  let x = None;
  let mut y = x;
  y = Some(Vec::new()); // Causes the old value in `y` to be dropped.
  y
};

Semantik Malam Saat Ini

Perilaku #![feature(const_if_match)] memperluas semantik berbasis nilai untuk bekerja pada grafik aliran kontrol yang kompleks dengan menggunakan aliran data. Dengan kata lain, kami mencoba membuktikan bahwa suatu variabel tidak memiliki kualifikasi yang dimaksud di sepanjang semua kemungkinan jalur melalui program.

enum Int {
    Zero,
    One,
    Many(String), // Dropping this variant is not allowed in a `const fn`...
}

// ...but the following code is legal under this proposal...
const fn good(x: i32) {
    let i = match x {
        0 => Int::Zero,
        1 => Int::One,
        _ => return,
    };

    // ...because `i` is never `Int::Many` on any possible path through the program.
    std::mem::drop(i);
}

Semua jalur yang mungkin melalui program termasuk yang mungkin tidak pernah dicapai dalam praktik. Contoh, menggunakan enum Int sama seperti di atas:

const fn bad(b: bool) {
    let i = if b == true {
        Int::One
    } else if b == false {
        Int::Zero
    } else {
        // This branch is dead code. It can never be reached in practice.
        // However, const qualification treats it as a possible path because it
        // exists in the source.
        Int::Many(String::new())
    };

    // ILLEGAL: `i` was assigned the `Int::Many` variant on at least one code path.
    std::mem::drop(i);
}

Analisis ini memperlakukan panggilan fungsi sebagai buram, dengan asumsi bahwa nilai baliknya dapat berisi nilai jenis apa pun. Kami juga kembali ke analisis berbasis tipe untuk variabel segera setelah referensi yang dapat diubah untuk itu dibuat. Perhatikan bahwa membuat referensi yang bisa berubah dalam konteks const saat ini dilarang pada karat yang stabil.

#![feature(const_mut_refs)]

const fn none() -> Option<Cell<i32>> {
    None
}

// ILLEGAL: We must assume that `none` may return any value of type `Option<Cell<i32>>`.
const BAD: &Option<Cell<i32>> = none();

const fn also_bad() {
    let x = Option::<Box<i32>>::None;

    let _ = &mut x;

    // ILLEGAL: because a mutable reference to `x` was created, we can no
    // longer assume anything about its value.
    std::mem::drop(x)
}

Anda dapat melihat lebih banyak contoh tentang bagaimana analisis berbasis nilai bersifat konservatif seputar mutabilitas interior dan impls penurunan kustom , serta beberapa kasus di mana analisis konservatif dapat membuktikan bahwa tidak ada hal ilegal yang dapat terjadi dalam rangkaian pengujian.

Alternatif

Saya merasa sulit untuk menemukan alternatif yang praktis dan kompatibel dengan pendekatan yang ada. Kita bisa kembali ke analisis berbasis tipe untuk semua variabel segera setelah conditional digunakan dalam konteks const. Namun, itu juga akan sulit untuk dijelaskan kepada pengguna, karena penambahan yang tampaknya tidak terkait akan menyebabkan kode tidak dapat dikompilasi lagi, seperti assert dalam contoh berikut dari @oli-obk.

const fn foo<T>(b: bool) {
    let x = Option::<T>::None;
    assert!(b);
    {x};
}

Peningkatan ekspresi dari analisis berbasis nilai tidak gratis. Perf run yang melakukan kualifikasi const pada semua badan item, bukan hanya const yang, menunjukkan regresi 5% pada build check . Ini adalah skenario terburuk, karena mengasumsikan bahwa semua item akan dibuat const di beberapa titik di masa mendatang. Pengoptimalan yang memungkinkan, seperti yang ada di #71330, telah dibahas sebelumnya di utas.

Pekerjaan masa depan

Saat ini, pemeriksaan const dijalankan sebelum elaborasi drop, yang berarti beberapa terminator drop tetap berada di MIR yang tidak dapat dijangkau dalam praktiknya. Ini mencegah Option::unwrap menjadi const fn (lihat #66753). Ini tidak terlalu sulit untuk diselesaikan, tetapi akan membutuhkan pemisahan pemeriksaan const menjadi dua fase (elaborasi sebelum dan sesudah penurunan).

Setelah #![feature(const_if_match)] distabilkan, banyak fungsi perpustakaan dapat dibuat const fn . Ini mencakup banyak metode pada tipe integer primitif, yang telah disebutkan di #53718.

Loop dalam konteks const diblokir pada pertanyaan kualifikasi const yang sama dengan conditional. Pendekatan berbasis aliran data saat ini juga berfungsi untuk CFG siklik tanpa modifikasi, jadi jika #![feature(const_if_match)] distabilkan, pemblokir utama untuk #52000 akan hilang.

ucapan terima kasih

Terima kasih khusus kami sampaikan kepada @oli-obk dan @eddyb , yang merupakan peninjau utama untuk sebagian besar pekerjaan implementasi, serta @rust-lang/wg-const-eval lainnya yang telah membantu saya memahami masalah yang relevan seputar const kualifikasi. Semua ini tidak akan mungkin terjadi tanpa Miri, yang dibuat oleh @solson dan sekarang dikelola oleh @RalfJung dan @oli-obk.

Ini dimaksudkan sebagai laporan stabilisasi sebelum FCP. Saya tidak bisa membuka FCP, namun.

@ecstatic-morse Terima kasih banyak atas semua kerja keras Anda dalam masalah ini!

Laporan yang bagus!

Satu hal yang saya pikir ingin saya lihat, @ecstatic-morse, adalah

  • tautan ke beberapa tes representatif dalam repo, sehingga kami dapat mengamati perilakunya
  • apakah ada implikasi di sekitar semver atau apa pun -- saya pikir jawabannya sebagian besar tidak , kan? Dengan kata lain, kami memutuskan analisis yang digunakan untuk menentukan apakah isi dari const fn legal, tetapi mengingat const fn, pilihan kami di sini tidak menentukan hal-hal seperti "apa yang dapat dilakukan oleh penelepon const fn hasilnya", kan? Saya mencoba mencari tahu apa contoh dari apa yang saya bicarakan - saya kira penelepon tidak mengetahui dengan tepat varian enum mana yang digunakan, hanya itu - nilai apa pun dikembalikan - itu tidak memiliki mutabilitas interior (yang mungkin tidak dapat mereka andalkan saat mencocokkan, karena).

Dengan kata lain, kami memutuskan analisis yang digunakan untuk menentukan apakah isi dari const fn legal, tetapi mengingat const fn, pilihan kami di sini tidak menentukan hal-hal seperti "apa yang dapat dilakukan oleh penelepon const fn hasilnya", kan? Saya mencoba mencari tahu apa contoh dari apa yang saya bicarakan - saya kira penelepon tidak mengetahui dengan tepat varian enum mana yang digunakan, hanya itu - nilai apa pun dikembalikan - itu tidak memiliki mutabilitas interior (yang mungkin tidak dapat mereka andalkan saat mencocokkan, karena).

Ya, badan const fn buram. Ini berbeda dengan ekspresi penginisialisasi item const . Anda dapat mengamati ini dengan fakta bahwa

const FOO: Option<Cell<i32>> = None;

dapat digunakan untuk membuat &'static Option<Cell<i32>>

const BAR: &'static Option<Cell<i32>> = &FOO;

sementara const fn dengan tubuh yang sama tidak dapat:

const fn foo() -> Option<Cell<i32>> { None }
const BAR: &'static Option<Cell<i32>> = &foo();

demo taman bermain

Ketika kami memperkenalkan aliran kontrol ke konstanta, ini berarti bahwa

const FOO: Option<Cell<i32>> = if MEH { None } else { None };

juga akan berfungsi, tidak relevan dengan nilai MEH dan

const FOO: Option<Cell<i32>> = if MEH { Some(Cell::new(42)) } else { None };

tidak akan berfungsi, sekali lagi, tidak relevan dengan nilai MEH .

Alur kontrol tidak mengubah apa pun tentang situs panggilan const fn , hanya tentang kode apa yang diizinkan di dalam const fn itu.

tautan ke beberapa tes representatif dalam repo, sehingga kami dapat mengamati perilakunya.

Saya menambahkan paragraf di akhir bagian "Semantik Malam Saat Ini" yang menautkan ke beberapa kasus uji yang menarik. Saya merasa kami membutuhkan lebih banyak tes (pernyataan yang benar terlepas dari keadaannya) sebelum ini distabilkan, tetapi itu dapat diatasi setelah kami memutuskan apakah semantik saat ini diinginkan.

apakah ada implikasi di sekitar semver atau apa pun.

Selain apa yang @oli-obk katakan di atas, saya ingin menunjukkan bahwa mengubah nilai akhir dari const secara teknis sudah merupakan perubahan yang melanggar semver:

// Upstream crate
const IDX: usize = 1; // Changing this to `3` will break downstream code!

// Downstream crate

extern crate upstream;

const X: i32 = [0, 1, 2][upstream::IDX]; // Only compiles if `upstream::IDX <= 2`

Namun, karena kita tidak dapat melakukan kualifikasi const dengan presisi sempurna, mengubah konstanta untuk menggunakan if atau match dapat merusak kode downstream, bahkan jika nilai akhir tidak berubah. Sebagai contoh:

// Changing from `cfg` attributes...

#[cfg(not(FALSE))]
const X: Option<Vec<i32>> = None;
#[cfg(FALSE)]
const X: Option<Vec<i32>> = Some(Vec::new());

// ...to the `cfg` macro...

const X: Option<Vec<i32>> = if !cfg!(FALSE) { None } else { Some(Vec::new() };

// ...could break downstream crates, even though `X` is still `None`!

// Downstream

 // Only legal if static analysis can prove the qualifications in `X`
const _: () =  std::mem::drop(upstream::X); 

Ini tidak berlaku untuk perubahan di dalam isi const fn , karena kami selalu menggunakan kualifikasi berbasis tipe untuk nilai yang dikembalikan, bahkan dalam peti yang sama.

Dalam pandangan saya, "dosa asal" di sini tidak jatuh kembali ke kualifikasi berbasis tipe untuk const dan static yang didefinisikan dalam peti eksternal. Namun, saya percaya bahwa ini telah terjadi sejak 1.0, dan saya menduga bahwa cukup banyak kode yang bergantung padanya. Segera setelah Anda mengizinkan inisialisasi const yang analisis statisnya tidak dapat benar-benar tepat, menjadi mungkin untuk memodifikasi penginisialisasi tersebut sedemikian rupa sehingga mereka akan menghasilkan nilai yang sama tanpa analisis statis dapat membuktikannya.

edit:

Tidak ada yang unik tentang if dan match dalam hal ini. Misalnya, saat ini merupakan perubahan besar untuk memfaktorkan ulang penginisialisasi const menjadi const fn jika peti hilir mengandalkan kualifikasi berbasis nilai.

// Upstream
const fn none<T>() -> Option<T> { None }

const VALUE_BASED: Option<Vec<i32>> = None;
const TYPE_BASED: Option<Vec<i32>> = none();

// Downstream

const OK: () = { std::mem::drop(upstream::VALUE_BASED); };
const ERROR: () = { std::mem::drop(upstream::TYPE_BASED); };

@ecstatic-morse Terima kasih telah menulis laporan stabilisasi! Mari kita mengukur konsensus secara asinkron:

@rfcbot gabung

Jika ada yang ingin membahas hal ini secara serempak dalam rapat, silakan mencalonkan kembali.

Anggota tim @joshtriplett telah mengusulkan untuk menggabungkan ini. Langkah selanjutnya adalah peninjauan oleh anggota tim yang ditandai lainnya:

  • [x] @cramertj
  • [x] @joshtriplett
  • [x] @nikomatsakis
  • [x] @pnkfelix
  • [ ] @scottmcm
  • [ ] @tanpa perahu

Tidak ada kekhawatiran saat ini terdaftar.

Setelah mayoritas peninjau menyetujui (dan paling banyak 2 persetujuan belum diselesaikan), ini akan memasuki periode komentar terakhirnya. Jika Anda menemukan masalah besar yang belum diangkat pada titik mana pun dalam proses ini, silakan angkat bicara!

Lihat dokumen ini untuk info tentang perintah apa yang dapat diberikan oleh anggota tim yang ditandai kepada saya.

:bell: Ini sekarang memasuki periode komentar terakhir , sesuai ulasan di atas . :lonceng:

Apakah ini juga memungkinkan penggunaan ? di const fn ?

Menggunakan ? berarti menggunakan sifat Try . Menggunakan ciri-ciri di const fn tidak stabil, lihat https://github.com/rust-lang/rust/issues/67794.

@TimDiekmann untuk saat ini, Anda harus menulis makro proc yang menurunkan ? secara manual. Hal yang sama berlaku untuk loop dan for , setidaknya hingga batas tertentu (gaya rekursi primitif) tetapi const eval memiliki batas seperti itu. Fitur ini sangat mengagumkan, memungkinkan BANYAK hal yang sebelumnya tidak mungkin. Anda bahkan dapat membuat wasm vm kecil di const fn jika Anda mau.

Periode komentar terakhir, dengan kecenderungan untuk menggabungkan , sesuai ulasan di atas , sekarang selesai .

Sebagai perwakilan otomatis dari proses tata kelola, saya ingin mengucapkan terima kasih kepada penulis atas pekerjaan mereka dan semua orang yang berkontribusi.

RFC akan segera digabung.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat