Rust: [Stabilisasi] async/menunggu MVP

Dibuat pada 26 Jun 2019  ·  58Komentar  ·  Sumber: rust-lang/rust

Target stabilisasi: 1.38.0 (beta cut 2019-08-15)

Ringkasan bisnis plan

Ini adalah proposal untuk menstabilkan fitur async/menunggu minimum yang layak, yang meliputi:

  • async penjelasan pada fungsi dan blok, menyebabkan mereka tertunda dalam evaluasi dan malah mengevaluasi ke masa depan.
  • Operator await , hanya valid dalam konteks async , yang mengambil masa depan sebagai argumen dan menyebabkan masa depan luar yang ada di dalamnya untuk menghasilkan kontrol hingga masa depan yang ditunggu selesai.

Diskusi sebelumnya terkait

RFC:

Masalah pelacakan:

Stabilisasi:

Keputusan besar tercapai

  • Masa depan yang dievaluasi oleh ekspresi async dibangun dari status awalnya, tidak menjalankan kode tubuh apa pun sebelum menghasilkan.
  • Sintaks untuk fungsi async menggunakan tipe pengembalian "dalam" (tipe yang cocok dengan ekspresi internal return ) daripada tipe pengembalian "luar" (tipe masa depan yang dievaluasi oleh panggilan ke fungsi)
  • Sintaks untuk operator menunggu adalah "sintaks titik postfix," expression.await , sebagai lawan dari await expression lebih umum atau sintaks alternatif lainnya.

Stabilisasi pemblokiran pekerjaan implementasi

  • [x] async fns harus dapat menerima beberapa masa pakai #56238
  • [x] ukuran generator tidak boleh tumbuh secara eksponensial #52924
  • [ ] Dokumentasi minimal yang layak untuk fitur async/menunggu
  • [ ] Tes kompiler yang memadai untuk perilaku

Pekerjaan masa depan

  • Async/menunggu dalam konteks tanpa-std: async dan await saat ini mengandalkan TLS untuk bekerja. Ini adalah masalah implementasi yang bukan merupakan bagian dari desain, dan meskipun tidak menghalangi stabilisasi, hal ini dimaksudkan untuk diselesaikan pada akhirnya.
  • Fungsi asinkron tingkat tinggi: async sebagai pengubah untuk literal penutupan tidak distabilkan di sini. Diperlukan lebih banyak pekerjaan desain terkait pengambilan dan abstraksi melalui penutupan asinkron dengan masa pakai.
  • Metode sifat asinkron: Ini melibatkan pekerjaan desain dan implementasi yang signifikan, tetapi merupakan fitur yang sangat diinginkan.
  • Pemrosesan aliran: Pasangan dengan sifat Masa Depan di perpustakaan masa depan adalah sifat Aliran, sebuah iterator asinkron. Mengintegrasikan dukungan untuk memanipulasi aliran ke std dan bahasa adalah fitur jangka panjang yang diinginkan.
  • Mengoptimalkan representasi generator: Lebih banyak pekerjaan dapat dilakukan untuk mengoptimalkan representasi generator agar ukurannya lebih sempurna. Kami telah memastikan bahwa ini benar-benar masalah pengoptimalan dan tidak signifikan secara semantik.

Latar belakang

Menangani IO non-blocking sangat penting untuk mengembangkan layanan jaringan berkinerja tinggi, kasus penggunaan target untuk Rust dengan minat yang signifikan dari pengguna produksi. Untuk alasan ini, solusi untuk membuatnya ergonomis dan layak untuk menulis layanan menggunakan IO non-blocking telah lama menjadi tujuan Rust. Fitur async/menunggu adalah puncak dari upaya itu.

Sebelum 1.0, Rust memiliki sistem greenthreading, di mana Rust menyediakan alternatif, primitif threading tingkat bahasa yang dibangun di atas IO nonblocking. Namun, sistem ini menyebabkan beberapa masalah: yang paling penting adalah memperkenalkan runtime bahasa yang memengaruhi kinerja bahkan program yang tidak menggunakannya, menambah secara signifikan overhead FFI, dan memiliki beberapa masalah desain utama yang belum terselesaikan terkait dengan implementasi tumpukan greenthread .

Setelah penghapusan greenthreads, anggota proyek Rust mulai mengerjakan solusi alternatif berdasarkan abstraksi berjangka. Kadang-kadang juga disebut janji, masa depan telah sangat berhasil dalam bahasa lain sebagai abstraksi berbasis perpustakaan untuk nonblocking IO, dan diketahui bahwa dalam jangka panjang mereka memetakan dengan baik ke sintaks async/menunggu yang bisa membuat mereka hanya sedikit kurang nyaman daripada sistem greenthreading yang sama sekali tidak terlihat.

Terobosan besar dalam pengembangan abstraksi Masa Depan adalah pengenalan model berbasis jajak pendapat untuk masa depan. Sementara bahasa lain menggunakan model berbasis panggilan balik, di mana masa depan sendiri bertanggung jawab untuk menjadwalkan panggilan balik untuk dijalankan ketika selesai, Rust menggunakan model berbasis polling, di mana pelaksana bertanggung jawab untuk polling masa depan hingga selesai, dan masa depan hanya menginformasikan pelaksana bahwa siap untuk membuat kemajuan lebih lanjut menggunakan abstraksi Waker. Model ini bekerja dengan baik karena beberapa alasan:

  • Ini memungkinkan rustc untuk mengkompilasi masa depan ke mesin negara yang memiliki overhead memori paling minimal, baik dari segi ukuran dan tipuan. Ini memiliki manfaat kinerja yang signifikan dibandingkan pendekatan berbasis panggilan balik.
  • Ini memungkinkan komponen seperti eksekutor dan reaktor ada sebagai API perpustakaan, bukan bagian dari runtime bahasa. Ini menghindari pengenalan biaya global yang berdampak pada pengguna yang tidak menggunakan fitur ini, dan memungkinkan pengguna untuk mengganti komponen individual dari sistem runtime mereka dengan mudah, daripada mengharuskan kami membuat keputusan kotak hitam untuk mereka di tingkat bahasa.
  • Itu membuat semua perpustakaan primitif konkurensi juga, daripada memanggang konkurensi ke dalam bahasa melalui semantik async dan menunggu operator. Ini membuat konkurensi lebih jelas dan lebih terlihat melalui teks sumber, yang harus menggunakan primitif konkurensi yang dapat diidentifikasi untuk memperkenalkan konkurensi.
  • Ini memungkinkan pembatalan tanpa overhead, dengan membiarkan eksekusi berjangka dijatuhkan sebelum selesai. Membuat semua futures dapat dibatalkan secara gratis memiliki manfaat kinerja dan kejelasan kode untuk pelaksana dan primitif konkurensi.

(Dua poin terakhir juga telah diidentifikasi sebagai sumber kebingungan bagi pengguna yang berasal dari bahasa lain yang tidak benar, dan membawa harapan dari bahasa tersebut bersama mereka. Namun, properti ini merupakan properti yang tidak dapat dihindari dari model berbasis polling yang memiliki keunggulan lain yang jelas dan, menurut pendapat kami, merupakan properti yang bermanfaat setelah pengguna memahaminya.)

Namun, model berbasis polling mengalami masalah ergonomis yang serius ketika berinteraksi dengan referensi; pada dasarnya, referensi di seluruh titik hasil memperkenalkan kesalahan kompilasi yang tidak dapat diselesaikan, meskipun seharusnya aman. Ini menghasilkan kode yang kompleks dan berisik yang penuh dengan busur, mutex, dan penutupan bergerak, yang tidak ada yang benar-benar diperlukan. Bahkan mengesampingkan masalah ini, tanpa primitif tingkat bahasa, masa depan menderita karena memaksa pengguna ke dalam gaya penulisan panggilan balik yang sangat bersarang.

Untuk alasan ini, kami mengejar gula sintaksis async/menunggu dengan dukungan untuk penggunaan referensi normal di seluruh titik hasil. Setelah memperkenalkan abstraksi Pin yang membuat referensi di seluruh titik hasil aman untuk didukung, kami telah mengembangkan sintaks async/await asli yang mengkompilasi fungsi ke masa depan berbasis polling kami, memungkinkan pengguna untuk mendapatkan keuntungan kinerja IO asinkron dengan futures saat menulis kode yang sangat mirip dengan kode imperatif standar. Fitur terakhir itu adalah subjek dari laporan stabilisasi ini.

async/menunggu deskripsi fitur

Pengubah async

Kata kunci async dapat diterapkan di dua tempat:

  • Sebelum ekspresi blok.
  • Sebelum fungsi bebas atau fungsi terkait dalam impl bawaan.

_(Lokasi lain untuk fungsi asinkron - literal penutupan dan metode sifat, misalnya, akan dikembangkan lebih lanjut dan distabilkan di masa mendatang.)_

Pengubah asinkron menyesuaikan item yang dimodifikasi dengan "mengubahnya menjadi masa depan". Dalam kasus blok, blok dievaluasi ke masa depan hasilnya, bukan hasilnya. Dalam kasus suatu fungsi, panggilan ke fungsi itu mengembalikan nilai pengembaliannya di masa depan, bukan nilai pengembaliannya. Kode di dalam item yang dimodifikasi oleh pengubah asinkron disebut berada dalam konteks asinkron.

Pengubah async melakukan modifikasi ini dengan menyebabkan item dievaluasi sebagai konstruktor murni masa depan, mengambil argumen dan menangkap sebagai bidang masa depan. Setiap titik menunggu diperlakukan sebagai varian terpisah dari mesin status ini, dan metode "jajak pendapat" masa depan memajukan masa depan melalui status ini berdasarkan transformasi kode yang ditulis pengguna, hingga akhirnya mencapai status terakhirnya.

Pengubah async move

Mirip dengan penutupan, blok asinkron dapat menangkap variabel dalam lingkup sekitarnya ke dalam keadaan masa depan. Seperti penutupan, variabel ini secara default ditangkap oleh referensi. Namun, mereka malah dapat ditangkap berdasarkan nilai, menggunakan pengubah move (seperti halnya penutupan). async muncul sebelum move , membuat blok ini async move { } blok.

Operator await

Dalam konteks async, ekspresi baru dapat dibentuk dengan menggabungkan ekspresi dengan operator await , menggunakan sintaks ini:

expression.await

Operator menunggu hanya dapat digunakan di dalam konteks async, dan jenis ekspresi yang diterapkan harus mengimplementasikan sifat Future . Ekspresi menunggu mengevaluasi ke nilai keluaran masa depan yang diterapkan.

Operator menunggu menghasilkan kontrol masa depan yang dievaluasi oleh konteks async hingga masa depan yang diterapkan telah selesai. Operasi kontrol hasil ini tidak dapat ditulis dalam sintaksis permukaan, tetapi jika bisa (menggunakan sintaks YIELD_CONTROL! dalam contoh ini) desugaring dari menunggu akan terlihat kira-kira seperti ini:

loop {
    match $future.poll(&waker) {
        Poll::Ready(value)  => break value,
        Poll::Pending       => YIELD_CONTROL!,
    }
}

Ini memungkinkan Anda untuk menunggu masa depan selesai mengevaluasi dalam konteks asinkron, meneruskan hasil kontrol melalui Poll::Pending ke luar ke konteks asinkron terluar, akhirnya ke pelaksana tempat masa depan telah muncul.

Poin keputusan utama

Menyerah segera

Fungsi dan blok async kami "menghasilkan segera" - membangunnya adalah fungsi murni yang menempatkannya dalam keadaan awal sebelum mengeksekusi kode dalam isi konteks async. Tak satu pun dari kode tubuh akan dieksekusi sampai Anda mulai polling masa depan itu.

Ini berbeda dari banyak bahasa lain, di mana panggilan ke fungsi async memicu pekerjaan untuk segera dimulai. Dalam bahasa lain ini, async adalah konstruk inheren bersamaan: ketika Anda memanggil fungsi async, itu memicu tugas lain untuk mulai mengeksekusi bersamaan dengan tugas Anda saat ini. Di Rust, bagaimanapun, futures tidak dieksekusi secara inheren secara bersamaan.

Kita bisa membuat item async dieksekusi hingga titik tunggu pertama saat dibuat, alih-alih membuatnya murni. Namun, kami memutuskan ini lebih membingungkan: apakah kode dieksekusi selama membangun masa depan atau polling itu akan tergantung pada penempatan menunggu pertama di tubuh. Lebih mudah untuk mempertimbangkan semua kode yang akan dieksekusi selama polling, dan tidak pernah selama konstruksi.

Referensi:

Kembalikan sintaks tipe

Sintaks fungsi async kami menggunakan tipe pengembalian "dalam", bukan tipe pengembalian "luar". Artinya, mereka mengatakan bahwa mereka mengembalikan tipe yang akhirnya mereka evaluasi, daripada mengatakan bahwa mereka mengembalikan masa depan tipe itu.

Pada satu tingkat, ini adalah keputusan tentang jenis kejelasan yang lebih disukai: karena tanda tangan juga menyertakan anotasi async , fakta bahwa mereka mengembalikan masa depan dibuat eksplisit dalam tanda tangan. Namun, akan sangat membantu bagi pengguna untuk melihat bahwa fungsi tersebut mengembalikan masa depan tanpa harus memperhatikan kata kunci async juga. Tapi ini juga terasa seperti boilerplate, karena informasinya juga disampaikan oleh kata kunci async .

Apa yang benar-benar membuat timbangan bagi kami adalah masalah penghapusan seumur hidup. Jenis pengembalian "luar" dari fungsi asinkron apa pun adalah impl Future<Output = T> , di mana T adalah jenis pengembalian dalam. Namun, masa depan itu juga menangkap masa pakai dari setiap argumen input itu sendiri: ini adalah kebalikan dari default untuk Sifat impl, yang tidak diasumsikan untuk menangkap masa pakai input apa pun kecuali Anda menentukannya. Dengan kata lain, menggunakan tipe pengembalian luar akan berarti bahwa fungsi async tidak pernah mendapat manfaat dari penghapusan seumur hidup (kecuali kami melakukan sesuatu yang lebih tidak biasa seperti memiliki aturan penghapusan seumur hidup bekerja secara berbeda untuk fungsi async dan fungsi lainnya).

Kami memutuskan bahwa mengingat betapa verbose dan terus terang membingungkan jenis pengembalian luar sebenarnya akan menulis, itu tidak sebanding dengan sinyal tambahan bahwa ini mengembalikan masa depan untuk meminta pengguna untuk menulisnya.

Pemesanan destruktor

Urutan destruktor dalam konteks async sama seperti dalam konteks non-async. Aturan yang tepat agak rumit dan di luar cakupan di sini, tetapi secara umum, nilai dihancurkan ketika keluar dari ruang lingkup. Namun, ini berarti bahwa mereka terus ada selama beberapa waktu setelah digunakan sampai dibersihkan. Jika waktu itu termasuk pernyataan menunggu, barang-barang itu perlu dipertahankan di masa depan sehingga penghancurnya dapat dijalankan pada waktu yang tepat.

Kita dapat, sebagai pengoptimalan untuk ukuran status masa depan, sebagai gantinya mengurutkan ulang destruktor menjadi lebih awal dalam beberapa atau semua konteks (misalnya, argumen fungsi yang tidak digunakan dapat segera dihapus, alih-alih disimpan dalam status masa depan). Namun, kami memutuskan untuk tidak melakukan ini. Urutan destruktor dapat menjadi masalah pelik dan membingungkan bagi pengguna, dan terkadang sangat signifikan untuk semantik program. Kami telah memilih untuk mengabaikan pengoptimalan ini demi menjamin pengurutan destruktor yang semudah mungkin - pengurutan destruktor yang sama jika semua kata kunci async dan menunggu dihapus.

(Suatu hari, kami mungkin tertarik untuk mengejar cara menandai destruktor sebagai murni dan dapat dipesan ulang. Itu adalah pekerjaan desain masa depan yang memiliki implikasi yang tidak terkait dengan async/menunggu juga.)

Referensi:

Tunggu sintaks operator

Satu penyimpangan utama dari fitur async/await bahasa lain adalah sintaks dari operator menunggu kami. Ini telah menjadi subjek dari banyak diskusi, lebih dari keputusan lain yang kami buat dalam desain Rust.

Sejak 2015, Rust telah memiliki operator ? postfix untuk penanganan kesalahan ergonomis. Jauh sebelum 1.0, Rust juga memiliki operator . postfix untuk akses field dan pemanggilan metode. Karena kasus penggunaan inti untuk futures adalah untuk melakukan semacam IO, sebagian besar futures mengevaluasi ke Result dengan beberapa
semacam kesalahan. Ini berarti bahwa dalam praktiknya, hampir setiap operasi menunggu diurutkan dengan ? atau pemanggilan metode setelahnya. Mengingat prioritas standar untuk operator awalan dan pascafiksasi, ini akan menyebabkan hampir setiap operator menunggu ditulis (await future)? , yang kami anggap sangat tidak ergonomis.

Oleh karena itu, kami memutuskan untuk menggunakan sintaks postfix, yang tersusun sangat baik dengan operator ? dan . . Setelah mempertimbangkan banyak opsi sintaksis yang berbeda, kami memilih untuk menggunakan operator . diikuti dengan kata kunci menunggu.

Referensi:

Mendukung eksekutor tunggal dan multithreaded

