Bagaimana cara Rust menyimpang dari fasilitas konkurensi C ++?

35

Pertanyaan

Saya mencoba untuk memahami apakah Rust secara mendasar dan cukup meningkatkan fasilitas konkurensi C ++ sehingga untuk memutuskan apakah saya harus menghabiskan waktu untuk belajar Rust.

Secara khusus, bagaimana peningkatan Rust idiomatik pada, atau pada tingkat apa pun berbeda dari, fasilitas konkurensi dari idiomatik C ++?

Apakah peningkatan (atau divergensi) sebagian besar bersifat sintaksis, atau apakah secara substansial merupakan peningkatan (divergensi) dalam paradigma? Atau itu sesuatu yang lain? Atau bukankah itu benar-benar perbaikan (divergensi) sama sekali?


Alasan

Saya baru-baru ini mencoba untuk belajar sendiri fasilitas konkurensi C ++ 14, dan sesuatu terasa kurang tepat. Sesuatu terasa aneh. Apa yang terasa aneh? Sulit untuk dikatakan.

Rasanya seolah-olah kompiler tidak benar-benar berusaha membantu saya untuk menulis program yang benar ketika datang ke concurrency. Rasanya hampir seolah-olah saya menggunakan assembler daripada compiler.

Harus diakui, sangat mungkin bahwa saya menderita konsep yang halus dan salah dalam hal konkurensi. Mungkin saya belum merasakan ketegangan Bartosz Milewski antara pemrograman stateful dan data races. Mungkin saya tidak begitu mengerti berapa banyak metodologi bersamaan suara dalam kompiler dan berapa banyak itu dalam OS.

thb
sumber

Jawaban:

56

Kisah konkurensi yang lebih baik adalah salah satu tujuan utama proyek Rust, jadi perbaikan harus diharapkan, asalkan kami memercayai proyek untuk mencapai tujuannya. Penafian penuh: Saya memiliki pendapat tinggi tentang Rust dan diinvestasikan di dalamnya. Seperti yang diminta, saya akan mencoba untuk menghindari penilaian nilai dan menjelaskan perbedaan daripada peningkatan (IMHO) .

Karat yang aman dan tidak aman

"Rust" terdiri dari dua bahasa: Satu yang berusaha sangat keras untuk mengisolasi Anda dari bahaya pemrograman sistem, dan yang lebih kuat tanpa aspirasi semacam itu.

Unsafe Rust adalah bahasa kasar, brutal yang terasa sangat mirip C ++. Hal ini memungkinkan Anda untuk melakukan hal-hal berbahaya yang sewenang-wenang, berbicara dengan perangkat keras, (mis-) mengelola memori secara manual, menembak diri sendiri, dll. Ini sangat mirip dengan C dan C ++ karena kebenaran program pada akhirnya ada di tangan Anda. dan tangan semua programmer lain yang terlibat di dalamnya. Anda memilih bahasa ini dengan kata kunci unsafe, dan seperti dalam C dan C ++, satu kesalahan di satu lokasi dapat membuat seluruh proyek hancur.

Safe Rust adalah "default", sebagian besar kode Rust aman, dan jika Anda tidak pernah menyebutkan kata kunci unsafedalam kode Anda, Anda tidak pernah meninggalkan bahasa yang aman. Sisa posting sebagian besar akan menyangkut dirinya sendiri dengan bahasa itu, karena unsafekode dapat mematahkan setiap dan semua jaminan bahwa Rust bekerja sangat keras untuk memberi Anda. Di sisi lain, unsafekode tidak jahat dan tidak diperlakukan seperti itu oleh komunitas (namun, sangat tidak dianjurkan bila tidak diperlukan).

Ini berbahaya, ya, tetapi juga penting, karena memungkinkan membangun abstraksi yang menggunakan kode aman. Kode tidak aman yang baik menggunakan sistem tipe untuk mencegah orang lain menyalahgunakannya, dan oleh karena itu keberadaan kode tidak aman dalam program Rust tidak perlu mengganggu kode aman. Semua perbedaan berikut ada karena sistem tipe Rust memiliki alat yang tidak dimiliki C ++, dan karena kode tidak aman yang mengimplementasikan abstraksi konkurensi menggunakan alat ini secara efektif.

Non-perbedaan: Memori bersama / bisa berubah

Meskipun Rust lebih menekankan pada pengiriman pesan dan sangat ketat mengontrol memori bersama, itu tidak mengesampingkan concurrency memori bersama dan secara eksplisit mendukung abstraksi umum (kunci, operasi atom, variabel kondisi, koleksi bersamaan).

Selain itu, seperti C ++ dan tidak seperti bahasa fungsional, Rust sangat menyukai struktur data imperatif tradisional. Tidak ada daftar tertaut terus-menerus / tidak dapat diubah di perpustakaan standar. Ada std::collections::LinkedListtapi seperti std::listdi C ++ dan berkecil hati untuk alasan yang sama seperti std::list(penggunaan cache yang buruk).

Namun, dengan merujuk pada judul bagian ini ("memori bersama / bisa berubah"), Rust memiliki satu perbedaan dengan C ++: Sangat mendorong memori itu "dibagi-pakai XOR bersama", yaitu, bahwa memori tidak pernah dibagi dan dapat diubah pada saat yang sama. waktu. Mutasi memori yang Anda inginkan "dalam privasi utas Anda sendiri", jadi untuk berbicara. Bandingkan ini dengan C ++ di mana memori yang dapat dibagikan bersama adalah opsi default dan banyak digunakan.

Sementara paradigma shared-xor-bisa berubah sangat penting untuk perbedaan di bawah ini, itu juga merupakan paradigma pemrograman yang sangat berbeda yang membutuhkan waktu untuk terbiasa, dan yang menempatkan batasan yang signifikan. Kadang-kadang seseorang harus memilih keluar dari paradigma ini, misalnya, dengan tipe atomik ( AtomicUsizeadalah inti dari memori yang dapat ditukar bersama). Perhatikan bahwa kunci juga mematuhi aturan shared-xor-bisa berubah, karena aturan ini secara bersamaan membaca dan menulis (sementara satu utas menulis, tidak ada utas lain yang dapat membaca atau menulis).

Non-perbedaan: Ras data adalah perilaku yang tidak terdefinisi (UB)

Jika Anda memicu perlombaan data dalam kode Rust, permainan berakhir, seperti halnya di C ++. Semua taruhan dimatikan dan kompiler dapat melakukan apapun yang diinginkan.

Namun, itu adalah jaminan sulit bahwa kode Rust yang aman tidak memiliki ras data (atau UB dalam hal ini). Ini meluas ke bahasa inti dan ke perpustakaan standar. Jika Anda dapat menulis program Rust yang tidak menggunakan unsafe(termasuk di perpustakaan pihak ketiga tetapi tidak termasuk perpustakaan standar) yang memicu UB, maka itu dianggap sebagai bug dan akan diperbaiki (ini sudah terjadi beberapa kali). Hal ini tentu saja sangat kontras dengan C ++, di mana sepele untuk menulis program dengan UB.

Perbedaan: Disiplin penguncian yang ketat

Tidak seperti C ++, kunci di Rust ( std::sync::Mutex, std::sync::RwLock, dll) memiliki data itu melindungi. Alih-alih mengambil kunci dan kemudian memanipulasi beberapa memori bersama yang terkait dengan kunci hanya dalam dokumentasi, data bersama tidak dapat diakses saat Anda tidak memegang kunci. Seorang penjaga RAII menjaga kunci dan secara bersamaan memberikan akses ke data yang terkunci (banyak ini dapat diimplementasikan oleh C ++, tetapi tidak dengan std::kunci). Sistem seumur hidup memastikan bahwa Anda tidak dapat terus mengakses data setelah Anda melepaskan kunci (jatuhkan penjaga RAII).

Tentu saja Anda dapat memiliki kunci yang tidak berisi data berguna ( Mutex<()>), dan hanya berbagi beberapa memori tanpa secara eksplisit mengaitkannya dengan kunci itu. Namun, memiliki memori bersama yang berpotensi tidak disinkronkan memerlukan unsafe.

Perbedaan: Pencegahan pembagian yang tidak disengaja

Meskipun Anda dapat berbagi memori, Anda hanya berbagi ketika Anda secara eksplisit memintanya. Misalnya, ketika Anda menggunakan pesan yang lewat (misalnya saluran dari std::sync), sistem seumur hidup memastikan bahwa Anda tidak menyimpan referensi ke data setelah Anda mengirimnya ke utas lain. Untuk berbagi data di balik kunci, Anda membuat kunci secara eksplisit dan memberikannya ke utas lainnya. Untuk berbagi memori yang tidak disinkronkan dengan unsafeAnda, Anda harus menggunakan unsafe.

Ini mengikat ke poin berikutnya:

Perbedaan: pelacakan thread-safety

Sistem tipe Rust melacak beberapa gagasan tentang keamanan benang. Secara khusus, Syncsifat menunjukkan tipe yang dapat dibagikan oleh beberapa utas tanpa risiko ras data, sementara Sendmenandai sifat yang dapat dipindahkan dari satu utas ke yang lain. Ini diberlakukan oleh kompiler sepanjang program, dan dengan demikian perancang perpustakaan berani membuat optimasi yang akan sangat berbahaya tanpa pemeriksaan statis ini. Misalnya, C ++ std::shared_ptryang selalu menggunakan operasi atom untuk memanipulasi jumlah referensi, untuk menghindari UB jika shared_ptrkebetulan digunakan oleh beberapa utas. Rust memiliki Rcdan Arc, yang hanya berbeda dalam yang Rc menggunakan operasi refcount non-atom dan tidak aman untuk thread (yaitu tidak mengimplementasikan Syncatau Send) sementara Arcsangat miripshared_ptr (dan mengimplementasikan kedua sifat).

Perhatikan bahwa jika suatu tipe tidak digunakan unsafeuntuk mengimplementasikan sinkronisasi secara manual, ada atau tidak adanya sifat disimpulkan dengan benar.

Perbedaan: Aturan yang sangat ketat

Jika kompiler tidak dapat benar-benar yakin bahwa beberapa kode bebas dari ras data dan UB lainnya, itu tidak akan dikompilasi, titik . Aturan-aturan dan alat-alat lain tersebut dapat membuat Anda cukup jauh, tetapi cepat atau lambat Anda akan ingin melakukan sesuatu yang benar, tetapi untuk alasan halus yang luput dari pemberitahuan kompiler. Ini bisa menjadi struktur data bebas kunci yang rumit, tetapi bisa juga sesuatu yang biasa seperti "Saya menulis ke lokasi acak dalam array bersama tetapi indeks dihitung sedemikian rupa sehingga setiap lokasi ditulis hanya oleh satu utas".

Pada titik itu Anda dapat menggigit peluru dan menambahkan sedikit sinkronisasi yang tidak perlu, atau Anda menyusun ulang kode sedemikian rupa sehingga kompiler dapat melihat kebenarannya (sering dapat dilakukan, kadang-kadang sangat sulit, kadang-kadang tidak mungkin), atau Anda memasukkan ke dalam unsafekode. Tetap saja, ini overhead mental ekstra, dan Rust tidak memberi Anda jaminan atas kebenaran unsafekode.

Perbedaan: Lebih sedikit alat

Karena perbedaan yang disebutkan di atas, di Rust jauh lebih jarang seseorang menulis kode yang mungkin memiliki ras data (atau penggunaan setelah bebas, atau bebas ganda, atau ...). Walaupun ini bagus, itu memiliki efek samping yang disayangkan bahwa ekosistem untuk melacak kesalahan seperti itu bahkan lebih terbelakang daripada yang diperkirakan karena kaum muda dan ukuran kecil masyarakat.

Walaupun alat-alat seperti valgrind dan LLVM's thread sanitizer pada prinsipnya dapat diterapkan pada kode Rust, apakah ini benar-benar berfungsi namun bervariasi dari satu alat ke alat lainnya (dan bahkan alat yang bekerja mungkin sulit untuk dipasang, terutama karena Anda mungkin tidak menemukan apa pun yang sesuai dengan keinginan Anda). Sumber daya -tanggal tentang cara melakukannya). Itu tidak benar-benar membantu bahwa Rust saat ini tidak memiliki spesifikasi nyata dan khususnya model memori formal.

Singkatnya, menulis unsafekode Rust dengan benar lebih sulit daripada menulis kode C ++ dengan benar, meskipun kedua bahasa secara kasar sebanding dalam hal kemampuan dan risiko. Tentu saja ini harus dipertimbangkan terhadap fakta bahwa program Rust yang khas hanya akan mengandung sebagian kecil unsafekode, sedangkan program C ++, yah, sepenuhnya C ++.


sumber
6
Di mana di layar saya adalah tombol +25 kenaikan suara? Saya tidak dapat menemukannya! Jawaban informatif ini sangat kami hargai. Itu membuat saya tidak memiliki pertanyaan yang jelas tentang poin-poin yang dicakupnya. Jadi, ke poin lain: Jika saya memahami dokumentasi Rust, Rust memiliki [a] fasilitas pengujian terintegrasi dan [b] sistem build bernama Cargo. Apakah ini cukup produksi siap dalam pandangan Anda? Juga, mengenai Cargo, apakah senang jika saya menambahkan shell, skrip Python dan Perl, kompilasi LaTeX, dll., Ke proses build?
thb
2
@ THB Barang-barang pengujian sangat sederhana (misalnya tidak mengejek) tetapi fungsional. Cargo bekerja dengan sangat baik, meskipun fokusnya pada Rust dan reproduktifitas berarti itu mungkin bukan pilihan terbaik untuk mencakup semua langkah dari kode sumber hingga artefak akhir. Anda dapat menulis skrip pembuatan tetapi itu mungkin tidak sesuai untuk semua hal yang Anda sebutkan. (Namun, orang-orang secara teratur menggunakan skrip build untuk mengkompilasi pustaka C atau menemukan versi pustaka C yang ada sehingga tidak seperti Cargo berhenti bekerja ketika Anda menggunakan lebih dari Karat murni.)
2
Ngomong-ngomong, untuk apa nilainya, jawaban Anda terlihat cukup meyakinkan. Karena saya suka C ++, karena C ++ memiliki fasilitas yang layak untuk hampir semua yang perlu saya lakukan, karena C ++ stabil dan digunakan secara luas, saya sampai sekarang cukup puas menggunakan C ++ untuk setiap kemungkinan tujuan yang tidak ringan (Saya tidak pernah mengembangkan minat di Jawa , sebagai contoh). Tapi sekarang kita memiliki konkurensi, dan C ++ 14 bagi saya tampaknya berjuang dengan itu. Saya belum secara sukarela mencoba bahasa pemrograman baru dalam satu dekade, tetapi (kecuali Haskell akan muncul pilihan yang lebih baik) Saya pikir saya harus mencoba Rust.
thb
Note that if a type doesn't use unsafe to manually implement synchronization, the presence or absence of the traits are inferred correctly.sebenarnya masih berlaku bahkan dengan unsafeelemen. Hanya pointer mentah tidak Syncatau Shareyang berarti bahwa secara default struct mengandung mereka tidak akan memiliki keduanya.
Hauleth
@ ŁukaszNiemier Itu bisa terjadi dengan baik, tetapi ada satu miliar cara di mana tipe pengguna yang tidak aman dapat berakhir Sendatau Syncmeskipun sebenarnya tidak seharusnya.
-2

Karat juga mirip dengan Erlang dan Go. Ini berkomunikasi menggunakan saluran yang memiliki buffer dan menunggu bersyarat. Sama seperti Go, itu melonggarkan pembatasan Erlang dengan membiarkan Anda melakukan memori bersama, mendukung penghitungan referensi dan kunci, dan dengan membiarkan Anda melewati saluran dari utas ke utas.

Namun, Rust melangkah lebih jauh. Sementara Go mempercayai Anda untuk melakukan hal yang benar, Rust menugaskan seorang mentor yang duduk bersama Anda dan mengeluh jika Anda mencoba melakukan hal yang salah. Mentor Rust adalah kompiler. Ia melakukan analisis canggih untuk menentukan kepemilikan nilai-nilai yang dilewatkan di sekitar utas dan memberikan kesalahan kompilasi jika ada masalah potensial.

Berikut ini adalah kutipan dari RUST docs.

Aturan kepemilikan memainkan peran penting dalam pengiriman pesan karena mereka membantu kami menulis kode yang aman dan berbarengan. Mencegah kesalahan dalam pemrograman konkuren adalah keuntungan yang kita dapatkan dengan melakukan pertukaran karena harus memikirkan kepemilikan di seluruh program Rust kami. - Pesan lewat dengan kepemilikan nilai.

Jika Erlang draconian dan Go adalah negara bebas, maka Rust adalah negara pengasuh.

Anda dapat menemukan informasi lebih lanjut dari ideologi Concurrency Bahasa Pemrograman: Jawa, C #, C, C +, Go, dan Rust

srinath_perera
sumber
2
Selamat Datang di Stack Exchange! Harap perhatikan bahwa setiap kali Anda menautkan ke blog Anda sendiri, Anda harus menyatakannya secara eksplisit; lihat pusat bantuan .
Glorfindel