Mengenal Pin di Rust
(without.boats)Pindi Rust adalah elemen dasar yang diperkenalkan untuk menangani state yang self-referential secara aman di dalam Future yang dibuat oleh async/await- Future async menyimpan state di setiap titik await, sehingga sebuah field di dalam satu objek bisa menjadi tipe self-referential yang merujuk ke field lain dalam objek yang sama
- Desain move constructor, offset pointer, dan
?Movemasing-masing tidak diadopsi karena masalah biaya pelacakan runtime, kemungkinan kompilasi, dan kompatibilitas mundur dengan API yang sudah ada - Desain akhirnya adalah
Pin, yang membungkus pointer dan memasukkan targetnya ke pinned typestate; berkat auto traitUnpin, sebagian besar tipe tetap bisa dipindahkan seperti sebelumnya - Kesulitan
Pinbukan berasal dari konsep immutability itu sendiri, melainkan dari keterbatasan tipe library; reborrowing,Pin::set, pinned projection, dan interaksi denganDropsangat menurunkan kemudahan penggunaannya
Masalah yang Membuat Pin Diperlukan
- Dalam ekosistem async Rust,
Pindan pinning adalah fondasi utama, tetapi bagi orang yang mempelajari async Rust, ini masih merupakan area yang sulit dan sering disalahpahami - Tujuan
Pinbukan agar pengguna bisa membuat tipe self-referential sendiri hanya dengan Rust yang aman- Tujuannya adalah memungkinkan Future self-referential yang dibuat compiler dari fungsi async, atau tipe self-referential yang dibuat runtime seperti tokio dengan kode unsafe, untuk dimanipulasi dengan aman
- Pada contoh
async fn bar, di titikfoo(&mut z).await,zdan FutureFooyang merujuk kezharus disimpan bersama dalam state Future yang sama- Pada titik ini, satu field di dalam objek Future merujuk ke field lain di dalam objek yang sama
- Tipe Future seperti ini menjadi tipe self-referential
- Jika objek dipindahkan setelah memasuki state seperti ini, referensi internalnya akan menunjuk ke lokasi memori lama, yang bisa saja sudah menjadi memori mati atau digunakan kembali untuk nilai lain
- Sebelum
Pin, Rust memungkinkan objek dipindahkan jika ada ownership atau mutable reference, sehingga diperlukan cara untuk menyatakan larangan pemindahan setelah titik tertentu
Pendekatan yang Tidak Menjadi Solusi
-
move constructor
- move constructor adalah cara menjalankan kode seperti destructor saat sebuah nilai dipindahkan untuk memperbaiki pointer self-referential ke lokasi baru
- Di Rust, pointer tidak selalu hanya berada “di dalam” nilai yang dipindahkan; misalnya, pointer juga bisa berada dalam vector pointer yang menunjuk ke state miliknya sendiri
- Untuk melacak semua pointer seperti ini, pada akhirnya dibutuhkan manajemen memori runtime yang mirip garbage collection
- Sejak awal Rust memutuskan untuk tidak memiliki move constructor, dan banyak kode unsafe bergantung pada asumsi bahwa nilai dapat dipindahkan hanya dengan menyalin memori
- Menambahkan move constructor belakangan akan menjadi breaking change
-
offset pointer
- offset pointer adalah cara mengompilasi self-reference sebagai offset relatif terhadap alamat objek self-referential, bukan sebagai referensi biasa
- Pada waktu kompilasi, tidak selalu mungkin menentukan apakah sebuah referensi adalah self-reference
- Bergantung pada cabang eksekusi, nilai yang sama bisa menunjuk ke bagian dalam objeknya sendiri atau ke luar objek
- Untuk menangani ini, referensi harus dikompilasi menjadi bentuk seperti enum berisi offset dan reference; pada masa pengerjaan async/await, pendekatan ini dinilai tidak realistis
Kebutuhan pinned typestate
- Future self-referential tidak harus selalu tidak bisa dipindahkan sejak awal; selama siklus hidupnya, ia harus bisa dipindahkan bebas sampai suatu titik tertentu, lalu tidak boleh dipindahkan setelah titik itu
- Saat Future sedang dikombinasikan dengan Future lain, ia harus tetap bisa dipindahkan
- Setelah ditempatkan di lokasi tempat ia akan dipoll, ia tidak boleh dipindahkan lagi
- Model Ralf Jung menambahkan state ketiga untuk Future self-referential, yaitu pinned typestate, selain typestate “owned” dan “shared” yang sudah ada
- Ketika sebuah objek masuk ke pinned typestate, objek itu tidak boleh dipindahkan lagi
- Lebih tepatnya, memori objek tersebut tidak boleh dibuat tidak valid tanpa terlebih dahulu menjalankan destructornya
- Secara praktis, ini bisa dipahami sebagai persyaratan agar objek tidak dipindahkan ke lokasi baru
- Sebagian besar tipe tidak dapat memuat self-reference, sehingga pinned typestate tidak memiliki makna khusus bagi tipe-tipe tersebut
- Untuk tipe seperti ini, sebaiknya mereka bisa keluar dari batasan pinning dan kembali dapat dipindahkan
- Model formal rinci untuk pinned typestate dijelaskan dalam tulisan Ralf Jung, A Formal Look at Pinning
Mengapa Desain ?Move Gagal
- Sebelum
Pin, sempat dicoba desain berbasis trait baru bernamaMove- Sebagian besar tipe mengimplementasikan
Move - Tipe yang dapat memuat self-reference tidak mengimplementasikan
Move - Ketika sebuah referensi dibuat ke nilai dari tipe yang tidak mengimplementasikan
Move, nilai tersebut masuk ke pinned typestate dan tidak bisa lagi dipindahkan
- Sebagian besar tipe mengimplementasikan
- Pendekatan ini intuitif karena mengaitkan waktu pembuatan referensi dengan transisi ke pinning untuk menjamin keamanan
- Bahkan sempat diimplementasikan di sebuah branch compiler
- Keterbatasan mendasarnya adalah ada kasus ketika kita ingin sementara mereferensikan nilai yang nantinya akan menjadi self-referential, tetapi belum ingin melakukan pinning
- Misalnya, kita mungkin ingin menyimpan nilai sementara di
Option, lalu mengeluarkannya denganOption::take
- Misalnya, kita mungkin ingin menyimpan nilai sementara di
- Masalah yang lebih besar adalah kompatibilitas mundur
Movetidak bisa dibuat sebagai auto trait- Sebab, sudah ada API stabil seperti
mem::swapyang berasumsi bahwa nilai selalu bisa dipindahkan dari mutable reference
- Cara menambahkannya sebagai
?Movejuga tidak kompatibel mundur karena associated type- Tempat menambahkan bound
?Traitpada associated type adalah di definisi trait - Jika bound associated type pada trait yang sudah ada dilonggarkan, kode yang bergantung pada bound tersebut bisa rusak
- Banyak operasi dasar terkait dengan associated type, termasuk associated future type milik
IntoFuture,TargetmilikDerefMut, tipe return fungsi, item iterator, nilai return index operator, dan nilai return operator aritmetika
- Tempat menambahkan bound
- Edition juga tidak dapat menyelesaikannya dengan mudah
- Sebab, agar crate dari edition berbeda bisa dikombinasikan, interface trait harus tetap sama
Desain Pin
- Desain akhirnya mengekspresikan pinned typestate bukan sebagai properti tipe objek, melainkan sebagai state yang dibuat oleh pointer khusus
Pinadalah wrapper type yang membungkus pointer- Ia juga dapat membungkus built-in reference type
- Ia juga dapat membungkus smart pointer yang didefinisikan library seperti
Box
Pinmemasukkan target yang ditunjuk pointer tersebut ke pinned typestate, dan target itu tidak boleh lagi dipindahkan- Untuk meminimalkan perubahan, desain ini diimplementasikan sebagai API library, bukan fitur compiler
- Kode yang benar-benar perlu mengubah objek pinned harus mengaksesnya melalui API unsafe
- Dalam hal ini, kode tersebut harus memberikan jaminan bahwa objek tidak dipindahkan melalui mutable reference biasa
- Karena bagi sebagian besar tipe tidak ada perbedaan bermakna antara state pinned dan state biasa, auto trait
Unpinditambahkan- Jika sebuah tipe tidak mungkin self-referential, mutable reference dapat diperoleh dari pinned pointer tanpa unsafe
- Objek yang mengimplementasikan
Unpinaman untuk dipindahkan keluar dariPin
- Karena pinning hanya berlaku pada pinned pointer, reference biasa yang unpinned tetap bekerja dengan tipe yang bukan
Unpin - Penjelasan tambahan tersedia di dokumentasi standar untuk tipe
Pindan modulpin - Keunggulan terbesar desain ini adalah dapat ditambahkan tanpa merusak kode yang sudah ada
- API yang dapat memindahkan data yang direferensikan, seperti
swap, membutuhkan mutable reference - Jika sebuah objek dipin dengan
Pin, API semacam itu tidak lagi dapat dipanggil pada objek tersebut - Karena pinned typestate hanya berlaku pada pinned reference khusus, jaminan kompatibilitas mundur seluruh bahasa Rust tidak dilanggar
- API yang dapat memindahkan data yang direferensikan, seperti
Masalah Kemudahan Penggunaan Pin
Pinmemenuhi kebutuhan dengan cara yang kompatibel mundur, tetapi begitu pengguna menanganinya secara langsung, muncul tebing kompleksitas- Salah satu penjelasannya adalah bahwa untuk mengubah objek pinned dibutuhkan kode unsafe
- Namun masalah ini tidak boleh dilebih-lebihkan
- Dengan
Pin::set, kita dapat melakukan assignment ke objek pinned secara aman - Kode yang benar-benar perlu mengubah objek pinned umumnya adalah kode hasil compiler yang menurunkan fungsi async menjadi Future, dan jarang ditulis langsung oleh pengguna
- Penjelasan bahwa
Pinsulit karena perilakunya bersifat kondisional juga bukan akar masalahnya- Rust memiliki fitur yang berperilaku berbeda bergantung kondisi, tetapi tetap membuat pemahaman lebih mudah
- non-lexical lifetimes adalah contoh ketika lifetime dapat berakhir pada titik berbeda di setiap cabang kondisi
- Masalah utamanya adalah
Pinmerupakan tipe library murni, sedangkan reference type biasa adalah tipe bawaan bahasa yang mendapat berbagai dukungan sintaks dan sugar- Fitur-fitur yang terasa alami pada referensi biasa hilang pada pinned reference
- Mental model yang dibangun pengguna berdasarkan perilaku referensi yang diterima compiler menjadi rusak pada pinned reference
reborrowing dan Pin::as_mut
- Mutable reference biasa
&mut Ttidak mengimplementasikanCopy, tetapi bisa diteruskan beberapa kali sebagai argumen yang sama- Ini karena compiler secara implisit melakukan reborrowing, seolah-olah memasukkan
&mut *xalih-alihx
- Ini karena compiler secara implisit melakukan reborrowing, seolah-olah memasukkan
- Karena
Pin<&mut T>adalah tipe library biasa dan tidak mengimplementasikanCopy, ia tidak memiliki kemudahan ini- Jika
Pin<&mut T>digunakan dua kali atau lebih, bisa muncul error penggunaan nilai setelah move, atau error lifetime yang lebih sulit dipahami - Kita harus secara eksplisit memanggil
Pin::as_mutuntuk melakukan reborrow
- Jika
- Pada mutable reference biasa, assignment dapat dilakukan langsung dengan dereference dan assignment operator, tetapi pada
Pin, kita harus mempelajari metodeset- Banyaknya API khusus seperti ini muncul karena
Pinadalah tipe library tanpa dukungan sintaks bahasa
- Banyaknya API khusus seperti ini muncul karena
pinned projection dan Drop
- pinned projection adalah masalah mendapatkan pinned reference ke field sebuah objek dari pinned reference ke objek tersebut
- projection berarti mengakses field dari sebuah objek
- Ini jauh lebih sulit daripada akses field pada referensi biasa, sehingga crate pihak ketiga seperti pin-project-lite digunakan
- Crate seperti ini mengharuskan kita mempelajari API baru yang kompleks, termasuk macro
- Interaksi terburuk terjadi antara pinned projection dan trait
DropDrop::dropmenerima mutable reference biasa- Jika sebuah tipe memiliki field self-referential, lalu field tersebut di-pin-project dan dipoll, kemudian destructor memindahkan field itu, jaminan pinning bisa dilanggar
- Misalnya, jika di dalam destructor Future tersebut di-pin di stack lalu dipoll, jaminan pinning yang sudah ada akan dilanggar
- Crate seperti
pin-project-litemenangani masalah ini dengan membatasi kemampuan mendefinisikan destructor- Secara praktis ini berjalan, tetapi menambahkan kompleksitas yang harus didokumentasikan ketika menjelaskan jaminan pinning
- Karena
Dropsudah stable sebelumPin, diperlukan workaround
Evaluasi Saat Ini dan Arah Perbaikan Berikutnya
Pinmemungkinkan fungsi async yang memuat referensi apa pun dikompilasi menjadi objek self-referential yang aman- Referensi adalah bagian penting dari cara dasar pengguna Rust menulis kode, sehingga tanpa ini, kemudahan penggunaan async/await akan jauh menurun
- Pada saat yang sama,
Pinditambahkan dengan cara yang sepenuhnya kompatibel mundur dengan Rust yang sudah ada Pintelah menjadi komponen dasar ekosistem yang menopang layanan jaringan berkinerja tinggi dan berbagai use case pemrograman asinkron lainnya- Namun menangani pinned reference jauh lebih sulit daripada menangani ordinary reference, dan
Pinmemang menciptakan tebing kompleksitas - Konsep utama untuk arah perbaikan berikutnya adalah pinned places
1 komentar
Pendapat Hacker News
Saya selalu merasa Pin sulit dipahami karena tidak dijelaskan dengan jelas di dokumentasi resmi
Khususnya, banyak penjelasan seperti “Pin menjamin objek tidak akan pernah berpindah”, padahal itu tidak benar
Itu hanya benar ketika objek bukan
Unpin, dan kebanyakan objek biasa adalahUnpin, jadi Pin biasanya tidak melakukan apa-apaButuh waktu sangat lama bagi saya untuk memahami ini, dan menurut saya himpunan tipe
Tyang membuat Pin benar-benar bermakna itu cukup khusus dan aneh, tetapi dokumentasinya tidak cukup menekankan hal tersebutTentu saja, tipe yang dalam praktik akan diperlakukan sebagai sesuatu yang dipin, yaitu future dan stream, jauh lebih mungkin merupakan objek khusus seperti itu
Meski begitu, menurut saya dokumentasinya sudah jauh membaik dalam beberapa tahun terakhir
Saat saya memeriksanya ketika menulis artikel ini, saya terkejut karena fokusnya sudah pada poin yang cukup tepat, dan seingat saya sekitar 2019 dokumentasi API
stdjauh lebih condong ke spesifikasi kontrak yang lebih cocok masuk ke dokumen referensi RustMenurut saya alasan pengguna menganggap Pin sulit adalah karena Pin itu sendiri tidak punya makna
Ini berbeda dari wrapper lain dalam bahasa ini; kalau ada pengecualian, mungkin hanya
AssertUnwindSafe, yang hampir tidak pernah dipakai untuk tujuan aslinyaKetika ada
Pin<&mut InnerType>, Pin saja di dalam bahasa atau standard library tidak memberi tahu apa yang bisa dan tidak bisa dilakukanHanya saja, jika
InnerTypedinyatakan sebagaiUnpin, artinya semua hal yang bisa dilakukan dengan pointer biasa juga bisa dilakukanSebaliknya, Pin bekerja dengan cara “bawalah maknanya sendiri”, yakni penyedia
InnerTypemembuat metode dan API tambahan yang secara internalunsafeuntuk memanipulasi objek yang dipin dengan amanTujuan Pin sendiri adalah menyediakan pointer dengan kemampuan bawaan yang lebih sedikit, seperti mengganti lewat
&mutatau mengeluarkan lalu memindahkan dariBox, agar tipe di dalamnya dapat mengizinkan kemampuan tambahan di atasnya secara amanMenurut saya ambiguitas makna inilah yang paling membingungkan orang, dan saya sendiri butuh waktu cukup lama untuk memahaminya
Konsep tentang field struktural dan nonstruktural hanyalah mekanisme untuk memungkinkan pola akses umum seperti “field ini data biasa, tetapi field itu berisi objek yang ingin dipin sendiri”
Unpin, target dari pointer ini berarti tidak akan pernah bisa dipindahkan lagiLebih tepatnya, artinya target tidak bisa dibuat tidak valid tanpa menjalankan destruktornya, dan itulah alasan mengapa pemindahan menjadi masalah
Jika melepaskan hak tertentu, kita memperoleh hak lain, seperti hak untuk menyimpan nilai yang mereferensikan dirinya sendiri
Kontrak antar-komponen umumnya bekerja seperti ini
Demikian pula, jika melepaskan hak untuk mengubah lewat referensi, kita sekaligus bisa membuat referensi itu memiliki alias
Setiap kali memikirkan hal ini, saya teringat dialog dalam film Lincoln, meski itu topik yang sama sekali berbeda dan jauh lebih berat: “Jika kita tunduk pada hukum, Alex, jika kita tunduk sampai kehilangan kebebasan — misalnya kebebasan untuk menindas — mungkin kita akan menemukan kebebasan lain yang sebelumnya tidak kita kenal”
Namun saya setuju bahwa fakta bahwa hak-hak seperti itu tidak bisa digunakan langsung dalam kode aman adalah masalah dari sisi edukasi
Sebab sulit menunjukkan dengan mudah apa yang bisa dilakukan dengan referensi yang dipin selain “memanggil metode
pollyang dibuatkan compiler”Saya sudah beberapa tahun mengembangkan secara profesional dengan Rust, tetapi jujur saja saya tidak begitu memahami Pin
Saya paham teorinya, tetapi tidak punya banyak intuisi tentang kapan harus menggunakannya
Penggunaan Pin pada dasarnya terasa seperti “saya mencoba sesuatu lalu compiler mengeluh, kemudian saya pin ini-itu sampai akhirnya bisa dikompilasi”
Dalam coding harian, sejauh ini Pin belum pernah benar-benar menjadi hambatan yang membuat saya harus duduk dan memahaminya secara mendalam
unsafe, dan bersyukurlah orang-orang compiler yang pintar sudah menyelesaikan semuanya”Sebaliknya di C++, saya sering berjalan di tepi rawa “hal-hal yang tidak saya pahami tetapi wajib saya pakai”, lalu dimakan buaya
Saat mengajarkannya, untuk memperjelas bahwa item
Unpintidak terpengaruh olehPin, mungkin bagus memakai analogi dunia nyata tentang benda yang tetap tidak terpengaruh meskipun memakai alat yang dibuat untuk menahannya di tempatKait velcro tidak menempel pada permukaan licin:
Pin→ velcro,Unpin→ permukaan licinMagnet tidak memengaruhi bahan nonmagnetik:
Pin→ magnet,Unpin→ nonmagnetik/kaca/kuninganLem tidak menempel pada permukaan antilengket:
Pin→ lem,Unpin→ antilengketDengan begitu jelas bahwa “velcro” memang menahan benda di tempatnya, tetapi jika benda itu “licin”, ia tidak terpengaruh oleh mekanisme velcro
Mengingat suasana penamaan di ekosistem Rust, rasanya indah jika nama trait-nya diganti ke arah seperti magnet dan nonmagnetik
Saya rasa
Unpinberarti objek siap dipin kapan sajaSaya membaca artikelnya tadi malam, tetapi saya sudah lupa apakah peminjaman butuh tahap penyesuaian
Jadi menurut saya
T: Pin + !Unpinseperti kertas yang hanya bisa dipasang dengan staples, sedangkanT: Pin + Unpinlebih mirip lukisan yang memiliki kait, bisa digantung pada paku lalu diturunkan lagi tanpa merusak kaitnyaIstilah “identitas nilai” tidak didefinisikan di mana pun dalam tulisan ini dan saya juga tidak menemukannya di dokumentasi Mojo, jadi dasar Modular mengatakan bahwa Mojo menyelesaikan masalah yang ingin diselesaikan Pin tidak jelas
Saya juga tidak mengklaim tahu jawabannya, tetapi saya teringat presentasi bagus dari Dave Abrahams, yang mengerjakan semantik nilai Swift bersama Chris Lattner
Judul presentasinya adalah “Value Semantics: Safety, Independence, Projection, & Future of Programming”
[0] https://www.youtube.com/watch?v=QthAU-t3PQ4
Rust memiliki referensi sebagai tipe kelas satu, sedangkan Swift dan, menurut saya, Mojo hanya mengizinkan referensi sebagai cara meneruskan parameter
Mojo tampaknya memperluas parameter
inoutSwift dengan juga menyediakan cara penerusan referensi immutableJika referensi tidak bisa disimpan di dalam objek, maka kode semacam yang dikompilasi Rust tidak bisa diimplementasikan, sehingga ini memang menyelesaikan masalah “struct yang mereferensikan dirinya sendiri”
Namun paragraf yang dikutip sama sekali tidak mengatakan hal seperti itu tentang Mojo, jadi cukup membingungkan maksudnya
Menurut saya masalahnya adalah jika ada referensi
&mutke suatu nilai, nilai itu bisa dipindahkan dengan hal sepertimem::swap/replaceNamun dalam praktiknya jarang sekali perlu melakukan itu
Kalau itu tidak diizinkan, memiliki referensi
&mutke nilai yang mereferensikan dirinya sendiri tampaknya akan sepenuhnya amanMungkin bisa saja ada cara untuk secara eksplisit memilih pemindahan melalui referensi hanya saat diperlukan, dan jika
swapsertareplacedibuatunsafe, mungkin seluruh masalah ini bisa dihindariSaya berharap ada yang mengeksplorasi ruang desain ini
&mutsebagai terlalu kuatJika
&muttidak memberi wewenang untuk memindahkan nilai di dalamnya, keseluruhan desainnya akan jauh lebih sederhanaSaya berencana membahas hal ini di tulisan berikutnya
Rust harus menjaga kompatibilitas mundur dan sudah memutuskan bahwa nilai bisa dipindahkan dari
&mut, tetapi jika tidak terikat oleh keputusan masa lalu, jelas ada desain yang jauh lebih rapi yang mungkin dibuatmem::swaphanyalah salah satu cara memindahkan nilai melalui referensi mutable, dan ada sangat banyak cara lainOption::takeadalah contoh yang cukup sering saya gunakan, dan akan terasa sangat aneh jika ituunsafeSenang melihat cerita latar ini. WithoutBoats sudah banyak melakukan diskusi aktif tentang topik yang sangat tepat waktu seperti iterator asynchronous,
poll, danpinhttps://news.ycombinator.com/from?site=without.boats
Sepertinya tidak banyak komunitas yang menggali detail internal bahasa sedalam ini secara terbuka, dan menyenangkan untuk diikuti
Async masih setengah matang dan sangat kompleks
Ini saya katakan sebagai orang yang selama 3 tahun terakhir menulis kode Rust 40 jam seminggu
Kita bisa membayangkan bahasa mirip Rust yang memiliki move constructor, semua subtipe
Futureyang dihasilkan bersifat opaque, dan otomatis dialokasikan di heapDengan begitu, pengguna tidak punya cara untuk merusaknya, dan karena opaque serta berada di tempat lain di heap, tidak ada cara untuk memindahkannya, sehingga Pin bisa jadi tidak diperlukan
Adanya move constructor berarti secara konseptual perpindahan adalah penghancuran lalu pembuatan ulang
Ini memberi efek baik:
Futurebisa digabungkan dan di-inline sebelum dieksekusiMirip juga dengan immutability di Rust. Bukan ada memori immutable, melainkan hanya referensi immutable
Namun itu berarti setiap pemanggilan fungsi asynchronous menimbulkan alokasi terpisah, dan ini sangat buruk bagi lokalitas memori
Sejenis stack virtual akan jauh lebih baik daripada itu, tetapi untuk mengoptimalkan stack agar pada dasarnya kecil, pada akhirnya diperlukan garbage collection
Movekestd, yang tertanam di bahasa pada level miripCopyMovemendefinisikan fungsi untuk memindahkan nilai dari satu alamat memori ke alamat lain, dan membuat struct tanpaimpl Movetidak bisa dipindahkanHampir semua tipe cukup diberi
#[derive(Move)], yang mengimplementasikan fungsi pemindahan sederhana berupa menyalin byteNamun dengan ini, jalan akan terbuka untuk tipe self-referential, future, dan banyak hal lain yang membutuhkan perilaku move yang lebih kompleks
Dalam praktiknya, mungkin lebih masuk akal membaginya menjadi dua trait yang mencerminkan perbedaan antara
CopydanCloneYang satu adalah marker trait yang memberi tahu compiler bahwa byte boleh dipindahkan begitu saja, sedangkan yang lain mengizinkan implementasi “move constructor” kustom oleh pengguna
Pin terlalu sulit dipahami, jadi akan bagus jika ada Move
Konsep yang rumit dibungkus dalam negasi ganda, kadang negasi tiga lapis. Saat melihat sesuatu seperti
fn(...), rasanya “apa ini?”, dan ketika sampai ke pin projection yangunsafe, saya mulai tersingkirSaya tidak tahu kapan aman dan kapan tidak aman, jadi akhirnya lepas tangan saja
Berpindah dari Rust tanpa
Moveke Rust denganMoveakan merepotkanHampir semua struct yang pernah ditulis sejauh ini harus ditambahi
#[derive(Move)], danstdjuga samaUntuk semua tipe yang belum di-pin di edition lama, compiler harus menginfer implementasi trait
MoveSecara mekanis mungkin bisa dilakukan, hanya saja pekerjaannya banyak
Async Rust itu mengerikan. Apalagi jika dibandingkan dengan future/promise di hampir semua bahasa lain
Suatu hari nanti seseorang akan memperbaiki model memory safety Rust dan membuat bahasa sistem baru yang mirip Rust, dengan trait
Movedan future yang lebih baikSecara pribadi, saya juga ingin ada eksekusi waktu kompilasi sebagai pengganti sistem macro Rust
Saya menyukai Rust, dan saya juga menyukai semua pekerjaan yang telah dilakukan timnya selama bertahun-tahun
Namun bahasa yang benar-benar saya nantikan adalah bahasa setelah Rust
Idenya sama, tetapi bahasa itu belajar dari kesalahan Rust, dan semakin jelas seperti apa bahasa bergaya Rust yang lebih baik itu nantinya
Saya benar-benar menantikannya
Satu lagi tulisan bagus dari WithoutBoats
Sejujurnya, ini salah satu bagian yang membuat saya merasa lega karena di Rust ia diabstraksikan dan terkubur di dalam runtime asynchronous
Meski begitu, selain implementasi
Futurekustom, saya penasaran di mana Pin benar-benar dipakaiDari cara diekspresikan, sepertinya bisa dipakai di dalam FFI
Misalnya jika fungsi
externmengembalikan*mut Tlalu menerima pointer, membungkusnya denganPin<&mut T>tampak bisa memberi semantik yang lebih baikNamun artikel itu mengatakan, “fakta lain tentang status tipe yang di-pin adalah bahwa ia sama sekali tidak relevan bagi kebanyakan tipe. Jika nilai suatu tipe tidak akan pernah bisa berisi referensi ke dirinya sendiri, mem-pin nilai itu tidak ada gunanya”
Saya masih sangat pemula dalam FFI, jadi ingin memahami cara terbaik membungkusnya dengan Rust yang aman
Ini juga berlaku saat berinteraksi dengan tipe sistem yang bergantung pada alamat
Misalnya, pada mutex/futex di sebagian sistem operasi, dokumentasi kernel mengatakan bahwa objek lock di user space tidak boleh berubah alamat setelah diinisialisasi, jadi setahu saya
stdmenggunakan sesuatu yang setara dengan PinYang unik adalah alamatnya tidak boleh berubah bahkan saat tidak sedang terkunci
Biasanya syarat itu hanya berlaku selama sedang terkunci, dan dalam kasus itu objek yang sedang direferensikan tidak bisa dipindahkan, sehingga Pin tidak diperlukan
Ini terlihat seperti pekerjaan besar-besaran demi menghindari perbaikan masalah yang sebenarnya, yaitu inefisiensi thread
Semua kode asynchronous, tanpa pengecualian, adalah hack untuk mengimplementasikan thread ringan dengan banyak syntactic sugar untuk manajemen state
Dalam bahasa seperti Rust, ini menambahkan kompleksitas luar biasa yang semestinya tidak perlu ada
Jika masalah efisiensi dan skalabilitas thread diperbaiki, semua ini akan hilang
Hilang begitu saja
Mirip dengan bagaimana
nulldalam bahasa seperti Java adalah “kesalahan bernilai triliunan dolar”Satu keputusan desain, atau dalam kasus ini ketiadaan desain, menimbulkan kompleksitas yang sangat besar
Pembatalan sangat berguna dalam aplikasi jaringan dan GUI
Thread membuat sulit untuk memanfaatkan CPU dan jaringan secara memadai sekaligus tanpa membuat salah satunya terlalu banyak didominasi
Begitu mulai memindahkan pekerjaan antar thread pool, Anda sudah masuk ke jalur mengimplementasikan ulang future
Atau Anda akan bekerja dengan callback/event, yang membuat kode terpecah-pecah, dan
async/awaitmemang dimaksudkan menjadi syntactic sugar untuk hal ituAlternatif untuk pembatalan dan timeout adalah menyelipkan objek
Contextke seluruh kode seperti di Go, tetapi ini menimbulkan masalah ketika kode di ujung secara naif memanggil fungsi yang tidak benar-benar mematuhiContextIni hanya sedikit lebih baik daripada masalah fungsi non-asynchronous di dalam kode asynchronous
Jadi saya penasaran, secara konkret apa yang seharusnya dilakukan
Apakah seharusnya mereka angkat tangan dan berkata, “suatu hari nanti mungkin ada yang memperbaiki Linux sehingga thread secara ajaib menjadi cepat, jadi kami tidak akan menambahkan async ke bahasa kami”?
Selain itu, jika sistem operasi dijadikan scheduler untuk semua pekerjaan asynchronous, maka setiap runtime harus memakai scheduler sistem operasi, sehingga berbagai desain scheduler menjadi tidak mungkin
Bahkan setelah “pendekatan yang disukai” terbukti tidak dapat dijalankan, mereka tampaknya tidak mengevaluasi ulang biaya/manfaatnya
“Saya ingin fitur X, tidak peduli akibatnya” jarang menjadi langkah yang menang dalam desain bahasa