Rust dirancang untuk membuat penulisan program bersamaan dan paralel lebih mudah tanpa membebankan biaya pada orang yang menulis program yang berjalan pada satu utas. Sangat penting untuk dapat menjalankan fungsi async baik pada pelaksana singlethreaded dan pelaksana multithreaded. Perbedaan utama antara kedua kasus penggunaan ini adalah bahwa pelaksana multithreaded akan mengikat futures yang dapat mereka hasilkan Send , dan pelaksana singlethreaded tidak akan.

Mirip dengan perilaku sintaks impl Trait , fungsi async "membocorkan" ciri-ciri otomatis masa depan yang mereka kembalikan. Artinya, selain mengamati bahwa tipe pengembalian luar adalah masa depan, pemanggil juga dapat mengamati apakah tipe itu Kirim atau Sinkronkan, berdasarkan pemeriksaan tubuhnya. Ini berarti bahwa ketika tipe kembali dari async fn dijadwalkan ke pelaksana multithread, ia dapat memeriksa apakah ini aman atau tidak. Namun, jenis ini tidak diperlukan untuk Kirim, dan sebagainya pengguna pada pelaksana singlethreaded dapat mengambil keuntungan dari primitif singlethreaded lebih performant.

Ada beberapa kekhawatiran bahwa ini tidak akan bekerja dengan baik ketika fungsi async diperluas ke dalam metode, tetapi setelah beberapa diskusi diputuskan bahwa situasinya tidak akan berbeda secara signifikan.

Referensi:

Pemblokir stabilisasi yang dikenal

ukuran negara

Masalah: #52924

Cara transformasi async ke mesin keadaan saat ini diimplementasikan sama sekali tidak optimal, menyebabkan keadaan menjadi jauh lebih besar dari yang diperlukan. Itu mungkin, karena ukuran status benar-benar tumbuh secara superlinear, untuk memicu stack overflows pada tumpukan nyata saat ukuran status tumbuh lebih besar dari ukuran utas sistem normal. Memperbaiki codegen ini agar ukurannya lebih masuk akal, setidaknya tidak cukup buruk untuk menyebabkan stack overflow dalam penggunaan normal, adalah perbaikan bug pemblokiran.

Beberapa masa pakai dalam fungsi asinkron

Edisi: #56238

fungsi async harus dapat memiliki beberapa masa pakai dalam tanda tangannya, yang semuanya "ditangkap" di masa mendatang fungsi tersebut dievaluasi saat dipanggil. Namun, penurunan saat ini ke impl Future di dalam kompiler tidak mendukung beberapa masa pakai input; refactor yang lebih dalam diperlukan untuk membuatnya
pekerjaan ini. Karena pengguna sangat mungkin untuk menulis fungsi dengan beberapa masukan (mungkin semua yang dihilangkan), ini adalah perbaikan bug pemblokiran.

Masalah pemblokiran lainnya:

Label

Pekerjaan masa depan

Semua ini dikenal dan ekstensi prioritas sangat tinggi untuk MVP yang ingin kami kerjakan segera setelah kami mengirimkan versi awal async/await.

Penutupan asinkron

Di RFC awal, kami juga mendukung pengubah async sebagai pengubah pada literal penutupan, membuat fungsi asinkron anonim. Namun, pengalaman menggunakan fitur ini menunjukkan bahwa masih ada sejumlah pertanyaan desain yang harus diselesaikan sebelum kami merasa nyaman untuk menstabilkan kasus penggunaan ini:

  1. Sifat penangkapan variabel menjadi lebih rumit dalam penutupan asinkron dan memerlukan beberapa dukungan sintaksis.
  2. Abstraksi melalui fungsi asinkron dengan masa pakai input saat ini tidak dimungkinkan dan mungkin memerlukan beberapa dukungan bahasa atau pustaka tambahan.

Dukungan tanpa STD

Implementasi operator menunggu saat ini membutuhkan TLS untuk meneruskan waker ke bawah saat polling masa depan batin. Ini pada dasarnya adalah "peretasan" untuk membuat sintaks bekerja pada sistem dengan TLS sesegera mungkin. Dalam jangka panjang, kami tidak memiliki niat untuk berkomitmen pada penggunaan TLS ini, dan lebih memilih untuk meneruskan waker sebagai argumen fungsi normal. Namun, ini memerlukan perubahan yang lebih dalam pada kode pembuatan mesin status sehingga dapat menangani pengambilan argumen.

Meskipun kami tidak memblokir penerapan perubahan ini, kami menganggapnya sebagai prioritas tinggi karena mencegah penggunaan async/menunggu pada sistem tanpa dukungan TLS. Ini adalah masalah implementasi murni: tidak ada desain sistem yang memerlukan penggunaan TLS.

Metode sifat asinkron

Saat ini kami tidak mengizinkan fungsi atau metode terkait asinkron dalam sifat; ini adalah satu-satunya tempat di mana Anda dapat menulis fn tetapi tidak async fn . Metode async jelas akan menjadi abstraksi yang kuat dan kami ingin mendukungnya.

Metode async secara fungsional akan diperlakukan sebagai metode yang mengembalikan tipe terkait yang akan diterapkan di masa mendatang; setiap metode async akan menghasilkan tipe masa depan yang unik untuk mesin status yang diterjemahkan oleh metode itu.

Namun, karena masa depan itu akan menangkap semua input, setiap masa pakai input atau parameter tipe perlu ditangkap dalam status itu juga. Ini setara dengan konsep yang disebut tipe terkait generik , fitur yang sudah lama kita inginkan tetapi belum diimplementasikan dengan benar. Dengan demikian, resolusi metode async terkait dengan resolusi tipe terkait generik.

Ada juga masalah desain yang luar biasa. Misalnya, apakah metode async dapat dipertukarkan dengan metode yang mengembalikan tipe masa depan yang akan memiliki tanda tangan yang sama? Selain itu, metode async menghadirkan masalah tambahan seputar ciri-ciri otomatis, karena Anda mungkin perlu meminta masa depan yang dikembalikan oleh beberapa metode async mengimplementasikan sifat otomatis saat Anda mengabstraksi suatu sifat dengan metode async.

Setelah kami memiliki dukungan minimal ini, ada pertimbangan desain lain untuk ekstensi di masa mendatang, seperti kemungkinan membuat metode async "aman objek".

Generator dan generator asinkron

Kami memiliki fitur generator yang tidak stabil menggunakan transformasi mesin keadaan coroutine yang sama untuk mengambil fungsi yang menghasilkan banyak nilai dan mengubahnya menjadi mesin keadaan. Kasus penggunaan yang paling jelas untuk fitur ini adalah membuat fungsi yang dikompilasi ke "iterator," seperti fungsi async yang dikompilasi ke
berjangka. Demikian pula, kita dapat menyusun dua fitur ini untuk membuat generator async - fungsi yang dikompilasi ke "aliran", yang setara dengan iterator async. Ada kasus penggunaan yang sangat jelas untuk ini dalam pemrograman jaringan, yang sering kali melibatkan aliran pesan yang dikirim antar sistem.

Generator memiliki banyak pertanyaan desain terbuka karena merupakan fitur yang sangat fleksibel dengan banyak opsi yang memungkinkan. Desain akhir untuk generator di Rust dalam hal sintaks dan API perpustakaan masih sangat terbuka dan tidak pasti.

A-async-await AsyncAwait-Focus F-async_await I-nominated T-lang disposition-merge finished-final-comment-period

Komentar yang paling membantu

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

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

RFC akan segera digabung.

Semua 58 komentar

@rfcbot penggabungan

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

  • [x] @Centril
  • [x] @cramertj
  • [x] @eddyb
  • [x] @joshtriplett
  • [x] @nikomatsakis
  • [ ] @pnkfelix
  • [x] @scottmcm
  • [x] @withoutboats

Kekhawatiran:

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

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

(Cukup daftarkan pemblokir yang ada dalam laporan di atas untuk memastikan mereka tidak tergelincir)

@rfcbot menyangkut implementasi-kerja-pemblokiran-stabilisasi

Anggota tim ... telah mengusulkan untuk menggabungkan ini

Bagaimana cara menggabungkan masalah Github (bukan permintaan tarik)?

@vi Bot hanya sedikit gila dan tidak memeriksa apakah itu masalah atau PR :) Anda dapat mengganti "merge" dengan "accept" di sini.

Wow, terima kasih atas ringkasan yang komprehensif! Saya hanya mengikuti secara tangensial, tetapi saya sepenuhnya yakin Anda berada di atas segalanya.

@rfcbot diulas

Mungkinkah untuk secara eksplisit menambahkan "Masalah Triage AsyncAwait-Unclear" ke pemblokir stabilisasi (dan/atau mendaftarkan kekhawatiran untuk itu)?

Saya punya https://github.com/rust-lang/rust/issues/60414 yang menurut saya penting (jelas, ini bug saya :p), dan ingin setidaknya secara eksplisit ditangguhkan sebelum stabilisasi :)

Saya hanya ingin menyampaikan terima kasih kepada komunitas atas upaya yang telah dilakukan tim Rust dalam fitur ini! Ada banyak desain, diskusi, dan beberapa gangguan dalam komunikasi, tetapi setidaknya saya, dan semoga banyak lainnya, merasa yakin bahwa melalui semua itu kami telah menemukan solusi terbaik untuk Rust. :tada:

(Yang mengatakan, saya ingin melihat penyebutan masalah dengan menjembatani ke API sistem berbasis penyelesaian dan pembatalan asinkron di masa mendatang. TL;DR mereka masih harus melewati buffer yang dimiliki. Ini masalah perpustakaan, tapi satu dengan menyebutkan.)

Saya juga ingin melihat penyebutan masalah dengan API berbasis penyelesaian. (lihat utas internal ini untuk konteksnya) Mempertimbangkan IOCP dan pengenalan io_uring , yang mungkin menjadi Cara untuk async IO di Linux, saya pikir penting untuk memiliki cara yang jelas untuk menanganinya. Ide drop async hipotetis IIUC tidak dapat diimplementasikan dengan aman, dan melewati buffer yang dimiliki akan kurang nyaman dan berpotensi kurang berkinerja (misalnya karena lokasi yang lebih buruk atau karena salinan tambahan).

@newpavlov Saya telah menerapkan hal serupa untuk Fuchsia, dan sangat mungkin dilakukan tanpa async drop. Ada beberapa rute berbeda untuk melakukan ini, seperti menggunakan pengumpulan sumber daya di mana memperoleh sumber daya berpotensi harus menunggu beberapa pekerjaan pembersihan selesai pada sumber daya lama. API berjangka saat ini dapat dan telah digunakan untuk memecahkan masalah ini secara efektif dalam sistem produksi.

Namun, masalah ini adalah tentang stabilisasi async/await, yang ortogonal dengan desain API berjangka, yang telah distabilkan. Jangan ragu untuk mengajukan pertanyaan lebih lanjut atau membuka masalah untuk diskusi tentang repo futures-rs.

@Ekleog

Mungkinkah untuk secara eksplisit menambahkan "Masalah Triage AsyncAwait-Unclear" ke pemblokir stabilisasi (dan/atau mendaftarkan kekhawatiran untuk itu)?

Yup, itu adalah sesuatu yang kami lakukan setiap minggu. WRT masalah khusus itu (#60414), saya yakin ini penting dan akan senang melihatnya diperbaiki, tetapi kami belum dapat memutuskan apakah itu harus memblokir stabilisasi atau tidak, terutama karena sudah dapat diamati di -> impl Trait fungsi.

@cramertj Terima kasih! Saya pikir masalah #60414 pada dasarnya adalah "kesalahan dapat muncul dengan sangat cepat sekarang", sementara dengan -> impl Trait sepertinya tidak ada yang menyadarinya sebelumnya -- maka tidak apa-apa jika tetap ditangguhkan, beberapa masalah harus :) (FWIW itu muncul dalam kode alami dalam fungsi di mana saya mengembalikan () di suatu tempat dan T::Assoc di tempat lain, yang IIRC membuat saya tidak dapat mengkompilasinya -- belum memeriksa kodenya sejak membuka #60414, jadi mungkin ingatan saya salah)

@Ekleog Ya itu masuk akal! Saya pasti bisa melihat mengapa ini menyebalkan-- Saya telah membuat aliran zulip untuk menyelami lebih dalam masalah spesifik itu.

EDIT: sudahlah, saya melewatkan target 1.38 .

@cramertj

Ada beberapa rute berbeda untuk melakukan ini, seperti menggunakan pengumpulan sumber daya di mana memperoleh sumber daya berpotensi harus menunggu beberapa pekerjaan pembersihan selesai pada sumber daya lama.

Bukankah mereka kurang efisien dibandingkan dengan menjaga buffer sebagai bagian dari status masa depan? Perhatian utama saya adalah bahwa desain saat ini tidak akan menjadi nol-biaya (dalam arti bahwa Anda akan dapat membuat kode yang lebih efisien dengan menjatuhkan abstraksi async ) dan kurang ergonomis pada API berbasis penyelesaian, dan ada tidak ada cara yang jelas untuk memperbaikinya. Ini bukan show-stopper dengan cara apa pun, tapi saya pikir penting untuk tidak melupakan kekurangan desain seperti itu, sehingga permintaan untuk menyebutkannya di OP.

@theduke

Tim lang tentu saja dapat menilai ini lebih baik daripada saya, tetapi menunda ke 1.38 untuk memastikan implementasi yang stabil akan tampak jauh lebih masuk akal.

Masalah ini menargetkan 1,38, lihat deskripsi baris pertama.

@huxi terima kasih, saya melewatkan itu. Mengedit komentar saya.

@newpavlov

Bukankah mereka kurang efisien dibandingkan dengan menjaga buffer sebagai bagian dari status masa depan? Perhatian utama saya adalah bahwa desain saat ini tidak akan menjadi nol-biaya (dalam arti bahwa Anda akan dapat membuat kode yang lebih efisien dengan menjatuhkan abstraksi asinkron) dan kurang ergonomis pada API berbasis penyelesaian, dan tidak ada cara yang jelas untuk memperbaikinya. dia. Ini bukan show-stopper dengan cara apa pun, tapi saya pikir penting untuk tidak melupakan kekurangan desain seperti itu, sehingga permintaan untuk menyebutkannya di OP.

Tidak, belum tentu, tetapi mari kita pindahkan diskusi ini ke masalah di utas terpisah, karena tidak terkait dengan stabilisasi async/await.

(Yang mengatakan, saya ingin melihat penyebutan masalah dengan menjembatani ke API sistem berbasis penyelesaian dan pembatalan asinkron di masa mendatang. TL;DR mereka masih harus melewati buffer yang dimiliki. Ini masalah perpustakaan, tapi satu dengan menyebutkan.)

Saya juga ingin melihat penyebutan masalah dengan API berbasis penyelesaian. (lihat utas internal ini untuk konteksnya) Mempertimbangkan IOCP dan pengenalan io_uring, yang mungkin menjadi Cara untuk async IO di Linux, saya pikir penting untuk memiliki cara yang jelas ke depan untuk menanganinya.

Saya setuju dengan Taylor bahwa membahas desain API di ruang masalah ini akan keluar dari topik, tetapi saya ingin membahas satu aspek spesifik dari komentar ini (dan diskusi seputar io_uring ini secara umum) yang relevan dengan async/await stabilization: masalah waktu.

io_uring adalah antarmuka yang akan hadir di Linux tahun ini , 2019. Proyek Rust telah mengerjakan abstraksi berjangka sejak 2015, empat tahun lalu. Pilihan mendasar untuk mendukung jajak pendapat berdasarkan API berbasis penyelesaian terjadi selama 2015 dan 2016. Di RustCamp pada 2015, Carl Lerche berbicara tentang mengapa dia membuat pilihan itu di mio, abstraksi IO yang mendasarinya. Dalam posting blog ini pada tahun 2016, Aaron Turon berbicara tentang manfaat untuk membuat abstraksi tingkat yang lebih tinggi. Keputusan ini dibuat sejak lama dan kami tidak bisa mencapai titik seperti sekarang ini tanpa mereka.

Saran bahwa kita harus meninjau kembali model masa depan yang mendasari kita adalah saran bahwa kita harus kembali ke keadaan kita 3 atau 4 tahun yang lalu, dan memulai dari titik itu. Jenis abstraksi apa yang dapat mencakup model IO berbasis penyelesaian tanpa memperkenalkan overhead untuk primitif tingkat yang lebih tinggi, seperti yang dijelaskan Aaron? Bagaimana kami memetakan model itu ke sintaks yang memungkinkan pengguna menulis "karat normal + anotasi minor" seperti yang dilakukan async/menunggu? Bagaimana kita dapat menangani pengintegrasian itu ke dalam model memori kita, seperti yang telah kita lakukan untuk mesin status ini dengan pin? Mencoba memberikan jawaban atas pertanyaan-pertanyaan ini akan di luar topik untuk utas ini; intinya adalah menjawabnya, dan membuktikan jawaban itu benar, adalah pekerjaan. Apa yang berarti satu dekade padat tahun kerja antara kontributor yang berbeda sejauh ini harus diulang lagi.

Tujuan Rust adalah mengirimkan produk yang dapat digunakan orang, dan itu berarti kami harus mengirimkan . Kami tidak dapat selalu berhenti untuk melihat ke masa depan pada apa yang mungkin menjadi masalah besar tahun depan, dan memulai kembali proses desain kami untuk memasukkannya. Kami melakukan yang terbaik yang kami bisa berdasarkan situasi yang kami hadapi. Jelas dapat membuat frustasi untuk merasa seperti kami hampir tidak melewatkan hal besar, tetapi seperti yang terjadi, kami juga tidak memiliki pandangan penuh a) tentang apa hasil terbaik untuk menangani io_uring akan, b) seberapa penting io_uring dalam ekosistem secara keseluruhan. Kami tidak dapat mengembalikan 4 tahun kerja berdasarkan ini.

Sudah ada batasan Rust yang serupa, bahkan mungkin lebih serius, di ruang lain. Saya ingin menyoroti satu yang saya lihat dengan Nick Fitzgerald musim gugur yang lalu: integrasi GC wasm. Rencana untuk menangani objek yang dikelola di wasm pada dasarnya adalah untuk membagi ruang memori, sehingga mereka ada di ruang alamat yang terpisah dari objek yang tidak dikelola (memang, suatu hari nanti di banyak ruang alamat yang terpisah). Model memori Rust sama sekali tidak dirancang untuk menangani ruang alamat yang terpisah, dan setiap kode tidak aman yang berhubungan dengan memori tumpukan saat ini mengasumsikan hanya ada 1 ruang alamat. Meskipun kami telah membuat sketsa solusi teknis yang melanggar dan secara teknis-nonbreaking-tetapi-sangat-mengganggu, jalan yang paling mungkin ke depan adalah menerima bahwa cerita wasm GC kami mungkin tidak sepenuhnya optimal , karena kami berurusan dengan keterbatasan Rust sebagai itu ada.

Aspek menarik yang kami stabilkan di sini adalah kami membuat struct referensi mandiri tersedia dari kode aman. Apa yang membuat ini menarik adalah bahwa dalam Pin<&mut SelfReferentialGenerator> , kami memiliki referensi yang dapat diubah (disimpan sebagai bidang di Pin ) yang menunjuk ke seluruh status generator, dan kami memiliki pointer di dalam status tersebut yang menunjuk ke bagian lain dari negara. Penunjuk dalam itu alias dengan referensi yang bisa berubah!

Referensi yang bisa berubah, setahu saya, tidak terbiasa untuk benar-benar mengakses bagian dari memori yang ditunjuk oleh pointer-ke-bidang lain. (Khususnya, tidak ada metode clone atau lebih yang akan membaca bidang pointer-to menggunakan pointer lain selain yang referensi sendiri.) Namun, ini semakin dekat untuk memiliki alias referensi yang bisa berubah dengan sesuatu dari apa pun di ekosistem inti, khususnya apa pun yang dikirimkan dengan rustc itu sendiri. "Garis" yang kami kendarai di sini menjadi sangat tipis, dan kami harus berhati-hati agar tidak kehilangan semua pengoptimalan bagus yang ingin kami lakukan berdasarkan referensi yang bisa berubah.

Mungkin ada sedikit yang bisa kita lakukan tentang itu pada saat ini, khususnya karena Pin sudah stabil, tetapi saya merasa perlu untuk menunjukkan bahwa ini akan secara signifikan memperumit aturan apa pun yang akhirnya mengizinkan aliasing dan yang mana bukan. Jika menurut Anda Stacked Borrows rumit, bersiaplah untuk keadaan yang semakin buruk.

Cc https://github.com/rust-lang/unsafe-code-guidelines/issues/148

Referensi yang bisa berubah, setahu saya, tidak terbiasa untuk benar-benar mengakses bagian dari memori yang ditunjuk oleh pointer-ke-bidang lain.

Orang-orang telah berbicara tentang membuat semua jenis coroutine ini mengimplementasikan Debug , sepertinya percakapan itu juga harus mengintegrasikan pedoman kode yang tidak aman untuk memastikan apa yang aman untuk di-debug cetak.

Orang-orang telah berbicara tentang membuat semua jenis coroutine ini mengimplementasikan Debug, sepertinya percakapan itu juga harus mengintegrasikan pedoman kode yang tidak aman untuk memastikan apa yang aman untuk di-debug cetak.

Memang. Implementasi Debug , jika mencetak bidang yang direferensikan sendiri, kemungkinan akan melarang optimasi berbasis referensi tingkat MIR di dalam generator.

Pembaruan tentang pemblokir:

Kedua pemblokir tingkat tinggi telah membuat kemajuan besar dan mungkin benar-benar selesai (?). Info lebih lanjut dari @cramertj @tmandry dan @nikomatsakis tentang ini akan sangat bagus:

  • Masalah beberapa masa pakai seharusnya telah diperbaiki pada #61775
  • Masalah ukuran lebih ambigu; akan selalu ada lebih banyak pengoptimalan yang harus dilakukan, tetapi saya pikir buah menggantung rendah dari menghindari peningkatan eksponensial yang jelas sebagian besar telah diselesaikan?

Ini meninggalkan dokumentasi dan pengujian sebagai penghambat utama dalam menstabilkan fitur ini. @Centril secara konsisten menyatakan keprihatinan bahwa fitur tersebut tidak diuji atau dipoles dengan baik; @Centril apakah ada di mana pun Anda telah menyebutkan masalah khusus yang dapat diperiksa untuk mendorong fitur ini ke stabilisasi?

Saya tidak yakin apakah ada yang mengemudi dokumentasi. Siapa pun yang ingin fokus pada peningkatan dokumentasi in-tree dalam buku, referensi, dll akan melakukan layanan yang luar biasa! Dokumentasi di luar pohon seperti di repo berjangka atau areweasyncyet memiliki sedikit waktu ekstra.

Mulai hari ini kami memiliki 6 minggu hingga beta dipotong, jadi katakanlah kami memiliki 4 minggu (hingga 1 Agustus) untuk menyelesaikan hal-hal ini agar yakin kami tidak akan tergelincir 1,38.

Masalah ukuran lebih ambigu; akan selalu ada lebih banyak pengoptimalan yang harus dilakukan, tetapi saya pikir buah menggantung rendah dari menghindari peningkatan eksponensial yang jelas sebagian besar telah diselesaikan?

Saya percaya begitu, dan beberapa lainnya juga ditutup baru-baru ini; tetapi ada masalah pemblokiran lainnya .

@Centril apakah ada di mana pun Anda telah menyebutkan masalah khusus yang dapat diperiksa untuk mendorong fitur ini ke stabilisasi?

Ada kertas dropbox dengan daftar hal-hal yang ingin kami uji dan ada https://github.com/rust-lang/rust/issues/62121 . Selain itu, saya akan mencoba meninjau kembali area yang menurut saya kurang diuji secepatnya. Yang mengatakan, beberapa daerah sekarang diuji dengan cukup baik.

Siapa pun yang ingin fokus pada peningkatan dokumentasi in-tree dalam buku, referensi, dll akan melakukan layanan yang luar biasa!

Memang; Saya akan senang untuk meninjau PR ke referensi. Juga cc @ehuss.


Saya juga ingin memindahkan async unsafe fn dari MVP ke gerbang fiturnya sendiri karena menurut saya a) jarang digunakan, b) tidak diuji dengan baik, c) seolah-olah berperilaku aneh karena .await titik bukan di mana Anda menulis unsafe { ... } dan ini dapat dimengerti dari "POV implementasi yang bocor" tetapi tidak terlalu banyak dari POV efek, d) telah melihat sedikit diskusi dan tidak termasuk dalam RFC atau laporan ini, dan e) kami melakukan ini dengan const fn dan itu bekerja dengan baik. (Saya bisa menulis fitur gating PR)

Saya baik-baik saja dengan destabilisasi async unsafe fn , meskipun saya skeptis kita berakhir dengan desain yang berbeda dari yang sekarang. Tetapi tampaknya bijaksana untuk memberi kita waktu untuk mencari tahu!

Saya membuat https://github.com/rust-lang/rust/issues/62500 untuk memindahkan async unsafe fn ke gerbang fitur yang berbeda dan mendaftarkannya sebagai pemblokir. Kami mungkin harus membuat masalah pelacakan yang tepat juga, saya kira.

Saya sangat skeptis bahwa kami akan mencapai desain yang berbeda untuk async unsafe fn dan saya terkejut dengan keputusan untuk tidak memasukkannya dalam putaran awal stabilisasi. Saya telah menulis sejumlah async fn s yang tidak aman dan akan membuat mereka async fn really_this_function_is_unsafe() atau sesuatu, saya kira. Ini tampak seperti regresi dalam harapan dasar yang dimiliki pengguna Rust dalam hal kemampuan untuk mendefinisikan fungsi yang memerlukan unsafe { ... } untuk dipanggil. Namun gerbang fitur lain akan berkontribusi pada kesan bahwa async / await belum selesai.

@cramertj sepertinya kita harus berdiskusi! Saya membuat topik Zulip untuk itu , untuk mencoba dan menjaga agar masalah pelacakan ini tidak terlalu berlebihan.

Mengenai ukuran di masa mendatang, kasus yang memengaruhi setiap poin await dioptimalkan. Masalah terakhir yang tersisa yang saya ketahui adalah #59087, di mana setiap pinjaman masa depan sebelum menunggu dapat menggandakan ukuran yang dialokasikan untuk masa depan itu. Ini sangat disayangkan, tetapi masih sedikit lebih baik daripada tempat kami sebelumnya.

Saya punya ide tentang cara memperbaiki masalah itu, tetapi kecuali ini jauh lebih umum daripada yang saya sadari, itu mungkin tidak boleh menjadi pemblokiran untuk MVP yang stabil.

Yang mengatakan, saya masih perlu melihat dampak dari pengoptimalan ini di Fuchsia (yang telah diblokir untuk sementara tetapi harus dibersihkan hari ini atau besok). Sangat mungkin kami akan menemukan lebih banyak kasus, dan perlu memutuskan apakah ada di antara mereka yang harus diblokir.

@cramertj (Pengingat: Saya menggunakan async/menunggu dan ingin menstabilkan ASAP) Argumen Anda terdengar seperti argumen untuk menunda stabilisasi async/menunggu, bukan untuk menstabilkan async unsafe sekarang tanpa eksperimen dan pemikiran yang tepat.

Terutama karena tidak termasuk dalam RFC, dan berpotensi memicu badai "sifat impl dalam posisi argumen" lain jika dipaksa keluar dengan cara ini.

[Catatan tambahan yang tidak benar-benar layak didiskusikan di sini: untuk "Namun gerbang fitur lain akan berkontribusi pada kesan bahwa async/await belum selesai", saya telah menemukan bug setiap beberapa jam menggunakan async/menunggu, disebarkan oleh beberapa bulan secara sah diperlukan oleh tim rustc untuk memperbaikinya, dan itu adalah hal yang membuat saya mengatakan itu belum selesai. Yang terakhir diperbaiki beberapa hari yang lalu, dan saya sangat berharap saya tidak akan menemukan yang lain ketika saya mencoba lagi untuk mengkompilasi kode saya dengan rustc yang lebih baru, tapi…]

Argumen Anda terdengar seperti argumen untuk menunda stabilisasi async/menunggu, bukan untuk menstabilkan async yang tidak aman saat ini tanpa eksperimen dan pemikiran yang tepat.

Tidak, itu bukan argumen untuk itu. Saya percaya bahwa async unsafe sudah siap, dan tidak dapat membayangkan desain lain untuk itu. Saya percaya hanya ada konsekuensi negatif untuk tidak memasukkannya dalam rilis awal ini. Saya tidak percaya bahwa menunda async / await secara keseluruhan, atau async unsafe secara khusus, akan menghasilkan hasil yang lebih baik.

tidak bisa membayangkan desain lain untuk itu

Desain alternatif, meskipun yang pasti membutuhkan ekstensi yang rumit: async unsafe fn adalah unsafe ke .await , bukan ke call() . Alasan di balik ini adalah bahwa _nothing unsafe can be done_ pada titik di mana async fn dipanggil dan menciptakan impl Future . Semua langkah itu adalah memasukkan data ke dalam struct (sebagai akibatnya, semua async fn adalah const untuk dipanggil). Titik ketidakamanan yang sebenarnya adalah memajukan masa depan dengan poll .

(imho, jika unsafe langsung, unsafe async fn lebih masuk akal, dan jika unsafe tertunda, async unsafe fn lebih masuk akal.)

Tentu saja, jika kita tidak pernah mendapatkan cara untuk mengatakan misalnya unsafe Future mana semua metode Future tidak aman untuk dipanggil, maka "mengangkat" unsafe ke pembuatan impl Future , dan kontrak unsafe itu untuk menggunakan masa depan yang dihasilkan dengan cara yang aman. Tapi ini juga bisa hampir sepele dilakukan tanpa unsafe async fn hanya dengan "desugaring" secara manual ke blok async : unsafe fn os_stuff() -> impl Future { async { .. } } .

Selain itu, ada pertanyaan apakah sebenarnya ada cara untuk memiliki invarian yang perlu ditahan setelah poll dimulai yang tidak perlu ditahan saat pembuatan. Ini adalah pola umum di Rust bahwa Anda menggunakan konstruktor unsafe ke tipe yang aman (misalnya Vec::from_raw_parts ). Tapi kuncinya adalah setelah konstruksi, tipe _cannot_ disalahgunakan; lingkup unsafe berakhir. Lingkup ketidakamanan ini adalah kunci dari jaminan Rust. Jika Anda memperkenalkan unsafe async fn yang menyimpan impl Future dengan persyaratan bagaimana/kapan polling, lalu berikan ke kode aman, kode aman itu tiba-tiba berada di dalam penghalang tidak aman Anda. Dan ini _sangat_ mungkin terjadi segera setelah Anda menggunakan masa depan ini dengan cara apa pun selain segera menunggunya, karena kemungkinan akan melalui _beberapa_ kombinator eksternal.

Saya kira TL;DR dari ini adalah pasti ada sudut async unsafe fn yang harus didiskusikan dengan baik sebelum menstabilkannya, terutama dengan arah const Trait berpotensi diperkenalkan (saya punya blog draft posting tentang generalisasi ini ke "sistem 'efek' yang lemah" dengan kata kunci fn -modifying). Namun, unsafe async fn mungkin sebenarnya cukup jelas tentang "pemesanan"/"penempatan" unsafe untuk menstabilkan.

Saya percaya bahwa sifat unsafe Future berbasis efek tidak hanya di luar jangkauan dari apa pun yang kita tahu bagaimana mengekspresikannya dalam bahasa atau kompiler hari ini, tetapi pada akhirnya akan menjadi desain yang lebih buruk karena efek tambahan- polimorfisme yang akan membutuhkan kombinator untuk memiliki.

tidak ada yang tidak aman yang dapat dilakukan pada titik di mana async fn dipanggil dan menciptakan masa depan impl. Semua langkah itu adalah memasukkan data ke dalam struct (sebagai akibatnya, semua async fn adalah const untuk dipanggil). Titik sebenarnya dari ketidakamanan adalah memajukan masa depan dengan jajak pendapat.

Memang benar bahwa karena async fn tidak dapat menjalankan kode pengguna apa pun sebelum .await ed, setiap perilaku yang tidak ditentukan kemungkinan akan tertunda hingga .await dipanggil. Saya pikir, ada perbedaan penting antara poin UB dan poin unsafe ty. Titik sebenarnya dari unsafe ty adalah di mana pun pembuat API memutuskan bahwa pengguna perlu berjanji bahwa serangkaian invarian yang tidak dapat diverifikasi secara statis terpenuhi, bahkan jika hasil dari invarian yang dilanggar tidak akan menyebabkan UB sampai nanti di beberapa kode aman lainnya. Salah satu contoh umum dari ini adalah fungsi unsafe untuk membuat nilai yang mengimplementasikan sifat dengan metode aman (persis seperti apa ini). Saya telah melihat ini digunakan untuk memastikan bahwa misalnya Visitor -trait-implementing types yang implementasinya bergantung pada unsafe invariants dapat digunakan dengan baik, dengan meminta unsafe untuk membangun tipe tersebut. Contoh lain termasuk hal-hal seperti slice::from_raw_parts , yang dengan sendirinya tidak akan menyebabkan UB (mengesampingkan validitas jenis invarian), tetapi akses ke irisan yang dihasilkan akan.

Saya tidak percaya bahwa async unsafe fn mewakili kasus yang unik atau menarik di sini-- ini mengikuti pola yang mapan untuk melakukan perilaku unsafe belakang antarmuka yang aman dengan memerlukan unsafe konstruktor.

@cramertj Fakta Anda bahkan harus berdebat untuk ini (dan saya tidak menyarankan saya pikir solusi saat ini adalah solusi yang buruk, atau bahwa saya memiliki ide yang lebih baik) berarti, bagi saya, bahwa debat ini harus di a tempat orang yang peduli tentang karat harus mengikuti: repositori RFC.

Sebagai pengingat, kutipan dari readme-nya:

Anda harus mengikuti proses ini jika [...] :

  • Setiap perubahan semantik atau sintaksis ke bahasa yang bukan merupakan perbaikan bug.
  • [... dan juga hal-hal yang tidak dikutip]

Saya tidak mengatakan perubahan apa pun pada desain saat ini akan terjadi. Sebenarnya, memikirkannya beberapa menit membuat saya berpikir itu mungkin desain terbaik yang bisa saya pikirkan. Tetapi proseslah yang memungkinkan kami untuk menghindari keyakinan kami menjadi bahaya bagi Rust, dan kami kehilangan kebijaksanaan banyak orang yang mengikuti repositori RFC tetapi tidak membaca setiap masalah dengan tidak mengikuti proses di sini.

Terkadang tidak mengikuti proses mungkin masuk akal. Di sini saya tidak melihat adanya urgensi yang mengharuskan pengabaian proses hanya untuk menghindari penundaan FCP selama 2 minggu.

Jadi tolong biarkan rust jujur ​​dengan komunitasnya tentang janji yang diberikannya di readme-nya sendiri, dan simpan fitur itu di bawah gerbang fitur sampai setidaknya ada RFC yang diterima dan semoga lebih banyak menggunakannya di alam liar. Baik itu seluruh gerbang fitur async/menunggu atau hanya gerbang fitur async yang tidak aman, saya tidak peduli, tetapi jangan menstabilkan sesuatu yang (AFAIK) terlihat sedikit penggunaan di luar async-wg dan hampir tidak diketahui di masyarakat secara keseluruhan.

Saya menulis pass pertama pada bahan referensi untuk buku ini. Sepanjang jalan, saya perhatikan bahwa RFC async-await mengatakan bahwa perilaku operator ? belum ditentukan. Namun tampaknya berfungsi dengan baik di blok async ( playground ). Haruskah kita memindahkannya ke gerbang fitur terpisah? Atau apakah itu diselesaikan di beberapa titik? Saya tidak melihatnya di laporan stabilisasi, tapi mungkin saya melewatkannya.

(Saya juga menanyakan pertanyaan ini di Zulip dan lebih suka tanggapan di sana, karena lebih mudah untuk saya kelola.)

Ya, itu dibahas dan diselesaikan bersama dengan perilaku return , break , continue et. Al. yang semuanya melakukan "satu-satunya hal yang mungkin" dan berperilaku seperti yang mereka lakukan di dalam penutupan.

let f = unsafe { || {...} }; juga aman untuk dipanggil dan IIRC itu setara dengan memindahkan unsafe ke dalam penutupan.
Hal yang sama untuk unsafe fn foo() -> impl Fn() { || {...} } .

Ini, bagi saya, cukup preseden untuk "hal yang tidak aman terjadi setelah meninggalkan ruang lingkup unsafe ".

Hal yang sama berlaku untuk tempat lain. Seperti yang telah dikemukakan sebelumnya, unsafe tidak selalu menjadi tempat UB yang potensial. Contoh:

    let mut vec: Vec<u32> = Vec::new();

    unsafe { vec.set_len(100); }      // <- unsafe

    let val = vec.get(5).unwrap();     // <- UB
    println!("{}", val);

Sepertinya kesalahpahaman tentang tidak aman bagi saya - tidak aman tidak menandai bahwa "operasi yang tidak aman terjadi di dalam sini"- itu menandai "Saya menjamin bahwa saya menjunjung tinggi invarian yang diperlukan di sini." Meskipun Anda dapat mempertahankan invarian pada titik tunggu, karena tidak melibatkan parameter variabel, ini bukan situs yang sangat jelas untuk memeriksa apakah Anda mendukung invarian. Ini jauh lebih masuk akal, dan jauh lebih konsisten dengan cara kerja semua abstraksi tidak aman kami, untuk menjamin Anda menjunjung tinggi invarian di situs panggilan.

Ini terkait dengan mengapa pemikiran tentang tidak aman sebagai efek mengarah pada intuisi yang tidak akurat (seperti yang dikatakan Ralf ketika gagasan itu pertama kali dikemukakan tahun lalu). Ketidakamanan secara khusus, sengaja, tidak menular. Meskipun Anda dapat menulis fungsi tidak aman yang memanggil fungsi tidak aman lainnya dan hanya meneruskan invarian mereka ke tumpukan panggilan, ini sama sekali bukan cara normal yang tidak aman digunakan, dan sebenarnya ini adalah penanda sintaksis yang digunakan untuk menentukan kontrak pada nilai dan memeriksanya secara manual. Anda menjunjung tinggi mereka.

Jadi bukan berarti setiap keputusan desain membutuhkan RFC keseluruhan, tetapi kami telah berupaya untuk memberikan kejelasan dan struktur yang lebih baik tentang bagaimana keputusan dibuat. Daftar poin keputusan utama dalam pembukaan masalah ini adalah contohnya. Dengan menggunakan alat yang tersedia bagi kami, saya ingin mencoba titik konsensus terstruktur seputar masalah async fns yang tidak aman ini, jadi ini adalah posting ringkasan dengan jajak pendapat.

async unsafe fn

async unsafe fns adalah fungsi async yang hanya dapat dipanggil di dalam blok yang tidak aman. Di dalam tubuh mereka diperlakukan sebagai ruang lingkup yang tidak aman. Desain alternatif utama akan membuat async tidak aman fns tidak aman untuk menunggu, bukan untuk panggilan. Ada sejumlah alasan kuat untuk memilih desain yang tidak aman untuk dipanggil:

  1. Ini konsisten secara sintaksis dengan perilaku fns tidak aman non-async, yang juga tidak aman untuk dipanggil.
  2. Ini lebih konsisten dengan cara kerja yang tidak aman secara umum. Fungsi tidak aman adalah abstraksi yang bergantung pada beberapa invarian yang dipertahankan oleh pemanggilnya. Artinya, ini bukan tentang menandai "di mana operasi yang tidak aman terjadi" tetapi "di mana invarian dijamin ditegakkan." Jauh lebih masuk akal untuk memeriksa bahwa invarian ditegakkan di situs panggilan, di mana argumen sebenarnya ditentukan, daripada di situs menunggu, terpisah dari saat argumen dipilih dan diverifikasi. Ini sangat normal untuk fungsi tidak aman secara umum, yang sering kali menentukan beberapa status yang diharapkan benar oleh fungsi aman lainnya
  3. Ini lebih konsisten dengan gagasan desugaring dari tanda tangan async fn, di mana Anda dapat memodelkan tanda tangan sebagai setara dengan menghapus pengubah async dan membungkus tipe pengembalian di masa mendatang.
  4. Alternatif ini tidak layak untuk diterapkan dalam waktu dekat atau menengah (artinya beberapa tahun). Tidak ada cara untuk menciptakan masa depan yang tidak aman untuk polling dalam bahasa Rust yang dirancang saat ini. Beberapa jenis "tidak aman sebagai efek" akan menjadi perubahan besar yang akan memiliki implikasi jauh mencapai dan kebutuhan untuk berurusan dengan bagaimana itu adalah kompatibel dengan tidak aman seperti yang ada saat ini sudah (seperti, fungsi yang tidak aman normal dan blok). Menambahkan async unsafe fns tidak secara signifikan mengubah lanskap itu, sedangkan async unsafe fns di bawah interpretasi unsafe saat ini memiliki kasus penggunaan praktis yang nyata dalam waktu dekat dan menengah.

@rfcbot tanya lang "Apakah kami menerima stabilisasi async unsafe fn sebagai async fn yang tidak aman untuk dipanggil?"

Saya tidak tahu bagaimana membuat polling dengan rfcbot tapi setidaknya saya sudah menominasikannya.

Anggota tim @withoutboats telah meminta tim: T-lang, untuk konsensus tentang:

"Apakah kami menerima stabilisasi async unsafe fn sebagai async fn yang tidak aman untuk dipanggil?"

  • [x] @Centril
  • [x] @cramertj
  • [x] @eddyb
  • [ ] @joshtriplett
  • [x] @nikomatsakis
  • [ ] @pnkfelix
  • [ ] @scottmcm
  • [x] @withoutboats

@tanpa perahu

Saya ingin mencoba titik konsensus terstruktur seputar masalah fns async yang tidak aman ini, jadi ini adalah posting ringkasan dengan jajak pendapat.

Terima kasih atas tulisannya. Diskusi ini membuat saya yakin bahwa async unsafe fn seperti yang berfungsi di malam hari hari ini berperilaku benar. (Beberapa tes mungkin harus ditambahkan karena terlihat jarang.) Juga, bisakah Anda mengubah laporan di bagian atas dengan bagian dari laporan Anda + deskripsi tentang bagaimana async unsafe fn berperilaku?

Ini lebih konsisten dengan cara kerja yang tidak aman secara umum. Fungsi tidak aman adalah abstraksi yang bergantung pada beberapa invarian yang dipertahankan oleh pemanggilnya. Artinya, ini bukan tentang menandai "di mana operasi yang tidak aman terjadi" tetapi "di mana invarian dijamin ditegakkan." Jauh lebih masuk akal untuk memeriksa bahwa invarian ditegakkan di situs panggilan, di mana argumen sebenarnya ditentukan, daripada di situs menunggu, terpisah dari saat argumen dipilih dan diverifikasi. Ini sangat normal untuk fungsi tidak aman secara umum, yang sering kali menentukan beberapa status yang diharapkan benar oleh fungsi aman lainnya

Sebagai seseorang yang tidak terlalu memperhatikan, saya setuju dan berpikir solusinya di sini adalah dokumentasi yang bagus.

Saya mungkin melenceng di sini, tetapi mengingat itu

  • futures bersifat kombinatorik, sangat mendasar bahwa mereka dapat dikomposisi.
  • titik menunggu di dalam implementasi masa depan umumnya merupakan detail implementasi yang tidak terlihat.
  • masa depan sangat jauh dari konteks eksekusi, dengan pengguna yang sebenarnya mungkin di antara, bukan di root.

bagi saya tampaknya invarian tergantung pada penggunaan/perilaku menunggu tertentu berada di antara ide yang buruk dan tidak mungkin untuk memerintah dengan aman.

Jika ada kasus di mana nilai output yang ditunggu adalah apa yang terlibat dalam menegakkan invarian, saya berasumsi masa depan hanya dapat memiliki output yang merupakan pembungkus yang membutuhkan akses yang tidak aman, seperti

struct UnsafeOutput<T>(T);
impl<T> UnsafeOutput<T> {
    unsafe fn unwrap(self) -> T { self.0 }
}

Mengingat bahwa unsafe ness sebelum async ness dalam "awal tidak aman" ini, saya akan jauh lebih senang dengan urutan pengubah menjadi unsafe async fn daripada async unsafe fn , karena unsafe (async fn) memetakan lebih jelas ke perilaku itu daripada async (unsafe fn) .

Saya akan dengan senang hati menerimanya, tetapi saya sangat merasa bahwa urutan pembungkus yang diperlihatkan di sini memiliki unsafe di bagian luar, dan urutan pengubah dapat membantu memperjelas hal ini. ( unsafe adalah pengubah ke async fn , bukan async pengubah ke unsafe fn .)

Saya akan dengan senang hati menerimanya, tetapi saya sangat merasa bahwa urutan pembungkus yang diperlihatkan di sini memiliki unsafe di bagian luar, dan urutan pengubah dapat membantu memperjelas hal ini. ( unsafe adalah pengubah ke async fn , bukan async pengubah ke unsafe fn .)

Saya bersama Anda sampai poin terakhir Anda yang dikurung. @withoutboats ' menulis cukup jelas bagi saya bahwa, jika ketidakamanan ditangani di situs panggilan, apa yang sebenarnya Anda miliki adalah unsafe fn (yang kebetulan dipanggil dalam konteks async).

Saya akan mengatakan kami mengecat gudang sepeda async unsafe fn .

Saya pikir async unsafe fn lebih masuk akal, tetapi saya juga berpikir bahwa kita harus secara tata bahasa menerima urutan apa pun di antara async, unsafe, dan const. Tetapi async unsafe fn lebih masuk akal bagi saya dengan gagasan bahwa Anda menghapus async dan memodifikasi tipe pengembalian menjadi "desugar" itu.

Alternatif ini tidak layak untuk diterapkan dalam waktu dekat atau menengah (artinya beberapa tahun). Tidak ada cara untuk menciptakan masa depan yang tidak aman untuk polling dalam bahasa Rust yang dirancang saat ini.

FWIW Saya mengalami masalah serupa yang saya sebutkan di RFC2585 ketika menyangkut penutupan di dalam unsafe fn dan sifat-sifat fungsi. Saya tidak berharap unsafe async fn mengembalikan Future dengan metode poll yang aman, tetapi sebaliknya mengembalikan UnsafeFuture dengan unsafe metode jajak pendapat. (*) Kami kemudian dapat membuat .await juga bekerja pada UnsafeFuture s ketika digunakan di dalam blok unsafe { } , tetapi tidak sebaliknya.

Dua sifat masa depan ini akan menjadi perubahan besar sehubungan dengan apa yang kita miliki saat ini, dan mereka mungkin akan memperkenalkan banyak masalah komposisi. Jadi kapal untuk menjelajahi alternatif mungkin telah berlayar. Terutama karena ini akan berbeda dengan cara kerja sifat Fn hari ini (mis. kami tidak memiliki sifat UnsafeFn atau serupa, dan masalah saya di RFC2585 adalah membuat penutupan di dalam unsafe fn mengembalikan penutupan yang impls Fn() , yaitu aman untuk dipanggil, meskipun penutupan ini dapat memanggil fungsi yang tidak aman.

Menciptakan Masa Depan yang "tidak aman" atau penutupan bukanlah masalahnya, masalahnya adalah memanggil mereka tanpa membuktikan bahwa hal itu aman, terutama ketika tipe mereka tidak mengatakan bahwa ini harus dilakukan.

(*) Kami dapat memberikan blanket impl UnsafeFuture untuk semua Future s, dan kami juga dapat menyediakan UnsafeFuture metode unsafe untuk "membuka" itu sendiri sebagai Future yang aman untuk poll .

Ini dua sen saya:

  • Penjelasan @cramertj (https://github.com/rust-lang/rust/issues/62149#issuecomment-510166207) meyakinkan saya bahwa unsafe fungsi async adalah desain yang tepat.
  • Saya lebih suka urutan tetap kata kunci unsafe dan async
  • Saya sedikit lebih suka memesan unsafe async fn karena urutannya tampak lebih logis. Mirip dengan "mobil listrik cepat" vs "mobil listrik cepat". Terutama karena async fn mengubah gula menjadi fn . Jadi, masuk akal jika kedua kata kunci itu bersebelahan.

Saya pikir let f = unsafe { || { ... } } harus membuat f aman, sifat UnsafeFn tidak boleh diperkenalkan, dan a priori .await ing dan async unsafe fn seharusnya aman. Setiap UnsafeFuture membutuhkan pembenaran yang kuat!

Semua ini terjadi karena unsafe harus eksplisit, dan Rust akan mendorong Anda kembali ke tempat yang aman. Juga dengan token ini, f 's ... harus _tidak_ menjadi blok yang tidak aman, https://github.com/rust-lang/rfcs/pull/2585 harus diadopsi, dan async unsafe fn harus memiliki tubuh yang aman.

Saya pikir poin terakhir ini mungkin terbukti agak penting. Ada kemungkinan bahwa setiap async unsafe fn akan menggunakan blok unsafe , tetapi sebagian besar akan mendapat manfaat dari beberapa analisis keamanan, dan banyak suara yang cukup rumit untuk kesalahan yang mudah.

Kita tidak boleh melewati pemeriksa peminjaman saat menangkap penutupan secara khusus.

Jadi komentar saya di sini: https://github.com/rust-lang/rust/issues/62149#issuecomment -511116357 adalah ide yang sangat buruk.

Sifat UnsafeFuture akan mengharuskan penelepon untuk menulis unsafe { } untuk polling masa depan, namun penelepon tidak tahu kewajiban mana yang harus dibuktikan di sana, misalnya, jika Anda mendapatkan Box<dyn UnsafeFuture> apakah unsafe { future.poll() } aman? Untuk semua masa depan? Anda tidak bisa tahu. Jadi ini sama sekali tidak berguna seperti yang ditunjukkan @rpjohnst pada perselisihan untuk sifat UnsafeFn serupa.

Mengharuskan Future's untuk selalu aman untuk pol masuk akal, dan proses membangun masa depan yang harus aman untuk polling bisa jadi tidak aman; Saya kira itulah async unsafe fn itu. Tetapi dalam kasus itu, item fn dapat mendokumentasikan apa yang perlu dipertahankan sehingga masa depan yang dikembalikan aman untuk polling.

@rfcbot implementasi-kerja-pemblokiran-stabilisasi

Masih ada 2 pemblokir implementasi yang diketahui sepengetahuan saya (https://github.com/rust-lang/rust/issues/61949, https://github.com/rust-lang/rust/issues/62517) dan itu akan masih bagus untuk menambahkan beberapa tes. Saya menyelesaikan masalah saya untuk membuat rfcbot tidak menjadi pemblokir kami dari waktu ke waktu dan kemudian kami akan benar-benar memblokir perbaikan sebagai gantinya.

@rfcbot menyelesaikan implementasi-kerja-pemblokiran-stabilisasi

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

Mengajukan PR stabilisasi di https://github.com/rust-lang/rust/pull/63209.

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

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

RFC akan segera digabung.

Aspek menarik yang kami stabilkan di sini adalah kami membuat struct referensi mandiri tersedia dari kode aman. Apa yang membuat ini menarik adalah bahwa dalam Pin<&mut SelfReferentialGenerator>, kami memiliki referensi yang dapat diubah (disimpan sebagai bidang di Pin) yang menunjuk ke seluruh status generator, dan kami memiliki penunjuk di dalam status tersebut yang menunjuk ke bagian lain dari status . Penunjuk dalam itu alias dengan referensi yang bisa berubah!

Sebagai tindak lanjut dari ini, @comex sebenarnya berhasil menulis beberapa (aman) async Rust code yang melanggar anotasi noalias LLVM seperti yang saat ini kami pancarkan. Namun, tampaknya karena penggunaan TLS, saat ini tidak ada kesalahan kompilasi.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat