Rust: Serikat tanpa tag (masalah pelacakan untuk RFC 1444)

Dibuat pada 8 Apr 2016  ·  210Komentar  ·  Sumber: rust-lang/rust

Masalah pelacakan untuk rust-lang / rfcs # 1444.

Pertanyaan yang belum terselesaikan:

  • [x] Apakah menugaskan langsung ke bidang serikat memicu penurunan konten sebelumnya?
  • [x] Ketika pindah dari satu bidang serikat, apakah yang lain dianggap tidak valid? ( 1 , 2 , 3 , 4 )
  • [] Dalam kondisi apa Anda dapat menerapkan Copy untuk serikat pekerja? Misalnya, bagaimana jika beberapa varian berjenis non-Salinan? Semua varian?
  • [] Interaksi apa yang ada antara gabungan dan pengoptimalan tata letak enum? (https://github.com/rust-lang/rust/issues/36394)

Buka masalah impor tinggi:

B-RFC-approved B-unstable C-tracking-issue F-untagged_unions T-lang disposition-merge finished-final-comment-period

Komentar yang paling membantu

@rc
Nah, subsetnya cukup jelas - "FFI unions", atau "C unions", atau "pre-C ++ 11 unions" - meskipun tidak sintaksis. Tujuan awal saya adalah menstabilkan subset ini ASAP (siklus ini, idealnya) sehingga dapat digunakan di perpustakaan seperti winapi .
Tidak ada yang sangat meragukan tentang subset yang tersisa dan implementasinya, hanya saja tidak mendesak dan perlu menunggu waktu yang tidak jelas sampai proses RFC "Serikat 1.2" selesai. Harapan saya adalah menstabilkan bagian yang tersisa dalam siklus 1, 2 atau 3 setelah stabilisasi bagian awal.

Semua 210 komentar

Saya mungkin melewatkannya dalam diskusi tentang RFC, tetapi apakah saya benar dalam berpikir bahwa penghancur varian serikat tidak pernah dijalankan? Akankah destruktor untuk Box::new(1) berjalan dalam contoh ini?

union Foo {
    f: i32,
    g: Box<i32>,
}

let mut f = Foo { g: Box::new(1) };
f.g = Box::new(2);

@sfackler Pemahaman saya saat ini adalah bahwa f.g = Box::new(2) _will_ menjalankan destruktor tetapi f = Foo { g: Box::new(2) } akan _not_. Artinya, menetapkan ke nilai Box<i32> l akan menyebabkan penurunan seperti biasa, tetapi menetapkan ke nilai Foo l tidak akan terjadi.

Jadi, penugasan ke varian seperti pernyataan bahwa bidang sebelumnya "valid"?

@sfackler Untuk Drop jenis, ya, itulah pemahaman saya. Jika sebelumnya tidak valid, Anda perlu menggunakan formulir konstruktor Foo atau ptr::write . Dari grep cepat, sepertinya RFC tidak eksplisit tentang detail ini. Saya melihatnya sebagai contoh dari aturan umum yang menulis ke Drop lvalue menyebabkan panggilan destruktor.

Haruskah & mut union dengan varian Drop menjadi lint?

Pada hari Jumat, 8 April 2016, Scott Olson [email protected] menulis:

@sfackler https://github.com/sfackler Untuk tipe Drop, ya, itu milik saya
pemahaman. Jika sebelumnya tidak valid, Anda perlu menggunakan Foo
bentuk konstruktor atau ptr :: write. Dari grep cepat, sepertinya tidak
RFC secara eksplisit tentang detail ini.

-
Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung atau lihat di GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment -207634431

Pada 8 April 2016 15:36:22 PDT, Scott Olson [email protected] menulis:

@sfackler Untuk Drop jenis, ya, itulah pemahaman saya. Jika mereka
sebelumnya tidak valid, Anda perlu menggunakan formulir konstruktor Foo atau
ptr::write . Dari grep cepat, sepertinya RFC tidak
eksplisit tentang detail ini.

Saya seharusnya membahas kasus itu secara eksplisit. Saya pikir kedua perilaku itu dapat dipertahankan, tetapi saya pikir akan jauh lebih mengejutkan untuk tidak pernah secara implisit menjatuhkan bidang. RFC sudah merekomendasikan lint untuk bidang gabungan dengan jenis yang menerapkan Jatuhkan. Saya tidak berpikir menugaskan ke bidang menyiratkan bahwa bidang tersebut sebelumnya valid.

Ya, pendekatan itu tampaknya sedikit kurang berbahaya bagi saya juga.

Tidak membatalkan saat menugaskan ke bidang gabungan akan membuat f.g = Box::new(2) bertindak berbeda dari let p = &mut f.g; *p = Box::new(2) , karena Anda tidak dapat membuat kasus terakhir _not_ dijatuhkan. Saya pikir pendekatan saya tidak terlalu mengejutkan.

Ini juga bukan masalah baru; unsafe programmer harus menghadapi situasi lain di mana foo = bar adalah UB jika foo tidak diinisialisasi dan Drop .

Saya pribadi tidak berencana untuk menggunakan tipe Drop dengan serikat pekerja sama sekali. Jadi saya akan menyerahkan sepenuhnya kepada orang-orang yang telah bekerja dengan kode analog yang tidak aman pada semantik untuk melakukannya.

Saya juga tidak berniat untuk menggunakan tipe Drop dalam serikat pekerja jadi bagaimanapun juga tidak masalah bagi saya selama itu konsisten.

Saya tidak bermaksud untuk menggunakan referensi yang bisa berubah ke serikat pekerja, dan mungkin
hanya yang "diberi tag aneh" dengan Into

Pada hari Jumat, 8 April 2016, Peter Atashian [email protected] menulis:

Saya juga tidak berniat untuk menggunakan tipe Drop dalam serikat pekerja, jadi bagaimanapun juga tidak
penting bagi saya selama itu konsisten.

-
Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung atau lihat di GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment -207653168

Sepertinya ini adalah masalah yang bagus untuk diangkat sebagai pertanyaan yang belum terselesaikan. Saya belum yakin pendekatan mana yang saya sukai.

@nikomatsakis Meskipun saya merasa canggung untuk menugaskan ke bidang persatuan jenis dengan Drop untuk meminta validitas sebelumnya dari bidang itu, kasus referensi @tsion yang disebutkan tampaknya hampir tidak dapat dihindari. Saya pikir ini mungkin hanya gotcha yang terkait dengan kode yang dengan sengaja menonaktifkan lint untuk menempatkan tipe dengan Drop dalam sebuah serikat pekerja. (Dan penjelasan singkat tentang itu harus ada dalam teks penjelasan untuk lint itu.)

Dan saya ingin menegaskan kembali bahwa unsafe programmer umumnya sudah tahu bahwa a = b berarti drop_in_place(&mut a); ptr::write(&mut a, b) untuk menulis kode yang aman. Tidak menghapus bidang gabungan akan menjadi pengecualian _satu lagi_ untuk dipelajari, tidak berkurang satu.

(NB: penurunan tidak terjadi saat a _statically_ diketahui sudah tidak diinisialisasi, seperti let a; a = b; .)

Tapi saya mendukung memiliki peringatan default terhadap varian Drop dalam serikat bahwa orang harus #[allow(..)] karena ini adalah detail yang cukup tidak jelas.

@tsion ini tidak benar untuk a = b dan mungkin hanya kadang-kadang benar untuk a.x = b tetapi ini benar untuk *a = b . Ketidakpastian inilah yang membuat saya ragu-ragu. Misalnya, ini mengkompilasi:

fn main() {
  let mut x: (i32, i32);
  x.0 = 2;
  x.1 = 3;
}

(meskipun mencoba mencetak x nanti gagal, tapi saya menganggap itu bug)

@nikomatsakis Contoh itu baru bagi saya. Saya kira saya akan menganggapnya sebagai bug yang dikompilasi contoh itu, berdasarkan pengalaman saya sebelumnya.

Tapi saya tidak yakin saya melihat relevansi dari contoh itu. Mengapa yang saya katakan tidak benar untuk a = b dan hanya kadang-kadang untuk a.x = b ?

Katakanlah, jika x.0 memiliki tipe dengan destruktor, pasti destruktor itu disebut:

fn main() {
    let mut x: (Box<i32>, i32);
    x.0 = Box::new(2); // x.0 statically know to be uninit, destructor not called
    x.0 = Box::new(3); // x.0 destructor is called before writing new value
}

Mungkin hanya serat terhadap jenis tulisan itu?

Maksud saya hanya = tidak _always_ menjalankan destruktor; Itu
menggunakan beberapa pengetahuan tentang apakah target diketahui
diinisialisasi.

Pada hari Selasa, 12 Apr 2016 pada 16:10:39 -0700, Scott Olson menulis:

@nikomatsakis Contoh itu baru bagi saya. Saya kira saya akan menganggapnya sebagai bug yang dikompilasi contoh itu, berdasarkan pengalaman saya sebelumnya.

Tapi saya tidak yakin saya melihat relevansi dari contoh itu. Mengapa yang saya katakan tidak benar untuk a = b dan hanya kadang-kadang untuk 'ax = b'?

Katakanlah, jika x.0 memiliki tipe dengan destruktor, pasti destruktor itu disebut:

fn main() {
    let mut x: (Box<i32>, i32);
    x.0 = Box::new(2); // x.0 statically know to be uninit, destructor not called
    x.0 = Box::new(3); // x.0 destructor is called
}

@nikomat

Ini menjalankan destruktor jika bendera drop disetel.

Tapi menurut saya tulisan semacam itu membingungkan, jadi mengapa tidak dilarang saja? Anda selalu dapat melakukan *(&mut u.var) = val .

Maksud saya hanya = tidak _always_ menjalankan destruktor; ia menggunakan beberapa pengetahuan tentang apakah target diketahui diinisialisasi.

@nikomatsakis Saya sudah menyebutkan bahwa:

(NB: penurunan tidak terjadi ketika a secara statis diketahui sudah tidak diinisialisasi, seperti misalkan a; a = b ;.)

Tapi saya tidak memperhitungkan pemeriksaan dinamis dari drop flag, jadi ini jelas lebih rumit daripada yang saya pikirkan.

@ion

Flag drop hanya semi-dinamis - setelah zeroing drop hilang, mereka adalah bagian dari codegen. Saya katakan kami melarang tulisan seperti itu karena itu lebih membingungkan daripada kebaikan.

Haruskah Drop jenis bahkan diperbolehkan dalam serikat pekerja? Jika saya memahami sesuatu dengan benar, alasan utama untuk memiliki serikat di Rust adalah untuk berinteraksi dengan kode C yang memiliki serikat pekerja, dan C bahkan tidak memiliki destruktor. Untuk semua tujuan lainnya, tampaknya lebih baik menggunakan enum dalam kode Rust.

Ada kasus penggunaan yang valid untuk menggunakan serikat pekerja untuk mengimplementasikan tipe NoDrop yang menghambat penurunan.

Serta menjalankan kode tersebut secara manual melalui drop_in_place atau serupa.

Bagi saya menjatuhkan nilai bidang saat menulis ke itu pasti salah karena jenis opsi sebelumnya tidak ditentukan.

Apakah mungkin untuk melarang pembuat lapangan tetapi membutuhkan penggantian serikat pekerja secara penuh? Dalam kasus ini jika serikat mengimplementasikan Jatuhkan serikat penuh drop akan dipanggil untuk nilai diganti seperti yang diharapkan.

Menurut saya tidak masuk akal untuk melarang pembuat lapangan; sebagian besar penggunaan serikat seharusnya tidak memiliki masalah dalam menggunakannya, dan bidang tanpa penerapan Jatuhkan kemungkinan akan tetap menjadi kasus umum. Serikat dengan bidang yang menerapkan Jatuhkan akan menghasilkan peringatan secara default, membuatnya semakin kecil kemungkinan terkena kasus ini secara tidak sengaja.

Demi diskusi, saya bermaksud untuk mengekspos referensi yang bisa berubah ke bidang dalam serikat pekerja _and_ memasukkan jenis arbitrary (mungkin Drop ) ke dalamnya. Pada dasarnya, saya ingin menggunakan serikat pekerja untuk menulis enum hemat ruang khusus. Sebagai contoh,

union SlotInner<V> {
    next_empty: usize, /* index of next empty slot */
    value: V,
}

struct Slot<V> {
    inner: SlotInner<V>,
    version: u64 /* even version -> is_empty */
}

@nikomatsakis Saya ingin mengajukan jawaban konkret untuk pertanyaan yang saat ini terdaftar sebagai belum terselesaikan di sini.

Untuk menghindari semantik rumit yang tidak perlu, menetapkan ke bidang gabungan harus bertindak seperti menetapkan ke bidang struct, yang berarti menghapus konten lama. Cukup mudah untuk menghindari hal ini jika Anda mengetahuinya, dengan menugaskan ke seluruh serikat pekerja. Ini masih merupakan perilaku yang sedikit mengejutkan, tetapi memiliki kolom gabungan yang mengimplementasikan Drop sama sekali akan menghasilkan peringatan, dan teks peringatan tersebut dapat secara eksplisit menyebutkan ini sebagai peringatan.

Apakah masuk akal untuk memberikan permintaan penarikan RFC yang mengubah RFC1444 untuk mendokumentasikan perilaku ini?

@joshtriplett Karena @nikomatsakis sedang pergi berlibur, saya akan menjawab: Saya rasa ini adalah bentuk yang bagus untuk mengajukan amandemen RFC untuk menjawab pertanyaan seperti ini. Kami sering mempercepat PR RFC seperti itu jika perlu.

@aturon Terima kasih. Saya telah mengajukan PR RFC baru https://github.com/rust-lang/rfcs/issues/1663 dengan klarifikasi ini. Ke RFC1444, untuk menyelesaikan masalah ini.

( @aturon, Anda dapat mencentang pertanyaan yang belum terselesaikan itu sekarang.)

Saya memiliki beberapa implementasi awal di https://github.com/petrochenkov/rust/tree/union.

Status: Diimplementasikan (bug modulo), PR dikirimkan (https://github.com/rust-lang/rust/pull/36016).

@petrochenov Mengagumkan! Tampak hebat sejauh ini.

Saya tidak begitu yakin bagaimana memperlakukan serikat dengan bidang non- Copy di pemeriksa pemindahan.
Misalkan u adalah nilai yang diinisialisasi dari union U { a: A, b: B } dan sekarang kita keluar dari salah satu bidang:

1) A: !Copy, B: !Copy, move_out_of(u.a)
Ini sederhana, u.b juga dimasukkan ke dalam status tidak diinisialisasi.
Pemeriksaan kesehatan: union U { a: T, b: T } harus berperilaku persis seperti struct S { a: T } + alias bidang.

2) A: Copy, B: !Copy, move_out_of(u.a)
Seharusnya u.b masih harus diinisialisasi, karena move_out_of(u.a) hanyalah memcpy dan tidak mengubah u.b dengan cara apa pun.

2) A: !Copy, B: Copy, move_out_of(u.a)
Ini adalah kasus yang paling aneh; seharusnya u.b juga harus dimasukkan ke dalam status tidak diinisialisasi meskipun Copy . Copy nilai dapat tidak diinisialisasi (misalnya let a: u8; ), tetapi mengubah statusnya dari diinisialisasi menjadi tidak diinisialisasi adalah sesuatu yang baru, AFAIK.

@ retret
Saya tahu ini sama sekali tidak relevan dengan kebutuhan FFI :)
Kabar baiknya adalah ini bukan pemblokir, saya akan menerapkan perilaku apa pun yang lebih sederhana dan mengirimkan PR akhir pekan ini.

@petrochenkov naluri saya adalah bahwa serikat pekerja adalah "ember kecil", pada dasarnya. Anda bertanggung jawab untuk melacak apakah data diinisialisasi atau tidak dan jenis aslinya. Ini sangat mirip dengan referensi penunjuk mentah.

Inilah sebabnya kami tidak dapat melepaskan data untuk Anda, dan juga mengapa akses apa pun ke kolom tidak aman (meskipun, katakanlah, hanya ada satu varian).

Dengan aturan ini, saya mengharapkan serikat untuk mengimplementasikan Copy jika salinan diterapkan untuk mereka. Tidak seperti struct / enums, bagaimanapun, tidak akan ada pemeriksaan kesehatan internal: Anda selalu dapat mengimplementasikan salinan untuk tipe gabungan jika Anda mau.

Izinkan saya memberikan beberapa contoh untuk memperjelas:

union Foo { ... } // contents don't matter

Gabungan ini affine, karena Copy belum diimplementasikan.

union Bar { x: Rc<String> }
impl Copy for Bar { }
impl Clone for Bar { fn clone(&self) -> Self { *self } }

Jenis gabungan ini Bar disalin, karena Copy telah diterapkan.

Perhatikan bahwa jika Bar adalah sebuah struct, itu akan menjadi kesalahan untuk mengimplementasikan Copy karena jenis bidang x .

Huh, saya rasa saya tidak benar-benar menjawab pertanyaan Anda, sekarang setelah saya membacanya kembali. =)

Oke, jadi, saya sadar saya tidak menjawab pertanyaan Anda sama sekali. Jadi izinkan saya mencoba lagi. Mengikuti prinsip "bit bucket", saya _still_ berharap bahwa kita dapat keluar dari serikat sesuai keinginan. Tetapi tentu saja pilihan lain adalah memperlakukannya seperti kami memperlakukan *mut T , dan mengharuskan Anda menggunakan ptr::read untuk pindah.

EDIT: Saya sebenarnya tidak sepenuhnya yakin mengapa kami melarang gerakan seperti itu. Mungkin harus dilakukan dengan gerakan menjatuhkan - atau mungkin hanya karena mudah membuat kesalahan dan tampaknya lebih baik untuk membuat "pemindahan" lebih eksplisit? Saya kesulitan mengingat sejarah di sini.

@nikomat

naluri saya adalah bahwa serikat pekerja pada dasarnya adalah "ember kecil".

Ha, saya, sebaliknya, ingin memberikan jaminan sebanyak yang kami bisa tentang konten serikat untuk konstruksi berbahaya seperti itu.

Interpretasinya adalah bahwa serikat adalah enum yang tidak kita ketahui diskriminannya, yaitu kita dapat menjamin bahwa setiap saat setidaknya salah satu varian serikat memiliki nilai yang valid (kecuali kode tidak aman terlibat).

Semua aturan pinjam / pindah dalam implementasi saat ini mendukung jaminan ini, secara bersamaan ini adalah interpretasi yang paling konservatif, yang memungkinkan kita untuk pergi ke cara "aman" (misalnya, mengizinkan akses yang aman ke serikat pekerja dengan bidang yang sama jenisnya, ini bisa jadi berguna ) atau cara "bit bucket" di masa mendatang, saat lebih banyak pengalaman dengan serikat Rust dikumpulkan.

Sebenarnya, saya ingin membuatnya lebih konservatif seperti yang dijelaskan di https://github.com/rust-lang/rust/pull/36016#issuecomment -242810887

@tokopedia

Interpretasinya adalah bahwa serikat adalah enum yang tidak kita ketahui diskriminannya, yaitu kita dapat menjamin bahwa setiap saat setidaknya salah satu varian serikat memiliki nilai yang valid (kecuali kode tidak aman terlibat).

Perhatikan bahwa kode yang tidak aman selalu terlibat, saat bekerja dengan serikat pekerja, karena setiap akses ke bidang tidak aman.

Cara saya memikirkannya, menurut saya, serupa. Pada dasarnya penyatuan itu seperti enum tetapi bisa ada lebih dari satu varian secara bersamaan. Himpunan varian yang valid tidak diketahui kompilator kapan pun, meskipun terkadang kita dapat mengetahui bahwa himpunan tersebut kosong (mis., Enum tidak diinisialisasi).

Jadi saya melihat penggunaan some_union.field pada dasarnya sebagai pernyataan implisit (dan tidak aman) bahwa rangkaian varian yang valid saat ini mencakup field . Ini tampaknya sesuai dengan cara kerja integrasi pemeriksa peminjam; jika Anda meminjam bidang x dan kemudian mencoba menggunakan y , Anda mendapatkan pesan kesalahan karena pada dasarnya Anda mengatakan bahwa data secara bersamaan adalah x dan y (dan itu dipinjam). (Sebaliknya, dengan enum biasa, tidak mungkin untuk menghuni lebih dari satu varian pada satu waktu, dan Anda dapat melihat hal ini dalam cara bermain aturan pinjam ).

Bagaimanapun, intinya adalah, ketika kita "pindah" dari satu bidang penyatuan, pertanyaan yang ada di tangan saya adalah apakah kita dapat menyimpulkan bahwa ini menyiratkan bahwa menafsirkan nilai sebagai varian lain tidak lagi valid. Saya pikir tidak akan terlalu sulit untuk berdebat. Saya menganggap ini sebagai zona abu-abu.

Bahaya menjadi konservatif adalah bahwa kita mungkin mengesampingkan kode tidak aman yang jika tidak masuk akal dan valid. Tapi saya baik-baik saja dengan memulai lebih ketat dan memutuskan apakah akan melonggarkan nanti.

Kita harus membahas masalah kondisi apa yang diperlukan untuk mengimplementasikan Copy pada sebuah serikat - juga, kita harus memastikan bahwa kita memiliki daftar lengkap dari area abu-abu yang tercantum di atas untuk memastikan kita menangani dan mendokumentasikan sebelum stabilisasi!

Pada dasarnya penyatuan itu seperti enum tetapi bisa ada lebih dari satu varian secara bersamaan.

Satu argumen yang menentang interpretasi "lebih dari satu varian" adalah bagaimana serikat berperilaku dalam ekspresi konstan - untuk serikat ini kami selalu mengetahui varian aktif tunggal dan juga tidak dapat mengakses varian tidak aktif karena mentransmutasi pada waktu kompilasi umumnya buruk (kecuali kita mencoba untuk mengubah kompilator menjadi semacam emulator target parsial).
Interpretasi saya adalah bahwa pada saat runtime varian tidak aktif masih tidak aktif tetapi dapat diakses jika layout tersebut kompatibel dengan varian aktif serikat (definisi yang lebih ketat) atau lebih tepatnya dengan riwayat penetapan fragmen union (lebih kabur, tetapi lebih berguna).

kita harus memastikan bahwa kita memiliki daftar lengkap dari area abu-abu ini

Saya akan mengubah serikat RFC di masa depan yang tidak terlalu jauh! Interpretasi "enum" memiliki konsekuensi yang cukup menyenangkan.

transmutasi pada waktu kompilasi umumnya buruk (kecuali kita mencoba mengubah kompilator menjadi semacam emulator target parsial)

@petrochenkov Ini adalah salah satu tujuan proyek Miri saya. Miri sudah dapat melakukan transmutasi dan berbagai kejahatan penunjuk mentah. Ini akan menjadi sedikit pekerjaan untuk membuat Miri menangani serikat pekerja (tidak ada yang baru di sisi penanganan memori mentah).

Dan @eddyb mendorong untuk mengganti evaluasi konstan

@tokopedia

Satu argumen yang menentang interpretasi "lebih dari satu varian" adalah bagaimana serikat pekerja berperilaku dalam ekspresi konstan ...

Cara terbaik untuk mendukung penggunaan serikat dalam konstanta adalah pertanyaan yang menarik, tetapi saya tidak melihat masalah dengan membatasi ekspresi konstan ke subset perilaku waktu proses (bagaimanapun juga, inilah yang selalu kami lakukan). Artinya, hanya karena kami mungkin tidak dapat sepenuhnya mendukung beberapa transmute tertentu pada waktu kompilasi tidak berarti itu ilegal pada waktu proses.

Interpretasi saya adalah bahwa pada saat runtime varian tidak aktif masih tidak aktif tetapi dapat diakses jika layout kompatibel dengan varian aktif union

Hmm, saya mencoba memikirkan perbedaannya dengan mengatakan bahwa penyatuan milik semua varian itu secara bersamaan. Saya belum benar-benar melihat perbedaannya. :)

Saya merasa interpretasi ini memiliki interaksi yang aneh dengan gerakan secara umum. Misalnya, jika datanya "benar-benar" adalah X, dan Anda menafsirkannya sebagai Y, tetapi Y adalah afin, apakah itu tetap X?

Terlepas dari itu, saya pikir tidak apa-apa jika perpindahan bidang apa pun menghabiskan seluruh serikat dapat dilihat sebagai konsisten dengan interpretasi ini. Misalnya, dalam pendekatan "set of variants", idenya adalah bahwa memindahkan nilai akan mendinisialisasi semua varian yang ada (dan tentu saja varian yang Anda gunakan haruslah salah satu set yang valid). Dalam versi Anda, ini akan tampak "berubah" menjadi varian itu (dan menggunakan aslinya).

Saya akan mengubah serikat RFC di masa depan yang tidak terlalu jauh! Interpretasi "enum" memiliki konsekuensi yang cukup menyenangkan.

Keyakinan seperti itu! Anda akan mencoba;)

Mau menjelaskan beberapa detail lebih lanjut tentang perubahan konkret apa yang ada dalam pikiran Anda?

Mau menjelaskan beberapa detail lebih lanjut tentang perubahan konkret apa yang ada dalam pikiran Anda?

Penjelasan lebih rinci tentang implementasi (yaitu dokumentasi yang lebih baik), beberapa ekstensi kecil (seperti serikat kosong dan .. dalam pola serikat), dua alternatif utama (bertentangan) dari evolusi serikat - "ruang awal" yang lebih tidak aman dan tidak terlalu membatasi interpretasi dan interpretasi "enum dengan diskriminan yang tidak diketahui" yang lebih aman dan lebih ketat - dan konsekuensinya untuk pemeriksa pemindahan / inisialisasi, Copy impls, unsafe ty akses lapangan, dll.

Ini juga akan berguna untuk menentukan saat mengakses bidang serikat yang tidak aktif adalah UB, misalnya

union U { a: u8, b: () }
let u = U { b: () };
let a = u.a; // most probably an UB, equivalent to reading from `mem::uninitialized()`

tapi ini adalah area yang sangat rumit.

Kedengarannya mungkin, semantik lintas bidang pada dasarnya adalah penunjuk yang dilemparkan, bukan?
_ (_ () sebagai * u8)

Pada hari Kamis, 1 September 2016, Vadim Petrochenkov [email protected]
menulis:

Ini juga akan berguna untuk menentukan saat mengakses bidang serikat yang tidak aktif
adalah UB, mis

serikat U {a: u8, b: ()}
misalkan u = U {b: ()};
biarkan a = ua; // kemungkinan besar seorang UB, setara dengan membaca dari mem::uninitialized()

tapi ini adalah area yang sangat rumit.

-
Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment -244154751,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/ABxXhi68qRITTFW5iJn6omZQQBQgzweNks5qlw4qgaJpZM4IDXsj
.

Bukankah akses lapangan selalu tidak aman?

Pada hari Kamis, 1 September 2016, Vadim Petrochenkov [email protected]
menulis:

Mau menjelaskan beberapa detail lebih lanjut tentang perubahan konkret apa yang ada dalam pikiran Anda?

Deskripsi implementasi yang lebih rinci (yaitu lebih baik
dokumentasi), beberapa ekstensi kecil (seperti serikat kosong dan .. dalam serikat pekerja
pola), dua alternatif utama (bertentangan) evolusi serikat - lebih
interpretasi "ruang gores" yang tidak aman dan tidak terlalu ketat dan lebih aman
dan interpretasi "enum dengan diskriminan yang tidak diketahui" yang lebih ketat - dan
konsekuensinya untuk pemeriksa pemindahan / inisialisasi, Salin impls, tidak aman
akses lapangan, dll.

-
Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment -244151164,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/ABxXhuHStN8AFhR3KYDU27U29MiMpN5Bks5qlws9gaJpZM4IDXsj
.

Bukankah akses lapangan selalu tidak aman?

Kadang-kadang dapat dibuat aman, misalnya

  • penugasan ke bidang serikat yang mudah dirusak aman.
  • akses apapun ke field dari union U { f1: T, f2: T, ..., fN: T } (yaitu semua field memiliki tipe yang sama) aman dalam interpretasi "enum dengan diskriminan yang tidak diketahui".

Tampaknya lebih baik untuk tidak menerapkan ketentuan khusus untuk ini, dari sudut pandang pengguna. Sebut saja tidak aman, selalu.

Saat ini menguji dukungan untuk serikat pekerja di Rustc terbaru dari git. Semua yang saya coba bekerja dengan sempurna.

Saya menemukan kasus menarik di pemeriksa lapangan mati. Coba kode berikut:

#![feature(untagged_unions)]

union U {
    i: i32,
    f: f32,
}

fn main() {
    println!("{}", std::mem::size_of::<U>());
    let u = U { f: 1.0 };
    println!("{:#x}", unsafe { u.i });
}

Anda akan mendapatkan kesalahan ini:

warning: struct field is never used: `f`, #[warn(dead_code)] on by default

Sepertinya pemeriksa dead_code tidak memperhatikan inisialisasi.

(Saya sudah mengajukan PR # 36252 tentang penggunaan "bidang struct", mengubahnya menjadi "bidang" saja.)

Serikat saat ini tidak dapat berisi bidang berukuran dinamis, tetapi RFC tidak menentukan perilaku ini dengan cara apa pun:

#![feature(untagged_unions)]

union Foo<T: ?Sized> {
  value: T,
}

Keluaran:

error[E0277]: the trait bound `T: std::marker::Sized` is not satisfied
 --> <anon>:4:5
  |
4 |     value: T,
  |     ^^^^^^^^ trait `T: std::marker::Sized` not satisfied
  |
  = help: consider adding a `where T: std::marker::Sized` bound
  = note: only the last field of a struct or enum variant may have a dynamically sized type

Kata kunci kontekstual tidak berfungsi di luar konteks root module / crate:

fn main() {
    // all work
    struct Peach {}
    enum Pineapple {}
    trait Mango {}
    impl Mango for () {}
    type Strawberry = ();
    fn woah() {}
    mod even_modules {
        union WithUnions {}
    }
    use std;

    // does not work
    union Banana {}
}

Sepertinya kutil konsistensi yang cukup buruk.

@agisa
Apakah Anda tidak sengaja menggunakan versi Rustc yang lebih lama?
Saya baru saja memeriksa contoh Anda di playpen dan berhasil (kesalahan modulo "serikat kosong").
Ada juga uji run-pass yang memeriksa situasi khusus ini - https://github.com/rust-lang/rust/blob/master/src/test/run-pass/union/union-backcomp.rs.

@petrochenkov ah, saya menggunakan play.rlo, tapi sepertinya itu mungkin kembali ke stable atau semacamnya. Lupakan aku, kalau begitu.

Saya pikir serikat pekerja pada akhirnya perlu mendukung _safe fields_, si kembar jahat dari bidang tidak aman dari proposal ini .
cc https://github.com/rust-lang/rfcs/issues/381#issuecomment -246703410

Saya pikir akan masuk akal untuk memiliki cara untuk menyatakan "serikat yang aman", berdasarkan berbagai kriteria.

Misalnya, gabungan yang secara eksklusif berisi bidang Salin non-Letakkan, semua dengan ukuran yang sama, tampaknya aman; tidak peduli bagaimana Anda mengakses bidang tersebut, Anda mungkin mendapatkan data yang tidak diharapkan, tetapi Anda tidak dapat mengalami masalah keamanan memori atau perilaku yang tidak ditentukan.

@joshtriplett Anda juga perlu memastikan tidak ada "lubang" dalam jenis yang dapat Anda gunakan untuk membaca data yang tidak diinisialisasi. Seperti yang ingin saya katakan: "Data yang tidak diinisialisasi adalah data acak yang tidak dapat diprediksi atau kunci pribadi SSH Anda, mana saja yang lebih buruk."

Bukankah &T Copy dan non-Drop? Masukkan itu ke dalam serikat "aman" dengan usize , dan Anda memiliki generator referensi yang tidak sesuai. Jadi aturannya harus sedikit lebih ketat dari itu.

Misalnya, gabungan yang secara eksklusif berisi bidang Salin non-Letakkan, semua dengan ukuran yang sama, tampaknya aman; tidak peduli bagaimana Anda mengakses bidang tersebut, Anda mungkin mendapatkan data yang tidak diharapkan, tetapi Anda tidak dapat mengalami masalah keamanan memori atau perilaku yang tidak ditentukan.

Saya menyadari ini hanyalah contoh yang tidak masuk akal, bukan proposal yang serius, tetapi berikut adalah beberapa contoh untuk menggambarkan betapa rumitnya hal ini:

  • u8 dan bool memiliki ukuran yang sama, tetapi sebagian besar nilai u8 tidak valid untuk bool dan mengabaikan ini akan memicu UB
  • &T dan &U memiliki ukuran yang sama dan Copy + !Drop untuk semua T dan U (selama keduanya atau tidak keduanya adalah Sized )
  • transmutasi antara uN / iN dan fN saat ini hanya dapat dilakukan dalam kode yang tidak aman. Saya percaya transmutasi seperti itu selalu aman tetapi ini memperbesar bahasa aman sehingga mungkin kontroverial.
  • Melanggar privasi (mis. Hukuman antara struct Foo(Bar); dan Bar ) juga tidak boleh, karena privasi mungkin digunakan untuk menegakkan invarian yang relevan dengan keselamatan.

@Amanieu Ketika saya menulis itu, saya bermaksud memasukkan catatan tentang tidak memiliki padding internal, dan entah bagaimana lupa melakukannya. Terima kasih sudah menangkapnya.

@ Cuviper Saya mencoba untuk mendefinisikan "data lama biasa", seperti dalam sesuatu yang tidak mengandung petunjuk. Anda benar, definisi tersebut perlu mengecualikan referensi. Mungkin lebih mudah untuk memasukkan kumpulan jenis dan kombinasi yang diizinkan dari jenis tersebut ke daftar putih.

@tokopedia

u8 dan bool memiliki ukuran yang sama, tetapi sebagian besar nilai u8 tidak valid untuk bool dan mengabaikan pemicu ini untuk UB

Poin yang bagus; masalah yang sama berlaku untuk enum.

& T dan & U memiliki ukuran yang sama dan merupakan Salin +! Jatuhkan untuk semua T dan U (asalkan keduanya berukuran sama atau tidak)

Saya sudah lupa itu.

transmisi antara uN / iN dan fN saat ini hanya dapat dilakukan dalam kode yang tidak aman. Saya percaya transmutasi seperti itu selalu aman tetapi ini memperbesar bahasa aman sehingga mungkin kontroverial.

Setuju pada kedua poin; ini tampaknya dapat diterima untuk diizinkan.

Melanggar privasi (misalnya hukuman antara struct Foo (Bar); dan Bar) adalah tidak boleh juga, karena privasi dapat digunakan untuk menegakkan invarian yang relevan dengan keselamatan.

Jika Anda tidak mengetahui internal jenisnya, Anda tidak dapat mengetahui apakah internal memenuhi persyaratan (seperti tidak ada padding internal). Jadi Anda bisa mengecualikan ini dengan mengharuskan semua komponen menjadi data biasa-lama, secara rekursif, dan Anda memiliki visibilitas yang memadai untuk memverifikasi itu.

transmisi antara uN / iN dan fN saat ini hanya dapat dilakukan dalam kode yang tidak aman. Saya percaya transmutasi seperti itu selalu aman tetapi ini memperbesar bahasa aman sehingga mungkin kontroverial.

Bilangan floating point memiliki pensinyalan NaN yang merupakan representasi trap yang menghasilkan UB.

@ retep998 Apakah Rust berjalan pada semua platform yang tidak mendukung penonaktifan jebakan floating-point? (Itu tidak mengubah masalah UB, tapi secara teori kita bisa membuat masalah itu hilang.)

@tokopedia

Interpretasinya adalah bahwa serikat adalah enum yang tidak kita ketahui diskriminannya, yaitu kita dapat menjamin bahwa setiap saat setidaknya salah satu varian serikat memiliki nilai yang valid

Saya pikir saya telah sampai pada interpretasi ini - yah, bukan _exactly_ ini. Saya masih memikirkannya karena ada beberapa varian legal yang ditentukan di tempat Anda menyimpan, seperti yang selalu saya lakukan. Saya berpikir menyimpan nilai ke dalam suatu kesatuan sama seperti memasukkannya ke dalam "keadaan kuantum" - sekarang berpotensi diubah menjadi salah satu dari banyak interpretasi hukum. Tetapi saya setuju bahwa ketika Anda keluar dari salah satu varian ini, Anda telah "memaksanya" menjadi salah satu varian saja, dan menghabiskan nilainya. Oleh karena itu, Anda seharusnya tidak dapat menggunakan enum lagi (jika jenis itu bukan Copy ). Jadi 👍, pada dasarnya.

Pertanyaan tentang #[repr(C)] : seperti yang baru-baru ini ditunjukkan @pnkfelix kepada saya, spesifikasi saat ini menyatakan bahwa jika sebuah serikat pekerja bukan #[repr(C)] , adalah ilegal untuk menyimpan dengan bidang x dan membaca dengan bidang y . Mungkin ini karena kita tidak diharuskan memulai semua bidang pada offset yang sama.

Saya dapat melihat beberapa utilitas dalam hal ini: misalnya, pembersih mungkin mengimplementasikan serikat dengan menyimpannya seperti enum normal (atau bahkan struct ...?) Dan memeriksa apakah Anda menggunakan varian yang sama dengan yang Anda masukkan.

_But_ sepertinya semacam senjata, dan juga salah satu repr menjamin kami tidak akan pernah bisa _ secara nyata_ berubah dalam praktiknya, karena terlalu banyak orang yang akan mengandalkannya di alam liar.

Pikiran?

@nikomat

Interpretasinya adalah bahwa serikat adalah enum yang tidak kita ketahui diskriminannya, yaitu kita dapat menjamin bahwa setiap saat setidaknya salah satu varian serikat memiliki nilai yang valid

Bagian terburuknya adalah fragmen varian / kolom, yang dapat langsung diakses oleh serikat pekerja.
Pertimbangkan kode ini:

union U {
    a: (u8, bool),
    b: (bool, u8),
}
fn main() {
    unsafe {
        let mut u = U { a: (2, false) };
        u.b.1 = 2; // turns union's memory into (2, 2)
    }
}

Semua bidang adalah Copy , tidak ada kepemilikan yang terlibat dan pemeriksa pemindahan senang, tetapi tugas parsial ke bidang tidak aktif b mengubah serikat menjadi keadaan dengan varian 0 valid. Saya belum berpikir bagaimana menghadapinya. Buat tugas seperti itu UB? Ubah interpretasi? Sesuatu yang lain?

@tokopedia

Buat tugas seperti itu UB?

Ini asumsi saya, ya. Ketika Anda menetapkan dengan a , varian b tidak ada dalam kumpulan varian yang valid, dan karenanya menggunakan u.b.1 (apakah akan membaca atau menetapkan) tidak valid.

Pertanyaan tentang # [repr (C)]: seperti yang baru-baru ini ditunjukkan @pnkfelix kepada saya, spesifikasi saat ini menyatakan bahwa jika sebuah union bukan # [repr (C)], adalah ilegal untuk menyimpan dengan field x dan membaca dengan field y . Mungkin ini karena kita tidak diharuskan memulai semua bidang pada offset yang sama.

Saya pikir kata-kata yang tepat di sini adalah bahwa 1) Membaca dari bidang yang tidak "kompatibel dengan tata letak" (ini tidak jelas) dengan bidang yang ditulis sebelumnya / fragmen bidang adalah UB 2) Untuk #[repr(C)] serikat pengguna tahu tata letak apa itu (dari dokumen ABI) sehingga mereka dapat membedakan antara UB dan non-UB 3) Untuk #[repr(Rust)] tata letak serikat tidak ditentukan sehingga pengguna tidak dapat mengatakan apa itu UB dan apa yang bukan, tetapi KAMI (rustc / libstd + mereka tes) memiliki pengetahuan suci ini, sehingga kita dapat memisahkan gandum dari sekam dan menggunakan #[repr(Rust)] dengan cara non-UB.

4) Setelah size / stride dan field reordering pertanyaan diputuskan, saya berharap struct dan union layout akan diatur dan ditentukan, sehingga pengguna akan mengetahui layoutnya juga dan akan dapat menggunakan #[repr(Rust)] unions sebebas #[repr(C)] dan masalah akan hilang.

@nikomatsakis Dalam diskusi tentang RFC serikat, orang-orang menyebutkan ingin memiliki kode Rust asli yang menggunakan serikat untuk membangun struktur data yang kompak.

Apakah ada yang berhenti dari orang yang menggunakan #[repr(C)] ? Jika tidak, maka saya tidak merasa perlu memberikan jaminan apa pun untuk #[repr(Rust)] , biarkan saja sebagai "di sini jadilah naga". Mungkin akan lebih baik jika memiliki lint yang diperingatkan secara default untuk serikat yang bukan #[repr(C)] .

@ retep998 Tampaknya masuk akal bagi saya bahwa repr(Rust) tidak menjamin tata letak tertentu atau tumpang tindih. Saya hanya akan menyarankan bahwa repr(Rust) seharusnya tidak melanggar asumsi orang tentang penggunaan memori dari sebuah serikat pekerja ("tidak lebih besar dari anggota terbesar").

Apakah Rust bekerja pada platform apa pun yang tidak mendukung penonaktifan jebakan floating-point?

Itu bukanlah pertanyaan yang valid untuk ditanyakan. Pertama-tama, pengoptimal sendiri dapat mengandalkan ke-UB-an representasi perangkap dan menulis ulang program dengan cara yang tidak terduga. Selain itu, Rust juga tidak terlalu mendukung pengubahan FP environment.

Tapi sepertinya semacam senjata api, dan juga salah satu pernyataan itu menjamin kami tidak akan pernah bisa benar-benar berubah dalam praktiknya, karena terlalu banyak orang yang akan mengandalkannya di alam liar.

Pikiran?

Menambahkan lint atau semacamnya yang memeriksa alur program dan memberikan keluhan kepada pengguna jika pembacaan dilakukan dari bidang ketika enum terbukti ditulis di bidang lain akan membantu hal ini¹. Serat berbasis MIR akan mempersingkatnya. Jika CFG tidak memungkinkan membuat kesimpulan apa pun tentang legalitas pemuatan bidang serikat dan pengguna membuat kesalahan, perilaku tidak terdefinisi adalah yang terbaik yang dapat kami tentukan tanpa menentukan IMO Rust repr itu sendiri.

¹: Sangat efektif jika orang mulai menggunakan serikat sebagai transmutasi orang miskin karena alasan tertentu.

seharusnya tidak melanggar asumsi orang tentang penggunaan memori serikat ("tidak lebih besar dari anggota terbesar").

Saya tidak setuju. Mungkin sangat masuk akal untuk memperluas repr(Rust) barang ke ukuran kata mesin pada beberapa arsitektur, misalnya.

Satu masalah yang mungkin ingin dipertimbangkan sebelum stabilisasi adalah https://github.com/rust-lang/rust/issues/37479. Sepertinya dengan versi terbaru dari serikat debugging LLDB mungkin tidak berfungsi :(

@alexcrichton Apakah berfungsi dengan GDB?

Dari apa yang saya tahu, ya. Bot Linux tampaknya menjalankan pengujian dengan baik.

Artinya Rust menyediakan semua informasi debugging yang benar, dan LLDB memiliki bug di sini. Saya tidak berpikir bug di salah satu dari beberapa debugger, tidak ada di yang lain, harus memblokir stabilisasi ini. LLDB hanya perlu diperbaiki.

Akan sangat keren untuk melihat apakah kami bisa memasukkan fitur ini ke FCP untuk siklus 1,17 (itu beta 16 Maret). Adakah yang bisa memberikan ringkasan pertanyaan yang belum terjawab dan situasi terkini dari fitur tersebut sehingga kami dapat melihat apakah kami dapat mencapai konsensus dan menyelesaikan semuanya?

@tokopedia
Rencanaku adalah

  • Tunggu rilis mendatang (3 Feb).
  • Usulkan stabilisasi serikat pekerja dengan bidang Copy . Ini akan mencakup semua kebutuhan FFI - pustaka FFI akan dapat menggunakan unions pada stable. Serikat "POD" digunakan selama beberapa dekade dalam C / C ++ dan dipahami dengan baik (aliasing berbasis tipe modulo, tetapi Rust tidak memilikinya), juga tidak ada pemblokir yang dikenal.
  • Tulis "Serikat 1.2" RFC hingga 3 Februari. Ini akan menjelaskan implementasi serikat saat ini dan menguraikan arah masa depan. Masa depan serikat pekerja dengan bidang non- Copy akan diputuskan dalam proses membahas RFC ini.

Perhatikan, bahwa mengekspos sesuatu seperti ManuallyDrop atau NoDrop dari pustaka standar tidak memerlukan serikat pekerja yang stabil.

PEMBARUAN STATUS (4 Feb): Saya sedang menulis RFC, tetapi saya mengalami blok penulis setelah setiap kalimat, seperti biasa, jadi ada kemungkinan saya akan menyelesaikannya pada akhir pekan depan (11-12 Feb) dan bukan akhir pekan ini (4-5 Februari).
PEMBARUAN STATUS (11 Feb): Teks sudah 95% siap, saya akan mengirimkannya besok.

@petrochenkov yang sepertinya merupakan tindakan yang sangat wajar.

@petrochenkov Kedengarannya masuk akal bagi saya. Saya juga meninjau proposal serikat 1.2 Anda, dan memberikan beberapa komentar; secara keseluruhan, ini terlihat bagus untuk saya.

@joshtriplett Saya berpikir bahwa, saat rapat @ rust-lang / lang kami membicarakan tentang menjaga agar daftar periksa selalu diperbarui, saya sebenarnya ingin melihat bahwa - untuk masing-masing poin ini - kami membuat keputusan yang tegas (yaitu, idealnya dengan @rfcbot). Ini mungkin akan menyarankan masalah yang berbeda (atau bahkan amandemen RFC). Kita bisa melakukan ini seiring waktu, tetapi sampai saat itu saya belum merasa kita telah "menyelesaikan" jawaban atas pertanyaan terbuka secara definitif. Sejalan dengan itu, mengekstrak dan meringkas percakapan yang relevan menjadi amandemen RFC atau bahkan hanya masalah yang dapat kami tautkan dari sini tampaknya merupakan langkah yang sangat baik untuk membantu memastikan bahwa setiap orang berada pada halaman yang sama - dan sesuatu yang dapat dilakukan oleh siapa pun yang tertarik. , tentu saja, bukan hanya anggota atau gembala @ rust-lang / lang.

Jadi, saya telah mengirimkan RFC "Serikat 1.2" - https://github.com/rust-lang/rfcs/pull/1897.

Sekarang saya ingin mengusulkan stabilisasi subset konservatif serikat pekerja - semua bidang serikat harus Copy , jumlah bidang harus bukan nol dan serikat tidak boleh mengimplementasikan Drop .
(Saya tidak yakin persyaratan terakhir dapat dijalankan, karena ini dapat dengan mudah dielakkan dengan membungkus serikat ke dalam sebuah struct dan menerapkan Drop untuk struct itu.)
Serikat pekerja tersebut mencakup semua kebutuhan perpustakaan FFI, yang seharusnya menjadi konsumen utama fitur bahasa ini.

Teks "Serikat 1.2" RFC tidak benar-benar memberi tahu sesuatu yang baru tentang serikat gaya FFI, kecuali bahwa teks tersebut secara eksplisit menegaskan bahwa jenis punning diizinkan.
EDIT : "Serikat 1.2" RFC juga akan membuat tugas ke bidang Copy dirusak sepele (lihat https://github.com/rust-lang/rust/issues/32836#issuecomment-281296416, https : //github.com/rust-lang/rust/issues/32836#issuecomment-281748451), ini juga memengaruhi serikat bergaya FFI.

Teks ini juga memberikan dokumentasi yang diperlukan untuk stabilisasi.
Bagian "Ringkasan" dapat disalin-tempel ke dalam buku dan "Desain terperinci" ke dalam referensi.

ping @nikomatsakis

Apakah sesuatu seperti ini benar-benar perlu ditambahkan sebagai bagian dari bahasa? Butuh waktu sekitar 20 menit untuk menyelesaikan implementasi serikat pekerja menggunakan sedikit unsafe dan ptr::write() .

use std::mem;
use std::ptr;


/// A union of `f64`, `bool`, and `i32`.
#[derive(Default, Clone, PartialEq, Debug)]
struct Union {
    data: [u8; 8],
}

impl Union {
    pub unsafe fn get<T>(&self) -> &T {
        &*(&self.data as *const _ as *const T)
    }

    pub unsafe fn set<T>(&mut self, value: T) {
        // "transmute" our pointer to self.data into a &mut T so we can 
        // use ptr::write()
        let data_ptr: &mut T = &mut *(&mut self.data as *mut _ as *mut T);
        ptr::write(data_ptr, value);
    }
}


fn main() {
    let mut u = Union::default();
    println!("data: {0:?} ({0:#p})", &u.data);
    {
        let as_i32: &i32 = unsafe { u.get() };
        println!("as i32: {0:?} ({0:#p})", as_i32);
    }

    unsafe {
        u.set::<f64>(3.14);
    }

    println!("As an f64: {:?}", unsafe { u.get::<f64>() });
}

Saya merasa tidak akan sulit bagi seseorang untuk menulis makro yang dapat menghasilkan sesuatu seperti itu kecuali memastikan bahwa array internal adalah ukuran tipe terbesar. Kemudian alih-alih saya yang benar-benar generik (dan sangat tidak aman) get::<T>() mereka dapat menambahkan sifat yang terikat untuk membatasi jenis yang bisa Anda dapatkan dan atur. Anda bahkan dapat menambahkan metode pengambil dan penyetel tertentu jika Anda ingin bidang bernama.

Saya pikir mereka mungkin menulis sesuatu seperti ini:

union! { Foo(u64, Vec<u8>, String) };

Maksud saya adalah, ini adalah sesuatu yang dapat Anda lakukan dengan sangat layak sebagai bagian dari perpustakaan alih-alih menambahkan sintaks dan kompleksitas ekstra ke bahasa yang sudah cukup kompleks. Ditambah dengan macro proc itu sudah sangat mungkin, bahkan jika itu belum sepenuhnya stabil.

@ Michael-F-Bryan Kami belum memiliki size_of konstan.

@ Michael-F-Bryan Tidak hanya cukup memiliki array [u8] , Anda juga perlu mendapatkan kesejajaran yang benar. Saya sebenarnya sudah menggunakan makro untuk menangani serikat pekerja tetapi karena kurangnya konstanta size_of dan align_of Saya harus mengalokasikan ruang yang benar secara manual, ditambah karena tidak ada penggabungan ident yang dapat digunakan dalam makro deklaratif I harus secara manual menentukan nama getter dan setter. Bahkan hanya menginisialisasi penyatuan sulit saat ini karena saya harus terlebih dahulu menginisialisasi dengan beberapa nilai default dan kemudian menetapkan nilai ke varian yang saya inginkan (atau menambahkan set metode lain untuk membangun penyatuan yang bahkan lebih verbositas dalam definisi serikat). Secara keseluruhan lebih banyak pekerjaan dan rentan terhadap kesalahan dan lebih buruk daripada dukungan asli untuk serikat pekerja. Mungkin Anda harus membaca RFC dan diskusi yang menyertainya sehingga Anda dapat memahami mengapa fitur ini sangat penting.

Dan sama untuk keselarasan.

Saya membayangkan penggabungan identitas seharusnya tidak terlalu sulit sekarang syn ada. Ini memungkinkan Anda untuk melakukan operasi pada AST yang diteruskan, sehingga Anda dapat mengambil dua Ident , mengekstrak representasi string mereka ( Ident implements AsRef<str> ), lalu membuat Ident yang adalah penggabungan keduanya menggunakan Ident::From<String>() .

RFC menyebutkan banyak tentang bagaimana implementasi makro yang ada tidak praktis untuk digunakan, namun dengan pembuatan peti baru-baru ini seperti syn dan quote , sekarang jauh lebih mudah untuk melakukan macro proc. Saya merasa hal itu akan sangat membantu dalam meningkatkan ergonomi dan mengurangi risiko kesalahan.

Misalnya, Anda dapat memiliki MyUnion::default() yang hanya nol buffer internal serikat, kemudian fn MyUnion::new<T>(value:T) -> MyUnion , di mana T memiliki sifat terikat memastikan Anda hanya dapat menginisialisasi dengan jenis yang benar .

Dalam hal perataan dan ukuran, apakah Anda dapat menggunakan modul mem dari pustaka standar (yaitu std :: mem :: align_of () and friends)? Saya kira semua yang saya usulkan akan bergantung pada kemampuan untuk menggunakannya pada waktu ekspansi makro untuk mengetahui ukuran dan penyelarasan yang diperlukan. 99,9% dari waktu serikat digunakan, itu dilakukan dengan tipe primitif, jadi saya rasa Anda akan dapat menulis fungsi pembantu yang mengambil nama tipe dan mengembalikan keselarasan atau ukurannya (mungkin bertanya kepada kompiler, meskipun itu lebih dari detail implementasi).

Saya akui, pencocokan pola bawaan akan sangat bagus, tetapi sebagian besar waktu setiap serikat yang Anda gunakan di FFI akan terbungkus dalam lapisan abstraksi tipis. Jadi Anda mungkin bisa mendapatkan beberapa pernyataan if / else atau menggunakan fungsi helper.

Dalam hal perataan dan ukuran, apakah Anda dapat menggunakan modul mem dari pustaka standar (yaitu std :: mem :: align_of () dan teman-teman)?

Itu tidak akan berhasil dalam konteks kompilasi silang.

@ Michael-F-Bryan Semua diskusi ini dan banyak lagi terjadi dalam sejarah https://github.com/rust-lang/rfcs/pull/1444 . Untuk meringkas respons terhadap masalah spesifik Anda, selain yang telah disebutkan: Anda harus menerapkan ulang padding dan aturan penyelarasan dari setiap platform / compiler target, dan menggunakan sintaks yang canggung di seluruh kode FFI Anda (yang sebenarnya telah dilakukan

Juga:

99,9% dari waktu serikat digunakan, itu dilakukan dengan tipe primitif

Tidak benar sama sekali. Kode C secara ekstensif menggunakan pola "struct of unions of struct", di mana sebagian besar field union terdiri dari tipe struct yang berbeda.

@rfcbot fcp merge per @petrochenkov 's komentar https://github.com/rust-lang/rust/issues/32836#issuecomment -279.256.434

Saya tidak punya apa-apa untuk ditambahkan, hanya memicu bot

Anggota tim @withoutboats telah mengusulkan untuk menggabungkan ini. Langkah selanjutnya adalah meninjau oleh tim yang diberi tag lainnya:

  • [x] @aturon
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @pnkfelix
  • [x] @withoutboats

Tidak ada masalah yang saat ini terdaftar.

Setelah peninjau tersebut mencapai konsensus, ini akan memasuki periode komentar terakhirnya. Jika Anda melihat masalah besar yang belum pernah diangkat dalam proses ini, silakan angkat bicara!

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

PSA: Saya akan memperbarui RFC "Serikat 1.2" dengan satu perubahan lagi yang mempengaruhi serikat bergaya FFI - Saya akan memindahkan tugas yang aman ke bidang serikat yang dapat dirusak secara sepele dari "Arah masa depan" ke RFC yang sesuai.

union.trivially_destructible_field = 10; // safe

Mengapa:

  • Penugasan ke bidang serikat yang dapat dirusak sepele aman tanpa syarat, terlepas dari interpretasi serikat pekerja.
  • Ini akan menghapus kira-kira setengah dari blok unsafe terkait dengan serikat pekerja.
  • Akan lebih sulit untuk dilakukan nanti karena potensi sejumlah besar unused_unsafe peringatan / kesalahan dalam kode stabil.

@petrochenkov Apakah yang Anda maksud adalah "bidang serikat yang dapat dirusak secara sepele", atau "serikat dengan bidang yang sepenuhnya dapat dirusak secara sepele"?

Apakah Anda mengusulkan agar semua perilaku tidak aman terjadi saat dibaca, di mana Anda memilih interpretasi? Misalnya, memiliki gabungan yang berisi enum dan bidang lainnya, di mana nilai serikat berisi diskriminan yang tidak valid?

Pada level tinggi, hal itu tampaknya masuk akal. Itu memungkinkan beberapa hal yang saya anggap tidak aman, tetapi Rust secara umum tidak, seperti melewati destruktor atau membocorkan memori. Pada level rendah, saya ragu untuk mempertimbangkan suara itu.

Saya merasa tidak apa-apa tentang menstabilkan subset itu. Saya belum tahu pendapat saya tentang Unions 1.2 RFC ini karena saya belum sempat membacanya! Saya tidak yakin apa yang saya pikirkan tentang mengizinkan akses yang aman ke bidang dalam beberapa kasus. Saya merasa bahwa upaya kami untuk membuat gagasan "minimal" tentang apa yang tidak aman (hanya petunjuk dereferencing) adalah sebuah kesalahan, dalam retrospeksi, dan kami seharusnya menyatakan petak yang lebih luas dari hal-hal yang tidak aman (misalnya, banyak pemain), karena mereka berinteraksi secara kompleks dengan LLVM. Saya merasa ini mungkin juga terjadi di sini. Dengan kata lain, saya mungkin lebih suka menarik kembali aturan tentang unsafe bersamaan dengan kemajuan lebih lanjut tentang pedoman kode yang tidak aman.

@bayu_joo
"bidang yang dapat dirusak sepele", saya telah mengubah kata-katanya.

Apakah Anda mengusulkan agar semua perilaku tidak aman terjadi saat dibaca, di mana Anda memilih interpretasi?

Iya. Tulisan saja tidak dapat menyebabkan sesuatu yang berbahaya tanpa pembacaan berikutnya.

EDIT:

Saya merasa upaya kami untuk membuat gagasan "minimal" tentang apa yang tidak aman (hanya petunjuk dereferencing) adalah sebuah kesalahan, jika dipikir-pikir

Oh.
Penulisan aman sepenuhnya sejalan dengan pendekatan saat ini terhadap ketidakamanan, tetapi jika Anda ingin mengubahnya, saya mungkin harus menunggu.

Saya tidak merasa senang menstabilkan subset ini. Biasanya ketika kita menstabilkan subset, itu adalah subset sintaksis atau setidaknya subset yang cukup jelas. Bagian ini terasa agak rumit bagi saya. Jika ada begitu banyak keraguan tentang fitur yang kami belum siap untuk menstabilkan implementasi saat ini, maka saya lebih suka membiarkan semuanya tidak stabil untuk beberapa saat lagi.

@rc
Nah, subsetnya cukup jelas - "FFI unions", atau "C unions", atau "pre-C ++ 11 unions" - meskipun tidak sintaksis. Tujuan awal saya adalah menstabilkan subset ini ASAP (siklus ini, idealnya) sehingga dapat digunakan di perpustakaan seperti winapi .
Tidak ada yang sangat meragukan tentang subset yang tersisa dan implementasinya, hanya saja tidak mendesak dan perlu menunggu waktu yang tidak jelas sampai proses RFC "Serikat 1.2" selesai. Harapan saya adalah menstabilkan bagian yang tersisa dalam siklus 1, 2 atau 3 setelah stabilisasi bagian awal.

Saya pikir saya memiliki argumen utama untuk tugas lapangan yang aman.
Penugasan lapangan tidak aman

unsafe {
    u.trivially_destructible_field = value;
}

setara dengan penugasan serikat penuh yang aman

u = U { trivially_destructible_field: value };

kecuali bahwa versi aman secara paradoks kurang aman karena akan menimpa byte u di luar trivially_destructible_field dengan undefs, sementara tugas lapangan memiliki jaminan untuk membiarkannya utuh.

@petrochenkov Yang ekstrim dari itu adalah size_of_val(&value) == 0 , bukan?

Persamaan antara dua cuplikan hanya benar jika bidang yang dimaksud adalah
"dapat dirusak secara sepele", bukan?

Dalam hal ini membuat tugas seperti ini aman tetapi hanya dalam beberapa kasus
tampaknya sangat tidak konsisten bagi saya.

Pada 22 Feb 2017 14:50, "Vadim Petrochenkov" [email protected]
menulis:

Saya pikir saya memiliki argumen utama untuk tugas lapangan yang aman.
Penugasan lapangan tidak aman

tidak aman {
u.trivially_destructible_field = nilai;
}

setara dengan penugasan serikat penuh yang aman

u = U {trivially_destructible_field: nilai};

kecuali bahwa versi aman secara paradoks kurang aman karena memang demikian
menimpa byte u di luar trivially_destructible_field dengan undefs,
sedangkan tugas lapangan memiliki jaminan untuk membiarkannya utuh.

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment-281660298 ,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/AApc0lUOXLU5xNTfM5PEfEz9nutMZhXUks5rfC8UgaJpZM4IDXsj
.

@deddyb

Ekstremnya adalah size_of_val (& value) == 0, bukan?

Ya.

@agisa
Saya tidak mengerti mengapa satu aturan sederhana tambahan yang menghilangkan sebagian besar positif palsu sangat tidak konsisten. "hanya beberapa kasus" mencakup semua serikat FFI secara khusus.
Saya pikir "sangat tidak konsisten" adalah perkiraan yang terlalu tinggi. Tidak sebesar "hanya variabel mut dapat ditetapkan? Inkonsistensi yang mengerikan!", Tetapi masih mengarah ke arah ini.

@petrochenkov tolong pertimbangkan kasus seperti itu:

// Somebody Somewhere in some crate (v 1.0.0)
struct Peach; // trivially destructible
union Banana { pub actually: Peach }

// Somebody Else in their dependent crate
extern some crate;
fn somefn(banana: &mut Banana) {
    banana.actually = Peach;
}

Sekarang karena menambahkan implementasi sifat umumnya bukan merupakan perubahan yang mengganggu, Tn. Somebody Somewhere tahu mungkin ada baiknya untuk menambahkan implementasi berikut

impl Drop for Peach { fn drop(&mut self) { println!("Moi Peach!") }

dan merilis versi 1.1.0 (semver kompatibel dengan 1.0.0 AFAIK) dari crate.

Tiba-tiba peti Mr. Somebody Else tidak terkompilasi lagi:

fn somefn(banana: &mut Banana) {
    banana.actually = Peach; // ERROR: Something something… unsafe assingment… somewhat somewhat trivially indestructible… 
}

Dan karena itu terkadang mengizinkan penugasan yang aman ke bidang serikat tidak semudah hanya mut penduduk setempat yang dapat mutasi.


Saat saya menuliskan contoh ini, saya menjadi tidak yakin sikap mana yang harus saya ambil, jujur ​​saja. Di satu sisi, saya ingin mempertahankan properti yang menambahkan implementasi secara umum bukan merupakan perubahan yang dapat menyebabkan gangguan (mengabaikan kasus potensial XID). Di sisi lain, mengubah bidang penyatuan apa pun dari yang dapat dirusak secara sepele menjadi dapat dirusak secara tidak sepele jelas merupakan perubahan yang tidak kompatibel semver yang sangat mudah untuk diabaikan dan aturan yang diusulkan akan membuat ketidaksesuaian ini lebih terlihat (selama penugasan tidak berada dalam blok yang tidak aman sudah).

@agisa
Ini adalah argumen yang bagus, saya tidak memikirkan kompatibilitas.

Masalahnya tampaknya bisa dipecahkan. Untuk menghindari masalah kompatibilitas, lakukan hal yang sama seperti koherensi - hindari alasan negatif. Yaitu mengganti "trivially-destructible" == "tidak ada komponen yang mengimplementasikan Drop " dengan pendekatan positif terdekat - "mengimplementasikan Copy ".
Copy type tidak dapat meng-un-implementasikan Copy backward-compatibly, dan tipe Copy masih mewakili mayoritas tipe "trivially-destructible", terutama dalam konteks unions FFI.

Menerapkan Drop sudah tidak kompatibel dengan versi sebelumnya dan ini tidak ada hubungannya dengan fitur gabungan:

// Somebody Somewhere in some crate (v 1.0.0)
struct Apple; // trivially destructible
struct Pineapple { pub actually: Apple }

// Somebody Else in their dependent crate
extern some crate;
fn pineapple_to_apple(pineapple: Pineapple) -> Apple {
    pineapple.actually
}
// some crate v 1.1.0
impl Drop for Pineapple { fn drop(&mut self) { println!("Moi Pineapple!") }
fn pineapple_to_apple(pineapple: Pineapple) -> Apple {
    pineapple.actually // ERROR: can't move out of Pineapple
}

Yang pada gilirannya terdengar seperti menerapkan Drop Drop Implicit Copy. Dan Salin
bisa diandalkan.

Pada Rabu, 22 Feb 2017 pukul 10:11, jethrogb [email protected] menulis:

Menerapkan Drop sudah tidak kompatibel ke belakang dan ini sudah
tidak ada hubungannya dengan fitur serikat pekerja:

// Seseorang di suatu tempat di beberapa peti (v 1.0.0)
struct Apple; // dapat dirusak sepele
struct Pineapple {pub sebenarnya: Apple}

// Seseorang di kandang tanggungan mereka
mengeluarkan beberapa peti;
fn pineapple_to_apple (nanas: Nanas) -> Apel {
nanas. sebenarnya
}

// some crate v 1.1.0
Im Jatuhkan untuk Nanas {fn drop (& mut self) {println! ("Moi Pineapple!")}

fn pineapple_to_apple (nanas: Nanas) -> Apel {
banana.actually // ERROR: tidak bisa keluar dari Pineapple
}

-
Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment-281752949 ,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/ABxXhgbFgRNzYOsU4c6Gu1KFfwdjDHn3ks5rfHpYgaJpZM4IDXsj
.

@tokopedia
Saya ingin menyebutkan masalah ini, tetapi tidak melakukannya karena memiliki sedikit prasyarat yang agak khusus - struct yang menerapkan Drop harus memiliki bidang publik dan bidang itu tidak boleh Copy . Kasus serikat mempengaruhi semua struct tanpa syarat.

@petrochenkov Saya menganggap bahwa mungkin argumen bahwa membuat serikat pekerja seharusnya tidak aman :)

@petrochenkov apa yang merupakan jalur pengembangan dan pengalaman pengguna malam untuk menstabilkan subset? Apakah kita menambahkan gerbang fitur baru terlebih dahulu untuk subset, sehingga orang bisa mendapatkan pengalaman konkret menggunakan subset sebelum menjadi stabil?

@pramugari_id
Apa yang saya asumsikan hanyalah berhenti membutuhkan #[feature(untagged_unions)] untuk subset ini, tidak ada fitur baru atau birokrasi lainnya.
Serikat pekerja bergaya FFI seharusnya menjadi jenis serikat yang paling sering digunakan, jadi fitur baru berarti jaminan kerusakan tepat sebelum stabilisasi, yang, menurut saya, akan mengganggu.

Saya hanya ingin mencatat bahwa karena atribut penyelarasan dan pengepakan belum diimplementasikan (apalagi distabilkan), saya belum benar-benar membutuhkan hal ini untuk distabilkan.

@ retret
Pengepakan? Jika yang Anda maksud #[repr(packed)] maka itu didukung di serikat pekerja sekarang (tidak seperti atribut align(>1) ).

@petrochenov #[repr(packed(N))] . Packing selain 1 dibutuhkan cukup banyak di winapi. Bukannya saya membutuhkan hal-hal ini secara khusus didukung pada serikat pekerja, saya hanya tidak ingin melompat ke versi mayor baru untuk meningkatkan persyaratan Rust minimum saya kecuali saya bisa mendapatkan semua hal itu pada saat yang bersamaan.

Untuk sedikit memperjelas status permainan di sini:

Proposal FCP saat ini hanya untuk serikat murni- Copy . Begitulah, sejauh yang saya tahu, pada dasarnya tidak ada pertanyaan luar biasa selain "Haruskah kita menstabilkan?" Diskusi sejak pindah ke FCP semuanya tentang RFC baru

@nrc dan @nikomatsakis , dari diskusi di IRC, saya kira Anda berdua siap untuk mencentang kotak Anda, tetapi saya akan menyerahkannya kepada Anda ;-)

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

@tokopedia
Bagi saya, ini akan mendapat manfaat dari ide yang saya lontarkan baru-baru ini. (https://internals.rust-lang.org/t/automatic-marker-trait-for-unconditionally-valid-repr-c-types/5054)

Meskipun tidak diusulkan dengan mempertimbangkan serikat pekerja, sifat Plain seperti yang dijelaskan (tunduk pada bikeshedding) akan memungkinkan penyatuan apa pun yang hanya terdiri dari jenis Plain untuk digunakan tanpa adanya ketidakamanan. Ini mengkodifikasikan properti bahwa pola _any_ bit dalam memori sama-sama valid, sehingga Anda dapat memecahkan masalah inisialisasi dengan mengamanatkan memori dikosongkan, dan memastikan pembacaan tidak dapat memanggil UB.

Dalam konteks FFI, menjadi Plain seperti yang didefinisikan juga merupakan persyaratan de facto untuk kode seperti itu dalam banyak kasus, sementara kasus di mana tipe non- Plain berguna jarang dan sulit untuk diatur dengan aman.

Dengan mengesampingkan keberadaan faktual dari suatu sifat yang dinamai, mungkin bijaksana untuk membagi fitur ini menjadi dua. Dengan union yang tidak memenuhi syarat menegakkan persyaratan dan mengizinkan penggunaan tanpa ketidakamanan, dan unsafe union dengan persyaratan santai untuk konten dan lebih banyak sakit kepala bagi pengguna. Itu akan memungkinkan menstabilkan tanpa menghalangi cara untuk menambahkan yang lain di masa depan.

@ le-j
Ini tampaknya cukup ortogonal bagi serikat pekerja.
Saya akan memperkirakan menerima Plain ke dalam Rust dalam waktu dekat karena sangat tidak mungkin + membuat lebih banyak akses ke bidang serikat aman lebih atau kurang kompatibel ke belakang (tidak sepenuhnya karena lint), jadi saya tidak akan menunda serikat karena untuk itu.

@petrochenkov Saya tidak menyarankan untuk menunda serikat dalam menunggu penutupan proposal saya, melainkan untuk mempertimbangkan _keberadaan_ dari kemungkinan pembatasan tersebut dengan sendirinya. Membuat lebih banyak akses ke bidang serikat yang aman di masa mendatang dapat menghadapi kendala karena hal itu menciptakan lebih banyak ketidakkonsistenan dalam bahasa. Secara khusus, membuatnya sedemikian rupa sehingga keamanan akses lapangan yang bervariasi dari satu bidang ke bidang lainnya tampak buruk.

Oleh karena itu saran saya untuk membuat deklarasi unsafe union , sehingga versi yang aman digunakan tanpa syarat dapat diperkenalkan nanti tanpa menambahkan kata kunci baru. Perlu dicatat bahwa versi yang sepenuhnya aman digunakan sudah cukup untuk sebagian besar kasus penggunaan.

Edit: Mencoba untuk menjelaskan apa yang ingin saya katakan.

Tempat lain di mana kemungkinan ini dapat menginformasikan desain saat ini dan masa depan adalah inisialisasi. Untuk penyatuan yang aman tanpa syarat, seluruh rentang memori yang dicadangkan untuk itu harus dikosongkan. Bahkan dengan versi saat ini, memastikan ini akan mengurangi kemungkinan skenario UB dan membuat penggunaan serikat lebih mudah.

Periode komentar terakhir sekarang sudah selesai.

Sekarang FCP yang akan digabungkan selesai, apa langkah selanjutnya di sini? Akan menyenangkan untuk menstabilkan ini untuk 1,19.

Akan luar biasa jika seseorang dapat menambahkan lebih banyak detail ke pesan @rfcbot itu ( kode sumber di sini ). Ini bisa mempermudah seseorang yang tidak terbiasa dengan proses untuk masuk dan memindahkan barang.

Jalannya jelas untuk memasukkan ini ke 1,19. Adakah yang terlibat untuk itu? cc @joshtripett

Apakah pengoptimalan tata letak NonZero enum dijamin akan diterapkan melalui union ? Misalnya, Option<ManuallyDrop<&u32>> seharusnya tidak mewakili None sebagai penunjuk null. Some(ManuallyDrop::new(uninitialized::<[Vec<Foo>; 10]>())).is_some() seharusnya tidak membaca memori yang tidak diinisialisasi.

https://crates.io/crates/nodrop (digunakan di https://crates.io/crates/arrayvec) memiliki peretasan untuk menangani hal ini.

@Septianjoko_
Ini sekarang ditandai sebagai pertanyaan yang belum terselesaikan di RFC .
Dalam pelaksanaannya saat ini program ini

#![feature(untagged_unions)]

struct S {
    _a: &'static u8
}
union U {
    _a: &'static u8
}

fn main() {
    use std::mem::size_of;
    println!("struct {}", size_of::<S>());
    println!("optional struct {}", size_of::<Option<S>>());
    println!("union {}", size_of::<U>());
    println!("optional union {}", size_of::<Option<U>>());
}

cetakan

struct 8
optional struct 8
union 8
optional union 16

, yaitu pengoptimalan tidak dilakukan.
cc https://github.com/rust-lang/rust/issues/36394

Ini tidak mungkin membuat 1,19.

@tokopedia

Ini tidak mungkin membuat 1,19.

PR stabilisasi digabungkan.

Dengan serikat yang tidak diberi tag sekarang dikirim dalam 1,19 (sebagian dari https://github.com/rust-lang/rust/pull/42068) - apakah ada yang tersisa dalam masalah ini atau haruskah kita menutup?

@septianjoko_
Masih ada banyak serikat pekerja dengan bidang non- Copy !
Kemajuan sebagian besar diblokir pada klarifikasi / dokumentasi RFC (https://github.com/rust-lang/rfcs/pull/1897).

Apakah sudah ada kemajuan pada serikat pekerja dengan bidang non- Copy sejak Agustus? Serikat 1.2 RFC tampaknya terhenti (Saya menduga karena periode impl?)

Mengizinkan ?Sized jenis dalam serikat - jika hanya untuk serikat jenis tunggal - akan memudahkan penerapan https://github.com/rust-lang/rust/issues/47034 :

`` karat
serikat ManuallyDrop{
nilai: T
}

@mikeyhew Anda hanya perlu mewajibkan paling banyak satu jenis dapat diubah ukurannya.

Saya melihat beberapa kode Rust menggunakan union s, dan tidak tahu apakah itu memanggil perilaku yang tidak ditentukan atau tidak.

Referensi [items :: unions] hanya menyebutkan:

Bidang yang tidak aktif juga dapat diakses (menggunakan sintaks yang sama) jika tata letak cukup kompatibel dengan nilai saat ini yang disimpan oleh union. Membaca bidang yang tidak kompatibel menghasilkan perilaku yang tidak ditentukan.

Tapi saya tidak bisa menemukan definisi "layout compatible" baik di [items :: unions] maupun di [type_system :: type_layout] .

Melihat melalui RFC saya belum dapat menemukan definisi "layout yang kompatibel", hanya contoh melambaikan tangan tentang apa yang harus dan tidak boleh berfungsi di RFC 1897: Unions 1.2 (tidak digabung).

RFC1444: serikat pekerja tampaknya hanya memungkinkan mentransmutasi serikat ke variannya selama itu tidak memicu perilaku yang tidak ditentukan tetapi saya tidak dapat menemukan di mana pun di RFC ketika itu / bukan masalahnya.

Apakah aturan _precise_ yang memberi tahu saya apakah sepotong kode yang menggunakan serikat pekerja telah mendefinisikan perilaku yang tertulis di suatu tempat (dan apa perilaku yang ditentukan itu)?

@gnzlbg Untuk perkiraan pertama: Anda tidak dapat mengakses padding, tidak dapat mengakses enum yang berisi diskriminan yang tidak valid, tidak dapat mengakses bool yang berisi nilai selain benar atau salah, tidak dapat mengakses nilai floating-point yang tidak valid atau memberi sinyal , dan beberapa hal lain seperti itu.

Jika Anda menunjuk ke kode tertentu yang melibatkan serikat, kami dapat melihatnya dan memberi tahu Anda jika kode tersebut melakukan sesuatu yang tidak ditentukan.

Untuk perkiraan pertama: Anda tidak dapat mengakses padding, tidak dapat mengakses enum yang berisi diskriminan yang tidak valid, tidak dapat mengakses bool yang berisi nilai selain true atau false, tidak dapat mengakses nilai floating-point yang tidak valid atau memberi sinyal, dan beberapa hal lain seperti itu.

Sebenarnya konsensus terbaru adalah bahwa membaca sembarang float tidak masalah (# 46012).

Saya akan menambahkan satu persyaratan lagi: varian sumber dan serikat target adalah #[repr(C)] dan begitu juga semua bidangnya (dan secara rekursif) jika mereka adalah struct.

@Amanieu Saya berdiri dikoreksi, terima kasih.

Jadi saya kira aturannya tidak tertulis di mana pun?

Saya melihat bagaimana menggunakan stdsimd dengan antarmuka barunya. Kecuali jika kita menstabilkan beberapa tambahan dengannya, kita perlu menggunakan serikat pekerja untuk melakukan pukulan jenis dengan beberapa jenis simd, seperti ini:

https://github.com/rust-lang-nursery/stdsimd/blob/03cb92ddce074a5170ed5e5c5c20e5fa4e4846c3/coresimd/src/x86/test.rs#L17

AFAIK menulis satu bidang serikat dan membaca yang lain sangat mirip dengan menggunakan transmute_copy , dengan batasan yang sama. Bahwa batasan-batasan tersebut masih samar-samar tidaklah spesifik untuk serikat pekerja.

Dalam hal ini, fungsi yang Anda tautkan bisa menggunakan transmute::<__m128d, [f64; 2]> . Meskipun versi gabungan bisa dibilang lebih bagus, setidaknya sekali transmute yang ada saat ini dihapus: Bisa jadi hanya A { a }.b[idx] .

@rkruppe Saya telah mengisi masalah clippy untuk menambahkan lint itu: https://github.com/rust-lang-nursery/rust-clippy/issues/2361

fungsi yang Anda tautkan bisa menggunakan transmute :: <__ m128d i = "8">

Saya rasa aturan yang saya cari adalah kapan transmutasi memunculkan perilaku yang tidak terdefinisi (jadi saya akan mencarinya).

Saya pikir itu akan membantu saya jika referensi bahasa tentang serikat pekerja akan menetapkan aturan untuk serikat dalam hal transmutasi (meskipun aturan untuk transmutasi belum 100% jelas) daripada hanya menyebutkan "kompatibilitas tata letak" dan membiarkannya pada saat itu. Lompatan dari "kompatibilitas tata letak" ke "jika transmute tidak memunculkan perilaku tidak terdefinisi, maka jenisnya kompatibel dengan tata letak dan dapat diakses melalui jenis punning" tidak jelas bagi saya.

Untuk lebih jelasnya, transmutasi [_copy] tidak "lebih primitif" dari pada unions. Sebenarnya transmute_copy secara harfiah hanya penunjuk as pemain ditambah ptr::read . transmute tambahan membutuhkan mem::uninitialized (tidak digunakan lagi) atau MaybeUninitialized (gabungan) atau sesuatu seperti itu, dan diimplementasikan sebagai intrinsik untuk efisiensi, tetapi itu juga bermuara pada tipe- punning memcpy. Alasan utama saya menarik koneksi untuk mentransmutasikan adalah karena itu lebih tua dan secara historis terlalu ditekankan dan oleh karena itu saat ini kami memiliki lebih banyak tulisan dan pengetahuan cerita rakyat yang berfokus pada transmisi secara khusus. Konsep dasar yang sebenarnya, yang menentukan apa yang valid dan apa yang tidak (dan mana yang akan dijelaskan oleh spesifikasi), adalah bagaimana nilai disimpan dalam memori sebagai byte dan urutan byte mana yang UB untuk dibaca sebagai tipe.

Koreksi: transmutasi sebenarnya tidak membutuhkan penyimpanan yang tidak diinisialisasi (melalui intrinsik, atau persatuan, atau sebaliknya). Selain efisiensi, Anda dapat melakukan sesuatu seperti ini (belum teruji, mungkin berisi kesalahan ketik yang memalukan):

fn transmute<T, U>(x: T) -> U {
    assert!(size_of::<T>() == size_of::<U>());
    let mut bytes = [0u8; size_of::<U>()];
    ptr::write(bytes.as_mut_ptr() as *mut T, x);
    mem::forget(x);
    ptr::read(bytes.as_ptr() as *const U)
}

Satu-satunya bagian "ajaib" dari transmute adalah ia dapat membatasi parameter tipe agar berukuran sama pada waktu kompilasi .

Referensi dan dan Serikat 1.2 RFC sengaja dibuat kabur mengenai hal ini karena aturan transmutasi pada umumnya tidak diselesaikan.
Maksudnya adalah "untuk repr(C) unions lihat spesifikasi ABI pihak ketiga, untuk repr(Rust) unions kompatibilitas tata letak sebagian besar tidak ditentukan (kecuali jika)".

Apakah sudah terlambat untuk meninjau kembali semantik pemeriksaan lepas serikat dengan bidang penurunan?

Masalah aslinya adalah bahwa menambahkan ManuallyDrop menyebabkan Josephine menjadi tidak sehat, karena itu (agak nakal) mengandalkan nilai-nilai yang dipinjam perma yang tidak memiliki penyimpanan pendukungnya yang direklamasi tanpa terlebih dahulu menjalankan destruktornya.

Contoh stripped-down ada di https://play.rust-lang.org/?gist=607e2dfbd51f4062b9dc93d149815695&version=nightly. Idenya adalah bahwa ada tipe Pin<'a, T> , dengan metode pin(&'a self) -> &'a T yang keamanannya bergantung pada invarian "setelah memanggil pin.pin() , jika memori yang mendukung pin pernah direklamasi, maka destruktor pin pasti sudah dijalankan ".

Invarian ini dipertahankan oleh Rust sampai #[allow(unions_with_drop_fields)] ditambahkan, dan digunakan oleh ManuallyDrop https://doc.rust-lang.org/src/core/mem.rs.html#949.

Invarian akan dipulihkan jika pemeriksa drop menganggap serikat dengan bidang drop memiliki impl. Ini adalah perubahan yang mengganggu, tetapi saya ragu kode apa pun di alam liar bergantung pada semantik saat ini.

Percakapan IRC: https://botbot.me/mozilla/rust-lang/2018-02-01/?msg=96386869&page=3

Masalah Josephine: https://github.com/asajeffrey/josephine/issues/52

cc: @nox @eddyb @pnkfelix

Masalah aslinya adalah bahwa menambahkan ManuallyDrop menyebabkan Josephine menjadi tidak sehat, karena itu (agak nakal) bergantung pada nilai yang dipinjam secara permanen yang tidak memiliki penyimpanan pendukungnya direklamasi tanpa terlebih dahulu menjalankan destruktornya.

Destructors tidak dijamin akan berjalan. Karat tidak menjamin itu. Ia mencoba, tetapi, misalnya, std::mem::forget dijadikan fungsi yang aman.

Invarian akan dipulihkan jika pemeriksa drop menganggap serikat dengan bidang drop memiliki impl. Ini adalah perubahan yang mengganggu, tetapi saya ragu kode apa pun di alam liar bergantung pada semantik saat ini.

Serikat pekerja tidak aman terutama karena Anda tidak tahu bidang serikat mana yang valid. Serikat pekerja tidak dapat memiliki otomatis Drop impl; jika Anda menginginkan impl seperti itu, Anda harus menulisnya secara manual, dengan mempertimbangkan cara apa pun Anda harus tahu apakah field union dengan Drop impl valid.

Satu klarifikasi di sini: Saya tidak percaya kita harus pernah membiarkan serikat dengan Drop bidang secara default tanpa setidaknya serat memperingatkan-by-default, jika tidak berbulu kesalahan-by-default. unions_with_drop_fields seharusnya tidak hilang sebagai bagian dari proses stabilisasi.

EDIT: Ups, tidak bermaksud untuk menekan "tutup dan komentar".

@joshtriplett yes, Rust tidak menjamin bahwa destruktor akan berjalan, tetapi hal itu terjadi (sebelum 1,19) untuk mempertahankan invarian bahwa nilai yang dipinjam perma hanya akan mendapatkan kembali memorinya jika destruktor berjalan. Ini bahkan benar jika ada mem::forget , karena Anda tidak dapat menyebutnya dengan nilai pinjaman-perma.

Inilah yang diandalkan oleh Joephine dengan agak nakal, tetapi tidak benar lagi karena bagaimana drop checker memperlakukan unions_with_drop_fields .

Tidak masalah jika allow(unions_with_drop_fields) dianggap sebagai anotasi yang tidak aman, ini tidak akan menjadi perubahan drastis, AFAICT, ini hanya membutuhkan deny(unsafe_code) untuk memeriksa allow(unions_with_drop_fields) .

@asajeffrey Saya masih mencoba memahami hal Pin ... jadi, jika saya mengikuti contoh dengan benar, alasan "bekerja" ini adalah karena fn pin(&'a Pin<'a, T>) -> &'a T memaksa peminjaman bertahan selama sebagai selama masa pakai 'a dijelaskan dalam jenisnya, dan masa pakai itu juga tidak berubah.

Itu pengamatan yang menarik! Saya tidak menyadari trik ini. Perasaan saya adalah bahwa ini bekerja "secara tidak sengaja", yaitu Rust aman tidak terjadi untuk menyediakan cara untuk mencegah destruktor dari berjalan tetapi itu tidak menjadikan ini bagian dari "kontrak". Khususnya, https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html tidak mencantumkan kebocoran.

IMO tidak masalah jika itu bekerja secara tidak sengaja atau sengaja. Tidak ada cara untuk menghindari Drop berjalan dengan trik ini sebelum ManuallyDrop ada (yang membutuhkan kode yang tidak aman untuk diterapkan), dan sekarang kita tidak dapat mengandalkannya lagi.

Penambahan ManuallyDrop pada dasarnya membunuh perilaku Rust yang rapi dan mengatakan bahwa itu seharusnya tidak diandalkan di tempat pertama terdengar seperti penalaran melingkar bagi saya. Jika ManuallyDrop tidak mengizinkan panggilan Pin::pin , apakah ada cara lain untuk membuat panggilan Pin::pin tidak sehat? Saya rasa tidak.

Saya tidak berpikir kita dapat berkomitmen untuk menjaga setiap jaminan yang secara tidak sengaja diberikan oleh Rustc saat ini. Kami tidak tahu apa jaminan ini, jadi kami akan menstabilkan babi dalam keadaan sulit (oke, saya harap idiom ini masuk akal ... menurut kamus saya cocok dengan idiom bahasa ibu saya, yang secara harfiah diterjemahkan menjadi " kucing di dalam tas ";) - yang ingin saya katakan adalah kami tidak tahu apa yang akan kami stabilkan).

Juga, ini adalah pedang bermata dua - setiap jaminan tambahan yang kami berikan adalah sesuatu yang harus dijaga oleh kode yang tidak aman. Jadi menemukan jaminan baru mungkin juga merusak kode tidak aman yang ada (duduk diam di suatu tempat di crates.io tanpa menyadarinya) seperti mengaktifkan kode tidak aman baru (yang terakhir terjadi di sini).

Misalnya, sangat bisa dibayangkan bahwa masa hidup leksikal memungkinkan kode yang tidak aman yang rusak oleh masa hidup non-leksikal. Saat ini semua masa pakai tersarang dengan baik, mungkin ada cara bagi kode yang tidak aman untuk mengeksploitasinya? Hanya dengan masa hidup non-leksikal mungkin ada masa hidup yang tumpang tindih, tetapi tidak ada yang termasuk dalam yang lain. Apakah ini membuat NLL menjadi perubahan yang merusak? Saya harap tidak!

Jika ManuallyDrop tidak mengizinkan pemanggilan Pin :: pin, akankah ada cara lain untuk membuat pemanggilan Pin :: pin tidak sehat? Saya rasa tidak.

Dengan unsafe kode, akan ada. Jadi dengan mendeklarasikan suara trik Pin , Anda menyatakan beberapa kode tidak aman unsound yang akan terdengar jika kita memutuskan ManuallyDrop tidak apa-apa.

Yang kami jelaskan adalah cara yang sangat ergonomis untuk mengintegrasikan Rust dengan GC. Apa yang ingin saya katakan adalah kedengarannya salah bagi saya untuk memberi tahu kami bahwa ini hanya kecelakaan yang berhasil dan bahwa kita harus melupakannya, ketika saya tidak dapat menemukan kasus penggunaan apa pun untuk tidak membatasi serikat dengan Drop Bidang @asajeffrey di sini, dan saat ini benar-benar satu-satunya kutil yang menghancurkan Josephine.

Saya akan dengan senang hati melupakannya jika seseorang dapat menunjukkan bahwa itu tidak sehat bahkan tanpa ManuallyDrop .

Apa yang ingin saya katakan adalah kedengarannya salah bagi saya untuk memberi tahu kami bahwa ini hanya kecelakaan yang berhasil

Saya tidak melihat indikasi bahwa trik ini pernah "dirancang", jadi saya rasa cukup adil untuk menyebutnya kecelakaan.

dan bahwa kita harus melupakannya

Saya seharusnya menjelaskan bahwa bagian ini hanya firasat pribadi saya. Saya pikir itu juga bisa menjadi titik tindakan yang masuk akal untuk menyatakan ini sebagai "kecelakaan yang membahagiakan" dan benar-benar menjadikannya jaminan - jika kami cukup yakin bahwa memang semua kode unsafe menghormati jaminan ini, dan itu memberikan jaminan ini lebih penting daripada kasus penggunaan ManuallyDrop . Ini adalah trade-off, mirip dengan leakpocalypse, di mana kita tidak bisa makan kue kita dan memilikinya juga (kita tidak bisa memiliki Rc dengan API-nya saat ini dan drop - berdasarkan untaian cakupan; kita tidak dapat memiliki ManuallyDrop dan Pin ) jadi kita harus membuat keputusan dengan cara apa pun.

Meskipun demikian, saya merasa sulit untuk mengungkapkan jaminan sebenarnya yang diberikan di sini dengan cara yang tepat, yang membuat saya secara pribadi lebih condong ke sisi " ManuallyDrop baik-baik saja".

jika kami cukup yakin bahwa memang semua kode unsafe mematuhi jaminan ini, dan bahwa memberikan jaminan ini lebih penting daripada kasus penggunaan ManuallyDrop . Ini adalah trade-off, mirip dengan leakpocalypse, di mana kita tidak bisa makan kue kita dan memilikinya juga (kita tidak bisa memiliki Rc dengan API-nya saat ini dan drop - berdasarkan untaian cakupan; kita tidak dapat memiliki ManuallyDrop dan Pin ) jadi kita harus membuat keputusan dengan cara apa pun.

Cukup adil, saya setuju dengan sepenuh hati. Perhatikan bahwa jika kita mempertimbangkan apa yang dideskripsikan oleh drop .

Sejauh yang saya mengerti proposal Alan bukanlah untuk menghapus ManuallyDrop , hanya untuk membuat dropck menganggap bahwa itu (dan serikat lain dengan kolom Drop ) memiliki destruktor. (Perusak itu kebetulan tidak melakukan apa-apa, tetapi keberadaannya hanya memengaruhi program apa yang diterima atau ditolak oleh dropck.)

Saya akan dengan senang hati melupakannya jika seseorang dapat menunjukkan bahwa itu tidak sehat bahkan tanpa ManuallyDrop.

Tidak yakin apakah ini memenuhi syarat, tapi inilah upaya pertama saya: Implementasi konyol dari sesuatu seperti ManuallyDrop yang bekerja di pra- union Rust.

pub mod manually_drop {
    use std::mem;
    use std::ptr;
    use std::marker::PhantomData;

    pub struct ManuallyDrop<T> {
        data: [u8; 32],
        phantom: PhantomData<T>,
    }

    impl<T> ManuallyDrop<T> {
        pub fn new(x: T) -> ManuallyDrop<T> {
            assert!(mem::size_of::<T>() <= 32);
            let mut data = [0u8; 32];
            unsafe {
                ptr::copy(&x as *const _ as *const u8, &mut data[0] as *mut _, mem::size_of::<T>());
            }
            mem::forget(x);
            ManuallyDrop { data, phantom: PhantomData }
        }

        pub fn deref(&self) -> &T {
            unsafe {
                &*(&self.data as *const _ as *const T)
            }
        }
    }
}

(Ya, saya mungkin harus melakukan beberapa pekerjaan lagi untuk mendapatkan keselarasan dengan benar, tetapi itu bisa dilakukan juga dengan mengorbankan beberapa byte.)
Taman bermain yang menampilkan jeda ini Pin : https://play.rust-lang.org/?gist=fe1d841cedb13d45add032b4aae6321e&version=nightly

Inilah yang saya maksud dengan pedang bermata dua di atas - sejauh yang saya bisa lihat, ManuallyDrop menghormati semua aturan yang telah kami keluarkan. Jadi, kami memiliki dua bagian kode tidak aman yang tidak kompatibel - ManuallyDrop dan Pin . Siapa yang "benar"? Saya akan mengatakan Pin bergantung pada jaminan yang tidak pernah kami buat dan karenanya "salah" di sini, tetapi ini adalah keputusan pengadilan, bukan bukti.

Nah, itu menarik. Dalam beberapa versi barang penyematan kami, Pin::pin mengambil &'this mut Pin<'this, T> , tetapi bukan tidak masuk akal jika ManuallyDrop memiliki DerefMut impl, benar ?

Berikut adalah taman bermain yang menunjukkan bahwa @RalfJung (tidak mengherankan) masih mematahkan Pin dengan metode &mut -mengambil pin .

https://play.rust-lang.org/?gist=5057570b54952e245fa463f8d7719663&version=nightly

tidak masuk akal jika ManuallyDrop Anda memiliki impl DerefMut, bukan?

Ya, saya baru saja menambahkan API yang saya butuhkan untuk contoh ini. deref_mut seharusnya berfungsi dengan baik.

Sejauh yang saya mengerti, proposal Alan bukanlah untuk menghapus ManuallyDrop, hanya untuk membuat dropck menganggap bahwa (dan serikat lain dengan kolom Drop) memiliki destruktor. (Perusak itu kebetulan tidak melakukan apa-apa, tetapi keberadaannya hanya memengaruhi program apa yang diterima atau ditolak oleh dropck.)

Ah, saya melewatkan itu; maaf soal itu. Menambahkan yang berikut ke contoh saya membuatnya tetap berfungsi:

    unsafe impl<#[may_dangle] T> Drop for ManuallyDrop<T> {
        fn drop(&mut self) {}
    }

Hanya jika saya menghapus #[may_dangle] Rust menolaknya. Jadi, paling tidak, kami harus membuat beberapa aturan yang dilanggar kode di atas - hanya mengatakan "ada beberapa kode yang kami ingin terdengar bahwa ini tidak kompatibel" adalah panggilan yang buruk karena membuatnya hampir tidak mungkin untuk melihat beberapa kode dan memeriksa apakah itu benar.


Saya pikir yang paling membuat saya tidak nyaman tentang "jaminan tidak disengaja" ini adalah bahwa saya tidak melihat satu pun alasan bagus Pin untuk bekerja tidak didasarkan pada "di sini adalah beberapa mekanisme dalam kompiler Rust, atau beberapa jenis jaminan sistem, yang cukup jelas mengatakan bahwa data yang dipinjam secara permanen tidak dapat dibocorkan" - ini lebih didasarkan pada “kami sudah berusaha keras dan kami belum bisa membocorkan data yang dipinjam secara permanen, jadi kami rasa tidak apa-apa”. Mengandalkan ini untuk kesehatan membuat saya sangat gugup. EDIT: Fakta bahwa dropck terlibat membuat saya semakin gugup karena bagian dari kompiler ini memiliki sejarah bug yang buruk. Alasan mengapa ini berhasil tampaknya adalah bahwa pinjaman-perma bertentangan dengan drop . Ini benar-benar tampaknya menjadi "penalaran berdasarkan analisis kasus yang lengkap tentang apa yang dapat dilakukan dengan data yang dipinjam secara permanen".

Sekarang, agar adil, orang bisa mengatakan hal serupa tentang mutabilitas interior - itu terjadi pada kasus yang mengizinkan modifikasi melalui referensi bersama benar-benar berfungsi dengan aman dalam beberapa kasus, jika kita memilih API yang tepat. Namun, membuat pekerjaan ini benar-benar diperlukan dukungan eksplisit dalam compiler ( UnsafeCell ) karena bentrokan dengan optimasi, dan ada kode yang tidak aman yang akan menjadi suara tanpa berubah-ubah interior namun tidak terdengar dengan berubah-ubah interior. Perbedaan lainnya adalah bahwa mutabilitas interior merupakan tujuan desain sejak awal (atau sejak awal - ini jauh sebelum saya bergabung dengan komunitas Rust), yang bukan merupakan kasus untuk "pinjaman permanen tidak bocor". Dan terakhir, untuk mutabilitas interior, saya pikir ada cerita yang cukup bagus tentang "berbagi membuat mutasi berbahaya , tetapi bukan tidak mungkin , dan API referensi bersama hanya mengatakan Anda tidak mendapatkan mutabilitas secara umum tetapi tidak mengecualikan mengizinkan lebih banyak operasi untuk spesifik jenis ", menghasilkan gambar keseluruhan yang koheren. Tentu saja, saya telah menghabiskan banyak waktu untuk memikirkan tentang referensi bersama, jadi mungkin ada gambaran yang sama koheren untuk masalah yang sedang dihadapi yang tidak saya sadari.

Zona waktu itu menyenangkan, saya baru saja bangun! Tampaknya ada dua masalah di sini (invarian secara umum, dan dropck pada khususnya), jadi saya akan menaruhnya di komentar terpisah ...

@RalfJung : ya, ini adalah masalah tentang invarian yang dikelola oleh Rust yang tidak aman. Untuk setiap versi Rust + std, ada lebih dari satu pilihan invarian I yang dipertahankan menggunakan alasan jaminan-jaminan. Dan memang mungkin ada dua pustaka L1 dan L2 , yang memilih I1 dan I2 yang tidak kompatibel, sehingga Rust + L1 aman dan Karat + L2 aman, tetapi Karat + L1 + L2 tidak aman.

Dalam kasus ini, L1 adalah ManuallyDrop dan L2 adalah Josephine , dan cukup jelas bahwa ManuallyDrop akan menang karena sekarang di std , yang memiliki batasan kompatibilitas ke belakang yang jauh lebih kuat daripada Josephine.

Menariknya, pedoman di https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html ditulis sebagai "Merupakan tanggung jawab programmer saat menulis kode yang tidak aman sehingga tidak mungkin membiarkan kode aman menunjukkan perilaku ini: ... "yaitu, ini adalah properti kontekstual (untuk semua konteks aman C, C [P] tidak bisa salah) dan begitu juga bergantung pada versi (karena Rust + std v1.20 memiliki lebih aman konteksnya daripada v1.18). Secara khusus, saya akan mengklaim bahwa pemasangan pin benar-benar memenuhi batasan ini untuk Rust sebelum 1,20, karena tidak ada konteks yang aman C st C [Pinning] berjalan salah.

Namun, ini hanya pengacara ruang barak, saya pikir semua orang setuju bahwa ada masalah dengan definisi kontekstual ini, maka semua diskusi tentang pedoman kode yang tidak aman.

Jika tidak ada yang lain, saya pikir pemasangan pin telah menunjukkan contoh menarik dari invarian kebetulan yang salah.

Hal khusus yang dilakukan serikat tanpa tag (dan karenanya ManuallyDrop ) lakukan adalah dalam interaksi dengan pemeriksa drop, khususnya ManualDrop bertindak seperti defnnya adalah:

unsafe impl<#[may_dangle] T> Drop for ManuallyDrop<T> { ... }

dan kemudian Anda dapat melakukan percakapan tentang apakah ini diizinkan atau tidak :) Memang, percakapan ini terjadi di utas may_dangle mulai dari https://github.com/rust-lang/rust/issues/ 34761 # penerbitan -362375924

@RalfJung kode Anda menunjukkan kasus sudut yang menarik, di mana jenis run-time untuk data adalah T , tetapi jenis waktu kompilasi adalah [u8; N] . Jenis mana yang menghitung sejauh may_dangle yang bersangkutan?

Menariknya, pedoman di https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html ditulis sebagai "Merupakan tanggung jawab programmer saat menulis kode yang tidak aman sehingga tidak mungkin membiarkan kode aman menunjukkan perilaku berikut: ... "yaitu, ini adalah properti kontekstual

Ah, menarik. Saya setuju ini jelas tidak cukup - ini akan membuat benang cakupan asli terdengar. Agar bermakna, ini harus (setidaknya) menentukan kumpulan kode tidak aman yang diizinkan untuk dipanggil oleh kode aman.

Secara pribadi, saya merasa cara yang lebih baik untuk menentukan ini adalah dengan memberikan invarian yang akan dipertahankan. Tapi saya jelas-jelas bias di sini, karena metodologi yang saya gunakan untuk membuktikan hal-hal tentang Rust membutuhkan ketidakberagaman. ;)

Saya sedikit terkejut bahwa halaman tersebut tidak berisi semacam pelepasan tanggung jawab hukum sebagai pendahuluan; kami belum begitu yakin apa sebenarnya batasannya - seperti yang ditunjukkan diskusi ini. Kami memerlukan kode yang tidak aman untuk setidaknya melakukan apa yang dikatakan dokumen itu, tetapi kami mungkin harus meminta lebih.

Misalnya, batasan perilaku tidak ditentukan dan apa yang dapat dilakukan kode tidak aman tidak sama. Lihat https://github.com/nikomatsakis/rust-memory-model/issues/44 untuk diskusi terbaru tentang topik itu: Menduplikasi &mut T untuk mem::size_of::<T>() == 0 tidak menyebabkan perilaku yang tidak ditentukan secara langsung, namun jelas dianggap ilegal untuk kode yang tidak aman untuk dilakukan. Alasannya adalah bahwa kode tidak aman lainnya mungkin bergantung pada disiplin kepemilikannya yang dihormati, dan menduplikasi hal-hal melanggar disiplin itu.

Jika tidak ada yang lain, saya pikir pemasangan pin telah menunjukkan contoh menarik dari invarian kebetulan yang salah.

Oh, tentu saja. Dan saya bertanya-tanya apa yang bisa kita lakukan untuk menghindari ini di masa depan? Mungkin memberi peringatan besar ke https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html yang mengatakan "hanya karena invarian kebetulan tersimpan di rustc + libstd, bukan berarti kode yang tidak aman dapat mengandalkannya; alih-alih inilah beberapa invarian yang dapat Anda andalkan "?

@RalfJung ya, saya rasa tidak ada orang yang menyukai definisi kontekstual dari "kebenaran", terutama karena itu rapuh dari kekuatan pengamatan konteks. Saya akan jauh lebih senang dengan definisi semantik dalam istilah invarian.

Satu-satunya hal yang saya minta adalah tolong, bisakah kita memberi diri kita ruang gerak dan menentukan dua invarian untuk alasan jaminan-kepercayaan (kode dapat mengandalkan R dan harus menjamin G, di mana G menyiratkan R). Dengan cara itu ada ruang untuk memperkuat R dan melemahkan G. Jika kita hanya memiliki satu invarian (yaitu R = G) kita terjebak tidak akan pernah bisa mengubahnya!

Pemeriksaan konstan saat ini bukan bidang gabungan kasus khusus: (cc @solson @ oli-obk)

union Transmute<T, U> { from: T, to: U }

const SILLY: () = unsafe {
    (Transmute::<usize, Box<String>> { from: 1 }.to, ()).1
};

fn main() {
    SILLY
}

Kode di atas menghasilkan kesalahan evaluasi miri "memanggil non-const fn std::ptr::drop_in_place::<(std::boxed::Box<std::string::String>, ())> - shim(Some((std::boxed::Box<std::string::String>, ()))) ".

Mengubahnya untuk memaksa tipe .to untuk diamati oleh const-checker:

const fn id<T>(x: T) -> T { x }

const SILLY: () = unsafe {
    (id(Transmute::<usize, Box<String>> { from: 1 }.to), ()).1
};

menghasilkan "destruktor tidak dapat dievaluasi pada waktu kompilasi".

Kode implementasi yang relevan ada di sini (khususnya panggilan restrict ):
https://github.com/rust-lang/rust/blob/5e4603f99066eaf2c1cf19ac3afbac9057b1e177/src/librustc_mir/transform/qualify_consts.rs#L557

Analisis yang lebih baik dari # 41073 telah mengungkapkan bahwa semantik saat penghancur dijalankan saat menetapkan ke subbidang serikat pekerja tidak cukup siap untuk stabilisasi. Lihat masalah itu untuk detailnya.

Apakah realistis untuk sepenuhnya mengesampingkan Drop jenis dalam serikat, dan menerapkan ManuallyDrop secara terpisah (sebagai lang-item)? Dari apa yang saya tahu, ManuallyDrop tampaknya menjadi motivasi terbesar untuk Drop dalam serikat pekerja, tetapi itu kasus yang sangat istimewa.

Tidak adanya sifat positif "tanpa jatuhkan", kita kemudian dapat mengatakan serikat pekerja terbentuk dengan baik jika setiap bidang adalah Copy atau dalam bentuk ManuallyDrop<T> . Itu sepenuhnya akan mengesampingkan semua komplikasi seputar menjatuhkan saat menetapkan bidang serikat (di mana tampaknya setiap solusi yang mungkin akan penuh dengan senjata yang mengejutkan), dan ManuallyDrop adalah penanda yang jelas bagi pemrogram bahwa mereka harus menangani Drop sendiri di sini. (Pengecekan bisa lebih pintar, misalnya bisa melintasi tipe produk dan tipe nominal yang dideklarasikan dalam peti yang sama. Tentu saja, memiliki cara positif untuk mengatakan "tipe ini tidak akan pernah mengimplementasikan Drop " akan menjadi lebih bagus.)


Daftar periksa di posting pertama tidak menyebutkan serikat tidak berukuran, juga tidak RFC --- tetapi kami masih

Hal ini bertentangan dengan cara serikat pekerja kadang-kadang digunakan di C, yang merupakan "titik ekstensi" (IIRC @joshtriplett -lah yang menyebutkan ini di semua tangan): File header dapat mendeklarasikan 3 varian untuk suatu gabungan, tetapi ini dianggap kompatibel ke depan dengan menambahkan lebih banyak varian nanti (selama itu tidak meningkatkan ukuran penyatuan). Pengguna perpustakaan berjanji untuk tidak menyentuh data gabungan jika tag (ada di tempat lain) menunjukkan bahwa mereka tidak mengetahui varian saat ini. Yang terpenting, jika Anda hanya mengetahui satu varian, bukan berarti hanya ada satu varian!

Pengecekan bisa lebih cerdas, misalnya bisa melintasi tipe produk dan tipe nominal yang dinyatakan dalam peti yang sama.

Predikat ini sudah ada, tetapi konservatif pada obat generik karena tidak ada sifat yang terikat.
Anda dapat mengaksesnya melalui std::mem::needs_drop (yang menggunakan instruksi intrinsik yang menerapkan rustc ).

@eddyb akan needs_drop memperhitungkan kompatibilitas ke depan, atau akankah dengan senang hati melihat ke ciptaan lain untuk menentukan apakah tipenya mengimplementasikan Drop ? Tujuannya di sini adalah untuk memiliki pemeriksaan yang tidak akan pernah putus dari perubahan yang kompatibel dengan semver, di mana misalnya menambahkan impl Drop ke struct tanpa parameter tipe atau masa hidup dan hanya bidang pribadi yang kompatibel semver.

@Ralfian

Hal ini bertentangan dengan cara serikat pekerja kadang-kadang digunakan di C, yang merupakan "titik ekstensi" (IIRC @joshtriplett -lah yang menyebutkan ini di semua tangan): File header dapat mendeklarasikan 3 varian untuk suatu gabungan, tetapi ini dianggap kompatibel ke depan dengan menambahkan lebih banyak varian nanti (selama itu tidak meningkatkan ukuran penyatuan). Pengguna perpustakaan berjanji untuk tidak menyentuh data gabungan jika tag (ada di tempat lain) menunjukkan bahwa mereka tidak mengetahui varian saat ini. Yang terpenting, jika Anda hanya mengetahui satu varian, bukan berarti hanya ada satu varian!

Itu kasus yang sangat spesifik.
Ini hanya mempengaruhi serikat gaya-C (jadi tidak ada destruktor dan semuanya Copy , tepatnya subset yang tersedia di stable) yang dihasilkan dari header C.
Kita dapat dengan mudah menambahkan field _dummy: () atau _future: () ke serikat tersebut dan tetap mendapatkan keuntungan dari model "enum" yang lebih aman secara default. Serikat FFI sebagai "titik ekstensi" adalah sesuatu yang perlu didokumentasikan dengan baik.

Pada 17 April 2018 10:08:54 PDT, Vadim Petrochenkov [email protected] menulis:

Kita dapat dengan mudah menambahkan bidang _dummy: () atau _future: () ke serikat tersebut
dan tetap memanfaatkan model "enum" kami yang lebih aman secara default.

Saya telah melihat orang-orang berbicara tentang memperlakukan serikat seperti enum yang kami tidak tahu diskriminasinya, tetapi sejauh pengetahuan saya, saya tidak tahu model atau perlakuan apa pun yang sebenarnya terhadap mereka. Dalam diskusi awal, bahkan serikat pekerja non-FFI menginginkan model "beberapa varian valid pada satu waktu", termasuk kasus penggunaan yang memotivasi untuk menginginkan serikat non-FFI sama sekali.

Menambahkan varian () ke sebuah serikat tidak akan mengubah apa pun, dan serikat tidak perlu melakukannya untuk mendapatkan semantik yang mereka harapkan. Serikat pekerja harus terus menjadi sekantong bit, dengan Rust tidak tahu apa yang mungkin terkandung di dalamnya pada waktu tertentu sampai kode yang tidak aman mengaksesnya.

Serikat FFI
menjadi "titik ekstensi" adalah sesuatu yang perlu didokumentasikan dengan baik
bagaimanapun.

Kita tentunya harus mendokumentasikan semantiknya setepat mungkin.

@RalfJung Tidak, ini berperilaku seperti auto trait s lakukan, memperlihatkan semua detil internal.

Saat ini ada beberapa diskusi seputar "bidang aktif" dan penurunan serikat pekerja di https://github.com/rust-lang/rust/issues/41073#issuecomment -380291471

Serikat pekerja harus terus menjadi sekantong bit, dengan Rust tidak tahu apa yang mungkin terkandung di dalamnya pada waktu tertentu sampai kode yang tidak aman mengaksesnya.

Ini persis seperti yang saya harapkan dari serikat pekerja. Mereka adalah fitur lanjutan untuk memeras kinerja ekstra dan berinteraksi dengan kode C, di mana tidak ada hal-hal seperti destruktor.

Bagi saya, jika Anda ingin menghapus konten sebuah serikat, Anda harus ~ harus melakukan cast / transmute (mungkin tidak dapat mentransmutasikan karena mungkin lebih besar dengan beberapa bit yang tidak digunakan di akhir untuk varian lain) ke tipe Anda ingin melepaskan ~ arahkan pointer ke bidang yang perlu dihapus dan gunakan std::ptr::drop_in_place , atau gunakan sintaks bidang untuk mengekstrak nilainya.

Jika saya tidak tahu apa-apa tentang serikat pekerja, inilah yang saya harapkan dari mereka:

Contoh - Mewakili mem::uninitialized sebagai serikat pekerja

pub union MaybeValid<T> {
    valid: T,
    invalid: ()
}

impl<T> MaybeValid<T> {
    #[inline] // this should optimize to a no-op
    pub fn from_valid(valid: T) -> MaybeValid<T> {
        MaybeValid { valid }
    }

    pub fn invalid() -> MaybeValid<T> {
        MaybeValid { invalid: () }
    }

   pub fn zeroed() -> MaybeValid<T> {
        // do whatever is necessary here...
        unimplemented!()
    }
}

fn example() {
    let valid_data = MaybeValid::from_valid(1_u8);
    // Destructor of a union always does nothing, but that's OK since our 
    // data type owns nothing.
    drop(valid_data);
    let invalid_data = MaybeValid::invalid();
    // Destructor of a union again does nothing, which means it needs to know 
    // nothing about its surroundings, and can't accidentally try to free unused memory.
    drop(invalid_data);
    let valid_data = MaybeValid::from_valid(String::from("test string"));
    // Now if we dropped `valid_data` we would leak memory, since the string 
    // would never get freed. This is already possible in safe rust using e.g. `Rc`. 
    // `union` is a similarly advanced feature to `Rc` and so new users are 
    // protected by the order in which concepts are introduced to them. This is 
    // still "safe" even though it leaks because it cannot trigger UB.
    //drop(valid_data)
    // Since we know that our union is of a particular form, we can safely 
    // move the value out, in order to run the destructor. I would expect this 
    // to fail if the drop method had run, even though the drop method does 
    // nothing, because that's the way stuff works in rust - once it's dropped
    // you can't use it.
    let _string_to_drop = unsafe { valid_data.valid };
    // No memory leak and all unsafety is encapsulated.
}

Saya akan memposting ini kemudian mengeditnya sehingga saya tidak kehilangan pekerjaan saya.
EDIT cara @SimonSapin untuk menjatuhkan bidang.

jika Anda ingin menghapus konten dari sebuah union, Anda harus melakukan cast / transmute (mungkin tidak dapat transmute karena mungkin lebih besar dengan beberapa bit yang tidak terpakai di akhir untuk varian lain) ke tipe yang ingin Anda drop , atau gunakan sintaks bidang untuk mengekstrak nilai

(Jika hanya untuk membiarkannya jatuh, tidak perlu mengekstrak nilai dalam arti memindahkannya, Anda dapat mengarahkan penunjuk ke salah satu bidang dan menggunakan std::ptr::drop_in_place .)

Terkait: Untuk konstanta, saat ini saya berpendapat bahwa setidaknya satu bidang gabungan di dalam konstanta harus benar: https://github.com/rust-lang/rust/pull/51361 (jika Anda memiliki bidang ZST yang selalu benar)

Saya akan memposting ini kemudian mengeditnya sehingga saya tidak kehilangan pekerjaan saya.

Harap perhatikan bahwa pengeditan tidak tercermin dalam pemberitahuan email. Jika Anda akan membuat perubahan signifikan pada komentar Anda, pertimbangkan untuk membuat komentar baru sebagai gantinya atau sebagai tambahan.

@derekdreery (dan semua orang) Saya tertarik dengan masukan Anda untuk https://internals.rust-lang.org/t/pre-rfc-unions-drop-types-and-manuallydrop/8025

Terkait: Untuk konstanta, saat ini saya berpendapat bahwa setidaknya satu bidang gabungan di dalam konstanta harus benar: # 51361

Saya telah melihat implementasinya tetapi tidak melihat argumennya. ;)

Yah ... argumen dengan "tidak memeriksa sama sekali sepertinya aneh".

Saya akan dengan senang hati menerapkan skema apa pun yang kami hasilkan di pemeriksa const, tetapi intuisi saya selalu adalah bahwa satu varian penyatuan harus sepenuhnya benar.

Jika tidak, serikat hanyalah cara yang bagus untuk menentukan tipe dengan ukuran dan penyelarasan tertentu dan beberapa kompilator menghasilkan kemudahan untuk mentransmutasi antara satu set tipe tetap.

Saya pikir serikat pekerja adalah "kantong bit yang tidak ditafsirkan" dengan beberapa cara mudah untuk mengaksesnya. Saya tidak melihat ada yang aneh sama sekali tentang tidak memeriksanya.

AFAIK sebenarnya ada beberapa kasus penggunaan @joshtriplett yang disebutkan di semua tangan Berlin di mana paruh pertama penyatuan cocok dengan satu bidang dan paruh kedua cocok dengan bidang lain.

Saya pikir serikat pekerja adalah "kantong bit yang tidak ditafsirkan" dengan beberapa cara mudah untuk mengaksesnya. Saya tidak melihat ada yang aneh sama sekali tentang tidak memeriksanya.

Saya selalu berpikir interpretasi ini agak bertentangan dengan semangat bahasa.
Di tempat lain kami menggunakan analisis statis untuk mencegah footgun, periksa apakah nilai yang tidak diinisialisasi atau dipinjam tidak diakses, tetapi untuk gabungan yang analisisnya tiba-tiba dinonaktifkan, silakan tembak.

Saya melihat bahwa persis seperti tujuan dari union . Maksud saya, kami juga memiliki petunjuk mentah di mana semua analisis dinonaktifkan. Serikat pekerja memberikan kontrol penuh atas tata letak data, seperti pointer mentah yang memberikan kontrol penuh atas akses memori. Keduanya mengorbankan keamanan.

Juga, ini membuat union sederhana . Saya pikir menjadi sederhana itu penting, dan bahkan lebih penting lagi ketika kode yang tidak aman terlibat (yang akan selalu terjadi pada serikat pekerja). Kami seharusnya hanya menerima kerumitan ekstra di sini jika itu memberikan manfaat yang nyata.

Kami tidak berpikir kami harus membayar biaya tersebut untuk serikat pekerja karena model bag-of-bits tidak memberikan peluang baru dibandingkan dengan model enum-with-unknown-variant.

Properti yang dipermasalahkan di sini setidaknya merupakan beban yang harus ditegakkan oleh kode yang tidak aman karena itu adalah pengamanan. Tidak ada analisis statis yang dapat mencegah semua kesalahan yang dapat merusak properti ini karena kami ingin menggunakan serikat untuk jenis yang tidak aman punning 1 , jadi "enum dengan varian yang tidak diketahui" berarti serikat yang menangani kode harus sangat berhati-hati dengan cara penulisannya ke serikat atau risiko UB instan, tanpa benar-benar mengurangi ketidakamanan yang terlibat dalam membaca dari serikat, karena membaca sudah membutuhkan pengetahuan (melalui saluran yang tidak dipahami oleh kompilator) bahwa bit tersebut valid untuk varian yang Anda baca. Kami hanya dapat benar-benar memperingatkan pengguna tentang penyatuan yang tidak valid untuk salah satu variannya adalah saat berjalan di bawah miri, bukan di sebagian besar kasus yang terjadi saat waktu proses.

1 Misalnya, dengan asumsi tupel adalah repr (C) untuk kesederhanaan, union Foo { a: (bool, u8), b: (u8, bool) } memungkinkan Anda untuk membuat sesuatu yang tidak valid hanya dengan tugas lapangan.

@tokopedia

serikat Foo {a: (bool, u8), b: (u8, bool)}

Hei, itu contoh saya :)
Dan itu valid di bawah model RFC 1897 (setidaknya satu dari fragmen "daun" bool -1, u8 -1, u8 -2, bool -2 berlaku setelah penugasan sebagian).

penanganan kode serikat pekerja harus sangat berhati-hati dengan bagaimana menulis ke serikat atau risiko UB instan

Itulah inti dari model RFC 1897, pemeriksaan statis memastikan bahwa tidak ada operasi yang aman (seperti penugasan atau penugasan parsial) yang dapat mengubah serikat menjadi tidak valid, jadi Anda tidak perlu selalu berhati-hati dan tidak mendapatkan UB instan .
Hanya operasi tidak aman yang tidak terkait dengan serikat pekerja seperti menulis melalui petunjuk liar yang dapat membuat serikat menjadi tidak valid.

Di sisi lain, tanpa pemeriksaan gerak, serikat pekerja dapat dengan mudah dimasukkan ke dalam keadaan tidak valid.

let u: Union;
let x = u.field; // UB

Itulah inti dari model RFC 1897, pemeriksaan statis memastikan bahwa tidak ada operasi yang aman (seperti penugasan atau penugasan parsial) yang dapat mengubah serikat menjadi tidak valid, jadi Anda tidak perlu selalu berhati-hati dan tidak mendapatkan UB instan .
Hanya operasi tidak aman yang tidak terkait dengan serikat pekerja seperti menulis melalui petunjuk liar yang dapat membuat serikat menjadi tidak valid.

Anda dapat secara otomatis mengenali beberapa jenis penulisan sebagai tidak melanggar invarian ekstra yang diberlakukan pada serikat pekerja, tetapi masih terdapat invarian tambahan yang perlu didukung oleh penulis. Karena membaca masih tidak aman dan memerlukan memastikan secara manual bahwa bit akan valid untuk varian yang dibaca, ini tidak benar-benar membantu pembaca, itu hanya membuat hidup penulis lebih sulit. Baik "bag of bits" maupun "enum with unknown variant" tidak ada yang membantu memecahkan masalah sulit dari serikat: bagaimana memastikannya benar-benar menyimpan jenis data yang ingin Anda baca.

Bagaimana pemeriksaan tipe yang lebih menarik memengaruhi Dropping? Jika Anda membuat serikat pekerja kemudian meneruskannya ke C, yang mengambil kepemilikan, akankah karat mencoba membebaskan data, mungkin menyebabkan bebas ganda? Atau apakah Anda selalu menerapkan Drop sendiri?

edit akan sangat keren jika serikat seperti "enum di mana varian diperiksa secara statis pada waktu kompilasi", jika saya mengerti sarannya

sunting 2 dapatkah serikat pekerja memulai sebagai sekantong bit dan kemudian memungkinkan akses yang aman sementara kompatibel dengan mundur?

Dan valid di bawah model RFC 1897 (setidaknya satu dari "leaf" fragmen bool-1, u8-1, u8-2, bool-2 valid setelah penetapan sebagian).

Jika kami memutuskan kami ingin ini berlaku, saya pikir @ oli-obk harus memperbarui pemeriksaan miri untuk mencerminkan bahwa - dengan https://github.com/rust-lang/rust/pull/51361 digabungkan, itu akan ditolak oleh miri.

@petrochenkov Bagian yang saya tidak mengerti adalah apa yang membeli kita. Kami mendapatkan kompleksitas ekstra, dalam hal implementasi (analisis statis) dan penggunaan (pengguna masih perlu mengetahui aturan yang tepat). Kompleksitas ekstra ini menambah fakta bahwa ketika serikat pekerja digunakan, kita sudah berada dalam konteks yang tidak aman sehingga segala sesuatunya secara alami menjadi lebih kompleks. Saya pikir kita harus memiliki motivasi yang jelas mengapa kerumitan ekstra ini sepadan. Saya tidak menganggap "itu agak melanggar semangat bahasa" sebagai motivasi yang jelas.

Satu hal yang dapat saya pikirkan adalah pengoptimalan tata letak. Dalam model "bag of bits", sebuah serikat tidak pernah memiliki ceruk. Namun, saya merasa itu adalah alamat yang lebih baik dengan memberi programmer lebih banyak kontrol manual atas ceruk, yang juga akan berguna dalam kasus lain .

Saya pikir saya kehilangan sesuatu yang mendasar di sini. Saya setuju dengan @rkruppe itu
masalah sulit dengan serikat pekerja adalah memastikan bahwa serikat pekerja saat ini menyimpan
data yang ingin dibaca oleh program.

Namun AFAIK masalah ini tidak dapat diselesaikan "secara lokal" dengan analisis statis. Kita
setidaknya tidak akan pernah menganalisis program secara keseluruhan, dan bahkan akan tetap demikian
masalah yang sulit dipecahkan.

Jadi ... apakah ada solusi untuk masalah ini di atas meja? Atau, apa artinya
solusi tepat yang diusulkan benar-benar membeli kami? Katakanlah saya mendapatkan penyatuan dari C,
tanpa menganalisis seluruh program Rust dan C, apa yang bisa diusulkan
analisis statis benar-benar menjamin bagi pembaca?

@gnzlbg Saya pikir satu-satunya jaminan yang kami dapatkan adalah apa yang ditulis @petrochenkov di atas

pemeriksaan statis memastikan bahwa tidak ada operasi yang aman (seperti penugasan atau penugasan sebagian) yang dapat mengubah serikat menjadi tidak valid

Di sisi lain, tanpa pemeriksaan gerak, serikat pekerja dapat dengan mudah dimasukkan ke dalam keadaan tidak valid.

Proposal Anda juga tidak melindungi dari pembacaan yang buruk, saya rasa itu tidak mungkin.

Juga, saya membayangkan beberapa pelacakan "diinisialisasi" yang sangat mendasar di sepanjang baris "menulis ke bidang apa pun menginisialisasi penyatuan". Kami tetap membutuhkan sesuatu jika impl Drop for MyUnion diizinkan. Baik atau buruk, kita harus memutuskan kapan dan di mana memasukkan panggilan drop otomatis untuk serikat pekerja. Aturan tersebut harus sesederhana mungkin karena ini adalah kode tambahan yang kita masukkan ke dalam kode tidak aman yang ada. Untuk serikat pekerja yang mengimplementasikan Drop , saya juga membayangkan batasan yang mirip dengan struct yang tidak mengizinkan penulisan ke bidang kecuali struktur datanya sudah diinisialisasi.

@tokopedia

dapatkah serikat pekerja dimulai sebagai sekantong bit dan kemudian memungkinkan akses yang aman sementara kompatibel dengan mundur?
Tidak. Setelah kami mengatakan itu sekantong bit, mungkin ada kode yang tidak aman dengan asumsi itu diizinkan.

Saya pikir ada nilai dalam pemeriksaan langkah minimal untuk melihat apakah serikat pekerja diinisialisasi. RFC asli secara eksplisit menetapkan bahwa menginisialisasi atau menetapkan ke bidang serikat mana pun membuat seluruh serikat diinisialisasi. Selain itu, rustc tidak boleh mencoba menyimpulkan apa pun tentang nilai dalam serikat yang tidak ditentukan secara eksplisit oleh pengguna; serikat mungkin berisi nilai sama sekali, termasuk nilai yang tidak valid untuk setiap bidang nya.

Salah satu kasus penggunaan untuk itu, misalnya: pertimbangkan gabungan tag gaya-C yang secara eksplisit dapat diperluas dengan lebih banyak tag di masa mendatang. C dan kode Rust membaca bahwa union tidak boleh berasumsi bahwa ia mengetahui setiap kemungkinan jenis bidang.

@Ralfian

Mungkin saya harus mulai dari arah lain.

Haruskah kode ini berfungsi 1) untuk serikat pekerja 2) untuk non-serikat pekerja?

let x: T;
let y = x.field;

Bagi saya jawabannya jelas "tidak" dalam kedua kasus, karena ini adalah seluruh kelas kesalahan yang dapat dan ingin dicegah Rust, terlepas dari "penyatuan" -ness T .

Ini berarti pemeriksa bergerak harus memiliki semacam skema yang sesuai dengan implementasi dukungan itu. Mengingat bahwa pemeriksa pemindahan (dan pemeriksa peminjam) umumnya bekerja dengan cara per bidang, skema paling sederhana untuk serikat adalah "aturan yang sama seperti untuk struct + (de) inisialisasi / meminjam bidang juga (de) menginisialisasi / meminjam bidang saudara kandungnya ".
Aturan sederhana ini mencakup semua pemeriksaan statis.

Kemudian, model enum hanyalah konsekuensi dari pemeriksaan statis yang dijelaskan di atas + satu kondisi lagi.
Jika 1) pemeriksaan inisialisasi diaktifkan dan 2) kode tidak aman tidak menulis byte tidak valid yang sewenang-wenang ke dalam area milik serikat, maka salah satu bidang "daun" serikat secara otomatis valid. Ini adalah jaminan dinamis yang tidak dapat dicentang (setidaknya untuk serikat dengan> 1 kolom dan di luar const-evaluator), tetapi ditargetkan pada orang yang membaca kode terlebih dahulu.

Kasus ini dari @joshtriplett , misalnya

Salah satu kasus penggunaan untuk itu, misalnya: pertimbangkan gabungan tag gaya-C yang secara eksplisit dapat diperluas dengan lebih banyak tag di masa mendatang. C dan kode Rust membaca bahwa union tidak boleh berasumsi bahwa ia mengetahui setiap kemungkinan jenis bidang.

akan jauh lebih jelas bagi orang-orang yang membaca kode jika serikat pekerja secara eksplisit memiliki bidang ekstra untuk "kemungkinan ekstensi di masa mendatang".

Tentu saja, kami dapat menyimpan pemeriksaan inisialisasi statis dasar, tetapi menolak kondisi kedua dan mengizinkan penulisan data yang mungkin tidak valid secara arbitrer ke serikat melalui beberapa cara "pihak ketiga" yang tidak aman tanpa menjadikannya UB instan. Maka kami tidak akan memiliki jaminan yang ditargetkan untuk orang yang dinamis itu lagi, saya hanya berpikir itu akan menjadi kerugian bersih.

@tokopedia

Haruskah kode ini berfungsi 1) untuk serikat pekerja 2) untuk non-serikat pekerja?

let x: T;
let y = x.field;

Bagi saya jawabannya jelas "tidak" dalam kedua kasus, karena ini adalah seluruh kelas kesalahan yang dapat dan ingin dicegah Rust, terlepas dari "penyatuan" -ness T .

Setuju, tingkat pemeriksaan untuk nilai yang tidak diinisialisasi ini tampaknya masuk akal, dan cukup layak.

Ini berarti pemeriksa bergerak harus memiliki semacam skema yang sesuai dengan implementasi dukungan itu. Mengingat bahwa pemeriksa pemindahan (dan pemeriksa peminjam) umumnya bekerja dengan cara per bidang, skema paling sederhana untuk serikat adalah "aturan yang sama seperti untuk struct + (de) inisialisasi / meminjam bidang juga (de) menginisialisasi / meminjam bidang saudara kandungnya ".
Aturan sederhana ini mencakup semua pemeriksaan statis.

Setuju sejauh ini, dengan asumsi saya memahami aturan untuk struct.

Kemudian, model enum hanyalah konsekuensi dari pemeriksaan statis yang dijelaskan di atas + satu kondisi lagi.
Jika 1) pemeriksaan inisialisasi diaktifkan dan 2) kode tidak aman tidak menulis byte tidak valid yang sewenang-wenang ke dalam area milik serikat, maka salah satu bidang "daun" serikat secara otomatis valid. Ini adalah jaminan dinamis yang tidak dapat dicentang (setidaknya untuk serikat dengan> 1 kolom dan di luar const-evaluator), tetapi ditargetkan pada orang yang membaca kode terlebih dahulu.

Ketentuan tambahan tersebut tidak valid untuk serikat pekerja.

Kasus ini dari @joshtriplett , misalnya

Salah satu kasus penggunaan untuk itu, misalnya: pertimbangkan gabungan tag gaya-C yang secara eksplisit dapat diperluas dengan lebih banyak tag di masa mendatang. C dan kode Rust membaca bahwa union tidak boleh berasumsi bahwa ia mengetahui setiap kemungkinan jenis bidang.

akan jauh lebih jelas bagi orang-orang yang membaca kode jika serikat pekerja secara eksplisit memiliki bidang ekstra untuk "kemungkinan ekstensi di masa mendatang".

Itu bukanlah cara kerja serikat C, atau cara serikat Rust ditentukan untuk bekerja. (Dan saya akan mempertanyakan apakah itu akan lebih jelas, atau hanya apakah itu cocok dengan ekspektasi yang berbeda.) Mengubah ini akan membuat serikat Rust tidak lagi cocok untuk beberapa tujuan yang mereka rancang dan usulkan.

Tentu saja, kami dapat menyimpan pemeriksaan inisialisasi statis dasar, tetapi menolak kondisi kedua dan mengizinkan penulisan data yang mungkin tidak valid secara arbitrer ke serikat melalui beberapa cara "pihak ketiga" yang tidak aman tanpa menjadikannya UB instan. Maka kami tidak akan memiliki jaminan yang ditargetkan untuk orang yang dinamis itu lagi, saya hanya berpikir itu akan menjadi kerugian bersih.

Artinya 'pihak ketiga "yang tidak aman' termasuk" mendapatkan serikat dari FFI ", yang merupakan kasus penggunaan yang sepenuhnya valid.

Inilah contoh konkretnya:

union Event {
    event_id: u32,
    event1: Event1,
    event2: Event2,
    event3: Event3,
}

struct Event1 {
    event_id: u32, // always EVENT1
    // ... more fields ...
}
// ... more event structs ...

match u.event_id {
    EVENT1 => { /* ... */ }
    EVENT2 => { /* ... */ }
    EVENT3 => { /* ... */ }
    _ => { /* unknown event */ }
}

Itu adalah kode yang benar-benar valid yang dapat dan akan ditulis orang menggunakan serikat.

@tokopedia

Haruskah kode ini berfungsi 1) untuk serikat pekerja 2) untuk non-serikat pekerja?
Bagi saya jawabannya jelas "tidak" dalam kedua kasus, karena ini adalah seluruh kelas kesalahan yang dapat dan ingin dicegah Rust, terlepas dari "penyatuan" -ness dari T.

Baik untukku.

Skema paling sederhana untuk serikat adalah "aturan yang sama seperti untuk struct + (de) inisialisasi / meminjam bidang juga (de) menginisialisasi / meminjam bidang saudaranya".

Wow. Aturan struct masuk akal karena semuanya didasarkan pada fakta bahwa bidang yang

Jika 1) pemeriksaan inisialisasi diaktifkan dan 2) kode tidak aman tidak menulis byte tidak valid yang sewenang-wenang ke dalam area milik serikat, maka salah satu bidang "daun" serikat secara otomatis valid. Ini adalah jaminan dinamis yang tidak dapat dicentang (setidaknya untuk serikat dengan> 1 kolom dan di luar const-evaluator), tetapi ditargetkan pada orang yang membaca kode terlebih dahulu.

Saya pikir itu sangat diinginkan untuk asumsi validitas dasar untuk diperiksa secara dinamis (informasi jenis yang diberikan). Kemudian kita dapat memeriksanya selama CTFE di miri, kita bahkan dapat memeriksanya selama menjalankan "penuh" miri (misalnya dari rangkaian pengujian), pada akhirnya kita dapat memiliki semacam pembersih atau mungkin mode di mana Rust mengeluarkan debug_assert! di tempat-tempat kritis untuk memeriksa invarian validitas.
Saya pikir pengalaman dengan aturan C yang tidak dapat dicentang memberikan banyak bukti bahwa ini bermasalah. Biasanya, langkah pertama untuk benar-benar memahami dan mengklarifikasi apa aturannya adalah menemukan cara yang dapat dicentang secara dinamis untuk mengekspresikannya. Bahkan untuk model memori konkurensi, varian yang "dapat diperiksa secara dinamis" (semantik operasional yang menjelaskan segala sesuatu dalam hal eksekusi langkah demi langkah dari mesin virtual) muncul dan tampaknya menjadi satu-satunya cara untuk memecahkan masalah terbuka yang sudah berlangsung lama dari aksiomatik model yang sebelumnya digunakan ("ouf of thin air problem" adalah kata kunci di sini).

Saya tidak bisa melebih-lebihkan betapa pentingnya menurut saya memiliki aturan yang dapat diperiksa secara dinamis. Saya pikir kita harus menargetkan 0 kasus UB yang tidak dapat diperiksa. (Kami belum sampai, tapi itu adalah tujuan yang harus kami miliki.) Itulah satu-satunya cara yang bertanggung jawab untuk memiliki UB dalam bahasa Anda, yang lainnya adalah kasus compiler / penulis bahasa membuat hidup mereka lebih mudah dengan mengorbankan semua orang yang harus hidup dengan konsekuensinya. (Saat ini saya sedang mengerjakan aturan yang dapat diperiksa secara dinamis untuk aliasing dan akses pointer mentah.)
Bahkan jika itu akan menjadi satu-satunya masalah, sejauh yang saya ketahui "tidak dapat diperiksa secara dinamis" adalah alasan yang cukup untuk tidak menggunakan pendekatan ini.

Karena itu, saya tidak melihat alasan mendasar mengapa ini tidak boleh dicentang: Untuk setiap byte dalam penyatuan, periksa semua varian untuk melihat nilai mana yang diizinkan untuk byte itu dalam varian ini, dan ambil penyatuan (heh;)) dari semua dari set tersebut. Urutan byte valid untuk penyatuan jika setiap byte valid menurut definisi ini.
Ini, bagaimanapun, cukup sulit untuk benar-benar menerapkan pemeriksaan - sejauh ini invarian validitas tipe dasar paling kompleks yang akan kita miliki di Rust. Itu adalah konsekuensi langsung dari fakta bahwa aturan validitas ini agak sulit untuk dijelaskan, itulah mengapa saya tidak menyukainya.

Tentu saja, kami dapat menyimpan pemeriksaan inisialisasi statis dasar, tetapi menolak kondisi kedua dan mengizinkan penulisan data yang mungkin tidak valid secara arbitrer ke serikat melalui beberapa cara "pihak ketiga" yang tidak aman tanpa menjadikannya UB instan. Maka kami tidak akan memiliki jaminan yang ditargetkan untuk orang yang dinamis itu lagi, saya hanya berpikir itu akan menjadi kerugian bersih.

Apa jaminan itu membeli kita ? Di mana sebenarnya itu membantu? Saat ini, yang saya lihat adalah setiap orang harus bekerja keras dan berhati-hati untuk menegakkannya. Saya tidak melihat manfaat yang kita, orang-orang, dapatkan dari itu.

@bayu_joo

pertimbangkan gabungan tag gaya-C yang secara eksplisit dapat diperluas dengan lebih banyak tag di masa mendatang. C dan kode Rust membaca bahwa union tidak boleh berasumsi bahwa ia mengetahui setiap kemungkinan jenis bidang.

Model yang diusulkan oleh @petrochenkov memungkinkan kasus penggunaan tersebut, dengan menambahkan bidang __non_exhaustive: () ke serikat pekerja. Namun, saya rasa itu tidak perlu. Dapat dibayangkan, generator yang mengikat dapat menambahkan bidang seperti itu.

@Ralfian

Ini dinamis tidak dapat dicentang (setidaknya untuk serikat dengan> 1 bidang dan di luar const-evaluator) jaminan

Saya pikir sangat diinginkan agar asumsi validitas dasar dapat diperiksa secara dinamis

Klarifikasi: Maksud saya tidak dapat dicentang di "secara default" / "dalam mode rilis", tentu saja ini dapat diperiksa dalam "mode lambat" dengan beberapa instrumentasi tambahan, tetapi Anda sudah menulis tentang ini lebih baik daripada yang saya bisa.

@Ralfian

Model yang diusulkan oleh @petrochenkov memungkinkan kasus penggunaan tersebut, dengan menambahkan bidang __non_exhaustive: () ke serikat.

Ya, saya mengerti bahwa itu adalah lamaran.

Namun, saya rasa itu tidak perlu. Dapat dibayangkan, generator yang mengikat dapat menambahkan bidang seperti itu.

Mereka bisa, tapi mereka harus menambahkannya secara sistematis ke setiap serikat pekerja.

Saya belum melihat argumen mengapa masuk akal untuk mendobrak kasus penggunaan utama serikat pekerja untuk mendukung beberapa kasus penggunaan yang tidak ditentukan yang bergantung pada pembatasan pola bit apa yang dapat dikandungnya.

@bayu_joo

kasus penggunaan utama serikat

Sama sekali tidak jelas bagi saya mengapa ini adalah kasus penggunaan utama.
Mungkin benar untuk repr(C) serikat pekerja jika Anda berasumsi bahwa semua penggunaan serikat untuk serikat yang diberi tag / "Rust enum emulation" di FFI mengasumsikan kemampuan diperpanjang (yang tidak benar), tetapi dari apa yang saya lihat, penggunaan repr(Rust) unions (kontrol drop, kontrol inisialisasi, transmutasi) tidak mengharapkan "varian yang tidak diharapkan" tiba-tiba muncul di dalamnya.

@petrochenkov Saya tidak mengatakan "mematahkan kasus penggunaan utama", aku berkata "memecahkan kasus penggunaan utama". FFI adalah salah satu kasus penggunaan utama serikat pekerja.

dan ambil gabungan (heh;)) dari semua set tersebut

Jelas ada kejelasan yang menarik untuk pernyataan bahwa "nilai yang mungkin dari suatu persatuan adalah penyatuan nilai yang mungkin dari semua varian yang mungkin" ...

Benar. Namun, itu bukan proposal - kami semua setuju bahwa yang berikut ini harus legal:

union F {
  x: (u8, bool),
  y: (bool, u8),
}
fn foo() -> F {
  let mut f = F { x: (5, false) };
  unsafe { f.y.1 = 17; }
  f
}

Sebenarnya saya pikir ini adalah bug yang bahkan membutuhkan unsafe .

Jadi, setidaknya serikat harus diambil dengan cara yang sama.
Juga, saya tidak berpikir "kejelasan yang menarik" dengan sendirinya adalah alasan yang cukup bagus. Setiap invarian yang kita putuskan merupakan beban yang signifikan bagi pembuat kode yang tidak aman, kita harus memiliki keuntungan konkret yang kita dapatkan pada gilirannya.

@Ralfian

Sebenarnya saya pikir ini adalah bug yang bahkan membutuhkannya tidak aman.

Saya tidak tahu tentang implementasi pemeriksa tidak aman berbasis MIR yang baru, tetapi dalam penerapan pemeriksa ketidakamanan berbasis HIR yang lama, hal itu tentu saja merupakan batasan / penyederhanaan pemeriksa - hanya ekspresi dalam bentuk expr1.field = expr2 yang dianalisis untuk kemungkinan "bidang tugas "penyisihan tidak aman, semua hal lainnya secara konservatif diperlakukan sebagai" akses lapangan "umum yang tidak aman untuk serikat pekerja.

Menjawab komentar di https://github.com/rust-lang/rust/issues/52786#issuecomment -408645420:

Jadi idenya adalah bahwa kompilator masih tidak tahu apa-apa tentang kontrak Wrap<T> dan misalnya tidak dapat melakukan optimasi layout. Oke, posisi ini dipahami.
Ini berarti bahwa secara internal, di dalam modul Wrap , implementasi modul Wrap<T> dapat, misalnya, untuk sementara menulis "nilai tak terduga" ke dalamnya, jika tidak membocorkannya ke pengguna, dan kompiler akan baik-baik saja dengan mereka.

Saya tidak yakin bagaimana tepatnya bagian dari kontrak Wrap s tentang tidak adanya nilai yang tidak diharapkan terkait dengan privasi bidang.

Pertama-tama, terlepas dari bidang pribadi atau publik, nilai tak terduga tidak dapat ditulis langsung melalui bidang tersebut. Anda memerlukan sesuatu seperti penunjuk mentah, atau kode di sisi lain FFI untuk melakukannya, dan ini dapat dilakukan tanpa akses bidang apa pun, hanya dengan memiliki penunjuk ke seluruh gabungan. Jadi kita perlu melakukan pendekatan ini dari beberapa arah selain akses ke bidang yang dibatasi.

Saat saya menafsirkan komentar Anda, pendekatannya adalah dengan mengatakan bahwa bidang pribadi (dalam serikat atau struct, tidak masalah) menyiratkan invarian sewenang-wenang yang tidak diketahui pengguna, jadi setiap operasi yang mengubah bidang itu (secara langsung atau melalui petunjuk liar, tidak ' t matter) mengakibatkan UB karena berpotensi memecah invarian yang tidak ditentukan tersebut.

Ini berarti bahwa jika sebuah serikat memiliki satu bidang pribadi, maka pelaksananya (tetapi bukan kompiler) dapat berasumsi bahwa tidak ada pihak ketiga yang akan menulis nilai yang tidak diharapkan ke dalam serikat tersebut.
Itu adalah "klausul dokumentasi union default" untuk pengguna dalam beberapa hal:
- (Default) Jika serikat memiliki bidang pribadi, Anda tidak dapat menulis sampah ke dalamnya.
- Jika tidak, Anda dapat menulis sampah ke dalam serikat pekerja kecuali jika secara eksplisit dilarang oleh dokumennya.

Jika beberapa serikat pekerja ingin melarang nilai yang tidak diharapkan sambil tetap memberikan pub akses ke bidang yang diharapkan (misalnya ketika bidang tersebut tidak memiliki invariannya sendiri), maka serikat masih dapat melakukannya melalui dokumentasi, itulah mengapa "kecuali" di klausa kedua diperlukan.

@Ralfian
Apakah ini menggambarkan posisi Anda secara akurat?

Bagaimana skenario seperti ini diperlakukan?

mod m {
    union MyPrivateUnion { /* private fields */ }
    extern {
        fn my_private_ffi_function() -> MyPrivateUnion; // Can return garbage (?)
    }
}

Saat saya menafsirkan komentar Anda, pendekatannya adalah dengan mengatakan bahwa bidang pribadi (dalam serikat atau struct, tidak masalah) menyiratkan invarian sewenang-wenang yang tidak diketahui pengguna, jadi setiap operasi yang mengubah bidang itu (secara langsung atau melalui petunjuk liar, tidak ' t matter) mengakibatkan UB karena berpotensi memecah invarian yang tidak ditentukan tersebut.

Tidak, bukan itu yang saya maksud.

Ada beberapa invarian. Saya tidak tahu berapa banyak yang kami butuhkan, tetapi setidaknya akan ada dua (dan saya tidak memiliki nama yang bagus untuk mereka):

  • "Layout-level invariant" (atau "syntactic invariant") dari suatu tipe sepenuhnya ditentukan oleh bentuk sintaksis dari tipe tersebut. Ini adalah hal-hal seperti " &mut T adalah non-NULL dan sejajar", " bool adalah 0 atau 1 ", " ! tidak dapat ada". Pada level ini, *mut T sama dengan usize - keduanya memungkinkan nilai apa pun (atau mungkin nilai apa pun yang diinisialisasi , tetapi perbedaan itu untuk diskusi lain). Kami, pada akhirnya, akan memiliki dokumen yang menguraikan invarian ini untuk semua jenis, dengan rekursi struktural: Invarian tingkat tata letak dari struct adalah bahwa semua bidangnya memiliki invariannya dipertahankan, dll. Visibilitas tidak berperan di sini.
Violating the layout-level invariant is instantaneous UB. This is a statement we can make because we have defined this invariant in very simple terms, and we make it part of the definition of the language itself. We can then exploit this UB (and we already do), e.g. to perform enum layout optimizations.
  • "Invarian tingkat jenis kustom" (atau "invarian semantik") dari suatu jenis dipilih oleh siapa pun yang mengimplementasikan jenis tersebut. Kompilator tidak dapat mengetahui invarian ini karena kami tidak memiliki bahasa untuk mengekspresikannya, dan hal yang sama berlaku untuk definisi bahasa. Kita tidak bisa membuat melanggar UB yang invarian ini, karena kita bahkan tidak bisa mengatakan apa itu invarian! Fakta bahwa bahkan dimungkinkan untuk memiliki invarian khusus adalah fitur dari sistem jenis apa pun yang berguna: Abstraksi. Saya menulis lebih banyak tentang ini di posting blog sebelumnya .

    Hubungan antara custom, semantic invariant dan UB adalah bahwa kami menyatakan bahwa unsafe code mungkin mengandalkan invarian semantic-nya yang disimpan oleh kode asing . Itu membuatnya tidak benar untuk langsung saja memasukkan barang acak ke dalam bidang ukuran Vec . Perhatikan bahwa saya mengatakan salah (terkadang saya menggunakan istilah tidak sehat ) - tetapi bukan perilaku yang diskusi tentang aturan aliasing untuk &mut ZST . Membuat sejajar yang menggantung dengan baik bukan-null &mut ZST tidak pernah langsung UB, tetapi masih salah / tidak sehat karena seseorang dapat menulis kode yang tidak aman yang bergantung pada hal ini agar tidak terjadi.

Alangkah baiknya untuk menyelaraskan kedua konsep ini, tapi menurut saya tidak praktis. Pertama-tama, untuk beberapa tipe (function pointers, dyn traits), definisi custom, semantic invariant sebenarnya menggunakan definisi UB dalam bahasa tersebut. Definisi ini akan melingkar jika kita ingin mengatakan bahwa UB pernah melanggar kebiasaan, semantic invariant. Kedua, saya lebih suka jika definisi bahasa kita, dan apakah jejak eksekusi tertentu menunjukkan UB, adalah properti yang dapat ditentukan. Semantic, custom invariants seringkali tidak dapat diputuskan.


Saya tidak yakin bagaimana tepatnya bagian dari kontrak Wraps tentang tidak adanya nilai yang tidak terduga terkait dengan privasi lapangan.

Pada dasarnya, ketika suatu tipe memilih invarian khusus, ia harus memastikan bahwa apa pun yang dapat dilakukan oleh kode aman akan mempertahankan invarian tersebut . Bagaimanapun, janji adalah bahwa hanya menggunakan API aman jenis ini tidak akan pernah mengarah ke UB. Ini berlaku untuk struct dan unions. Salah satu hal yang dapat dilakukan kode aman adalah mengakses bidang publik, dari sinilah koneksi ini berasal.

Misalnya, bidang publik dari sebuah struct tidak boleh memiliki invarian khusus yang berbeda dari invarian khusus dari jenis bidang : Bagaimanapun, setiap pengguna yang aman dapat menulis data sewenang-wenang ke dalam bidang itu, atau membaca formulir bidang dan mengharapkan "baik" data. Sebuah struct di mana semua bidang bersifat publik dapat dibangun dengan aman, menempatkan batasan lebih lanjut pada bidang tersebut.

Persatuan dengan bidang publik ... yah itu agak menarik. Membaca bidang persatuan juga tidak aman, jadi tidak ada yang berubah di sana. Menulis kolom gabungan aman, jadi gabungan dengan kolom publik harus mampu menangani data arbitrer yang memenuhi invarian khusus jenis kolom tersebut yang dimasukkan ke kolom. Saya ragu ini akan sangat berguna ...

Jadi, untuk rekap, ketika Anda memilih invarian khusus, itu adalah tanggung jawab Anda untuk memastikan bahwa kode keamanan asing tidak dapat merusak invarian ini (dan Anda memiliki alat seperti bidang pribadi untuk membantu Anda mencapai ini). Merupakan tanggung jawab kode asing yang tidak aman untuk tidak melanggar invarian Anda ketika kode tersebut melakukan sesuatu yang tidak dapat dilakukan oleh kode aman.


Artinya secara internal, di dalam modul Wrap, implementasi Wrapmodule dapat, misalnya, untuk sementara menulis "nilai yang tidak diharapkan" ke dalamnya, jika modul tidak membocorkannya ke pengguna, dan compiler akan baik-baik saja dengan mereka.

Benar. (keamanan panik menjadi perhatian di sini tetapi Anda mungkin menyadarinya). Ini seperti, dalam Vec , saya dapat melakukannya dengan aman

let sz = self.size;
self.size = 1337;
self.size = sz;

dan tidak ada UB.


mod m {
    union MyPrivateUnion { /* private fields */ }
    extern {
        fn my_private_ffi_function() -> MyPrivateUnion; // Can return garbage (?)
    }
}

Dalam hal invarian tata letak sintaksis, my_private_ffi_function dapat melakukan apa saja (dengan asumsi panggilan fungsi ABI dan tanda tangan cocok). Dalam hal invarian khusus semantik, itu tidak terlihat dalam kode - siapa pun yang menulis modul ini memiliki invarian dalam pikirannya, mereka harus mendokumentasikannya di samping definisi gabungan mereka dan kemudian memastikan bahwa fungsi FFI mengembalikan nilai yang memenuhi invarian .

Saya akhirnya menulis posting blog tentang apakah dan kapan &mut T harus diinisialisasi, dan dua jenis invarian yang saya sebutkan di atas.

Apakah ada yang tersisa untuk dilacak di sini yang belum dicakup oleh https://github.com/rust-lang/rust/issues/55149 , atau haruskah kita menutupnya?

E0658 masih menunjuk di sini:

kesalahan [E0658]: serikat dengan bidang non- Copy tidak stabil (lihat masalah # 32836)

Ini saat ini bermain buruk dengan atomics, karena mereka tidak menerapkan Copy . Apakah ada yang tahu solusinya?

Saat https://github.com/rust-lang/rust/issues/55149 diimplementasikan, Anda akan dapat menggunakan ManuallyDrop<AtomicFoo> dalam sebuah serikat pekerja. Sampai saat itu, satu-satunya solusi adalah menggunakan Nightly (atau tidak menggunakan union dan mencari alternatif lain).

Dengan menerapkannya, Anda bahkan tidak perlu ManuallyDrop ; setelah semua rustc tahu bahwa Atomic* tidak mengimplementasikan Drop .

Menugaskan diri saya sendiri untuk mengalihkan masalah pelacakan ke yang baru.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat