Rust: Selesaikan sintaks `await`

Dibuat pada 15 Jan 2019  ·  512Komentar  ·  Sumber: rust-lang/rust

Sebelum berkomentar di utas ini, periksa https://github.com/rust-lang/rust/issues/50547 dan coba periksa apakah Anda tidak menduplikasi argumen yang sudah dibuat di sana.


Catatan dari para gembala:

Jika Anda baru mengenal utas ini, pertimbangkan untuk memulai dari https://github.com/rust-lang/rust/issues/57640#issuecomment -456617889, yang diikuti oleh tiga komentar ringkasan yang bagus, yang terbaru adalah https: //github.com/rust-lang/rust/issues/57640#issuecomment -457101180. (Terima kasih, @traviscross!)

A-async-await AsyncAwait-Focus C-tracking-issue T-lang

Komentar yang paling membantu

Saya pikir mungkin berguna untuk menulis bagaimana bahasa lain menangani konstruksi menunggu.


Kotlin

val result = task.await()

C

var result = await task;

F

let! result = task()

Scala

val result = Await.result(task, timeout)

Python

result = await task

JavaScript

let result = await task;

C ++ (Coroutines TR)

auto result = co_await task;

Retas

$result = await task;

Anak panah

var result = await task;

Dengan semua itu, ingatlah bahwa ekspresi Rust dapat menghasilkan beberapa metode yang dirantai. Kebanyakan bahasa cenderung tidak melakukan itu.

Semua 512 komentar

Saya pikir mungkin berguna untuk menulis bagaimana bahasa lain menangani konstruksi menunggu.


Kotlin

val result = task.await()

C

var result = await task;

F

let! result = task()

Scala

val result = Await.result(task, timeout)

Python

result = await task

JavaScript

let result = await task;

C ++ (Coroutines TR)

auto result = co_await task;

Retas

$result = await task;

Anak panah

var result = await task;

Dengan semua itu, ingatlah bahwa ekspresi Rust dapat menghasilkan beberapa metode yang dirantai. Kebanyakan bahasa cenderung tidak melakukan itu.

Dengan semua itu, ingatlah bahwa ekspresi Rust dapat menghasilkan beberapa metode yang dirantai. Kebanyakan bahasa cenderung tidak melakukan itu.

Saya akan mengatakan bahwa bahasa yang mendukung metode ekstensi cenderung memilikinya. Ini akan mencakup Rust, Kotlin, C # (misalnya metode-sintaks LINQ dan berbagai pembangun) dan F #, meskipun yang terakhir banyak menggunakan operator pipa untuk efek yang sama.

Murni anekdot di pihak saya tetapi saya secara teratur menjalankan ke lusinan + ekspresi rantai metode dalam kode Rust di alam liar dan membaca dan berjalan dengan baik. Saya belum pernah mengalami ini di tempat lain.

Saya ingin melihat bahwa masalah ini direferensikan di posting teratas # 50547 (di samping kotak centang "Sintaks akhir untuk menunggu.").

Kotlin

val result = task.await()

Sintaks Kotlin adalah:

val result = doTask()

await hanyalah suspendable function , bukan barang kelas satu.

Terima kasih telah menyebutkannya. Kotlin terasa lebih implisit karena secara default masa depan sangat menarik. Namun, masih merupakan pola umum dalam blok yang ditangguhkan untuk menggunakan metode itu untuk menunggu di blok yang ditangguhkan lainnya. Saya pasti sudah melakukannya beberapa kali.

@cramertj Karena ada 276 komentar di https://github.com/rust-lang/rust/issues/50547 , bisakah Anda meringkas argumen yang dibuat di sana agar lebih mudah untuk tidak mengulanginya di sini? (Mungkin menambahkannya ke OP di sini?)

Kotlin terasa lebih implisit karena secara default masa depan sangat menarik. Namun, masih merupakan pola umum dalam blok yang ditangguhkan untuk menggunakan metode itu untuk menunggu di blok yang ditangguhkan lainnya. Saya pasti sudah melakukannya beberapa kali.

mungkin Anda harus menambahkan kedua kasus penggunaan dengan sedikit konteks / deskripsi.

Juga ada apa dengan bahasa lain yang menggunakan menunggu implisit, seperti go-lang?

Salah satu alasan untuk mendukung sintaks pasca-perbaikan adalah bahwa dari perspektif pemanggil, await berperilaku sangat mirip dengan pemanggilan fungsi: Anda melepaskan kontrol aliran dan ketika Anda mendapatkannya kembali, hasilnya sedang menunggu di tumpukan Anda. Bagaimanapun, saya lebih suka sintaks yang mencakup perilaku mirip fungsi dengan memuat fungsi-paranthesis. Dan ada alasan bagus untuk ingin memisahkan konstruksi coroutine dari eksekusi pertamanya sehingga perilaku ini konsisten antara blok sinkron dan asinkron.

Tetapi meskipun gaya coroutine implisit telah diperdebatkan, dan saya berada di pihak yang eksplisit, dapatkah memanggil coroutine tidak cukup eksplisit? Ini mungkin bekerja paling baik ketika coroutine tidak langsung digunakan di tempat dibangun (atau dapat bekerja dengan aliran). Intinya, berbeda dengan panggilan biasa, kami mengharapkan coroutine membutuhkan waktu lebih lama dari yang diperlukan dalam urutan evaluasi yang lebih santai. Dan .await!() kurang lebih merupakan upaya untuk membedakan antara panggilan normal dan panggilan coroutine.

Jadi, setelah semoga memberikan pandangan yang agak baru tentang mengapa pasca-perbaikan bisa lebih disukai, proposal sederhana untuk sintaks:

  • future(?)
  • atau future(await) yang datang dengan pengorbanannya sendiri tentu saja tetapi tampaknya diterima sebagai kurang membingungkan, lihat bagian bawah posting.

Mengadaptasi contoh yang cukup populer dari utas lain (dengan asumsi logger.log juga menjadi coroutine, untuk menunjukkan seperti apa panggilan langsung itu):

async fn log_service(&self) -> T {
   let service = self.myService.foo(); // Only construction
   self.logger.log("beginning service call")(?);
   let output = service(?); // Actually wait for its result
   self.logger.log("foo executed with result {}.", output)(?);
   output
}

Dan dengan alternatifnya:

async fn log_service(&self) -> T {
   let service = self.myService.foo(); // Only construction
   self.logger.log("beginning service call")(await);
   let output = service(await);
   self.logger.log("foo executed with result {}.", output)(await);
   output
}

Untuk menghindari kode yang tidak terbaca dan untuk membantu penguraian, hanya izinkan spasi setelah tanda tanya, bukan di antara kode dan paran terbuka. Jadi future(? ) bagus sedangkan future( ?) tidak bagus. Masalah ini tidak muncul dalam kasus future(await) mana semua token saat ini dapat digunakan seperti sebelumnya.

Interaksi dengan operator pasca-perbaikan lainnya (seperti ? -try saat ini) juga seperti dalam pemanggilan fungsi:

async fn try_log(message: String) -> Result<usize, Error> {
    let logger = acquire_lock()(?);
    // Very terse, construct the future, wait on it, branch on its result.
    let length = logger.log_into(message)(?)?;
    logger.timestamp()(?);
    Ok(length)
}

Atau

async fn try_log(message: String) -> Result<usize, Error> {
    let logger = acquire_lock()(await);
    // Very terse, construct the future, wait on it, branch on its result.
    let length = logger.log_into(message)(await)?;
    logger.timestamp()(await);
    Ok(length)
}

Beberapa alasan untuk menyukai ini:

  • Dibandingkan dengan .await!() itu tidak menyinggung anggota yang bisa memiliki kegunaan lain.
  • Ini mengikuti prioritas alami panggilan, seperti perangkaian dan penggunaan ? . Hal ini membuat jumlah kelas prioritas lebih rendah dan membantu pembelajaran. Dan pemanggilan fungsi selalu agak spesial dalam bahasanya (meskipun memiliki sifat), sehingga tidak ada harapan dari kode pengguna yang dapat menentukan my_await!() mereka sendiri yang memiliki sintaks dan efek yang sangat mirip.
  • Hal ini dapat menggeneralisasi generator dan stream, serta generator yang mengharapkan lebih banyak argumen untuk diberikan saat dimulainya kembali. Intinya, ini berperilaku sebagai FnOnce sementara Streams akan berperilaku seperti FnMut . Argumen tambahan juga dapat diakomodasi dengan mudah.
  • Bagi mereka yang telah menggunakan Futures sebelumnya, ini menangkap bagaimana seharusnya ? dengan Poll seharusnya bekerja selama ini (urutan stabilisasi yang tidak menguntungkan di sini). Sebagai langkah pembelajaran, ini juga konsisten dengan mengharapkan operator berbasis ? untuk mengalihkan aliran kendali. (await) di sisi lain tidak akan memenuhi ini tetapi bagaimanapun juga fungsi akan selalu berharap untuk melanjutkan pada titik divergen.
  • Itu memang menggunakan sintaks seperti fungsi, meskipun argumen ini hanya bagus jika Anda setuju dengan saya: smile:

Dan alasan untuk tidak menyukai ini:

  • ? tampaknya menjadi argumen tetapi bahkan tidak diterapkan ke ekspresi. Saya percaya ini dapat diselesaikan melalui pengajaran, karena token yang tampaknya diterapkan adalah pemanggilan fungsi itu sendiri, yang merupakan gagasan yang agak benar. Ini juga berarti secara positif bahwa sintaksnya tidak ambigu, saya harap.
  • Lebih banyak (dan berbeda) campuran paranthesis dan ? bisa sulit diurai. Terutama ketika Anda memiliki satu masa depan yang mengembalikan hasil masa depan yang lain: construct_future()(?)?(?)? . Tapi Anda bisa membuat argumen yang sama untuk hasil dari objek fn , yang menyebabkan ekspresi seperti ini diperbolehkan: foobar()?()?()? . Karena bagaimanapun, saya belum pernah melihat ini digunakan atau keluhan, membagi menjadi pernyataan terpisah dalam kasus seperti itu tampaknya jarang diperlukan. Masalah ini juga tidak ada untuk construct_future()(await)?(await)? -
  • future(?) adalah bidikan terbaik saya pada sintaks yang singkat dan masih agak ringkas. Namun, alasannya didasarkan pada detail implementasi di coroutine (sementara dikembalikan dan dikirim pada resume), yang mungkin membuatnya tidak cocok untuk abstraksi. future(await) akan menjadi alternatif yang masih bisa dijelaskan setelah await telah diinternalisasi sebagai kata kunci tetapi posisi argumen agak sulit untuk saya terima. Ini bisa saja baik-baik saja, dan tentunya lebih mudah dibaca ketika coroutine memberikan hasil.
  • Interferensi dengan proposal panggilan fungsi lainnya?
  • Milikmu? Anda tidak perlu menyukainya, hanya sia-sia jika tidak setidaknya mengusulkan sintaks pasca-perbaikan yang singkat ini.

future(?)

Tidak ada yang istimewa tentang Result : Futures dapat mengembalikan semua jenis Rust. Kebetulan beberapa Futures mengembalikan Result

Jadi bagaimana itu akan berhasil untuk Futures yang tidak mengembalikan Result ?

Sepertinya tidak jelas apa yang saya maksud. future(?) adalah apa yang sebelumnya dibahas sebagai future.await!() atau serupa. Bercabang di masa depan yang mengembalikan hasil juga akan menjadi future(?)? (dua cara berbeda bagaimana kita dapat melepaskan aliran kontrol lebih awal). Ini membuat polling berikutnya (?) dan pengujian hasil ? ortogonal. Edit: menambahkan contoh tambahan untuk ini.

Bercabang di masa depan yang mengembalikan hasil juga akan menjadi future(?)?

Terima kasih telah menjelaskan. Dalam hal ini saya jelas bukan penggemar itu.

Itu berarti bahwa memanggil fungsi yang mengembalikan Future<Output = Result<_, _>> akan ditulis seperti foo()(?)?

Ini sangat sintaks, dan menggunakan ? untuk dua tujuan yang sama sekali berbeda.

Jika secara khusus petunjuk ke operator ? yang berat, tentu saja seseorang dapat menggantinya dengan kata kunci yang baru dipesan. Awalnya saya hanya menganggap bahwa ini terasa terlalu seperti argumen sebenarnya dari tipe yang membingungkan tetapi pengorbanannya dapat bekerja dalam hal membantu mengurai pernyataan secara mental. Jadi pernyataan yang sama untuk impl Future<Output = Result<_,_>> akan menjadi:

  • foo()(await)?

Argumen terbaik mengapa ? sesuai adalah bahwa mekanisme internal yang digunakan agak mirip (jika tidak, kami tidak dapat menggunakan Poll di pustaka saat ini) tetapi ini mungkin kehilangan inti dari abstraksi yang baik.

Ini sangat berat sintaks

Saya pikir itulah inti dari menunggu eksplisit?

itu menggunakan? untuk dua tujuan yang sangat berbeda.

ya, jadi sintaks foo()(await) akan jauh lebih bagus.

sintaks ini seperti memanggil fungsi yang mengembalikan closure lalu memanggil closure itu di JS.

Saya membaca "syntax-heavy" lebih dekat dengan "sigil-heavy", melihat urutan ()(?)? cukup menggelegar. Ini dibicarakan di postingan aslinya:

Lebih banyak (dan berbeda) campuran paranthesis dan ? bisa sulit diurai. Terutama ketika Anda memiliki satu masa depan yang mengembalikan hasil masa depan yang lain: construct_future()(?)?(?)?

Tapi Anda bisa membuat argumen yang sama untuk hasil dari objek fn , yang menyebabkan ekspresi seperti ini diperbolehkan: foobar()?()?()? . Karena bagaimanapun, saya belum pernah melihat ini digunakan atau keluhan, membagi menjadi pernyataan terpisah dalam kasus seperti itu tampaknya jarang diperlukan.

Saya pikir bantahan di sini adalah: berapa kali Anda melihat -> impl Fn di alam liar (apalagi -> Result<impl Fn() -> Result<impl Fn() -> Result<_, _>, _>, _> )? Berapa kali Anda berharap melihat -> impl Future<Output = Result<_, _>> dalam basis kode asinkron? Harus menamai nilai pengembalian impl Fn langka untuk membuat kode lebih mudah dibaca sangat berbeda dengan harus memberi nama pecahan signifikan dari nilai pengembalian sementara impl Future .

Harus memberi nama nilai pengembalian impl Fn yang langka untuk membuat kode lebih mudah dibaca sangat berbeda dengan harus memberi nama pecahan signifikan dari nilai pengembalian impl Future sementara.

Saya tidak melihat bagaimana pilihan sintaks ini memengaruhi berapa kali Anda harus memberi nama jenis hasil Anda secara eksplisit. Saya tidak berpikir itu tidak mempengaruhi jenis inferensi yang berbeda dari await? future .

Namun, Anda semua membuat poin yang sangat bagus di sini dan semakin banyak contoh yang saya buat dengannya (saya mengedit posting asli agar selalu berisi kedua versi sintaks), semakin saya condong ke future(await) sendiri. Hal ini tidak masuk akal untuk mengetik, dan masih mempertahankan semua kejelasan sintaks panggilan fungsi yang dimaksudkan untuk dibangkitkan.

Berapa kali Anda berharap untuk melihat -> impl Future> dalam basis kode asinkron?

Saya berharap untuk melihat jenis yang setara dengan ini (async fn yang mengembalikan Hasil) sepanjang waktu , bahkan mungkin sebagian besar dari semua async fns, karena jika yang Anda tunggu adalah IO, Anda hampir pasti akan melempar Kesalahan IO ke atas.


Menautkan ke posting saya sebelumnya tentang masalah pelacakan dan menambahkan beberapa pemikiran lagi.

Saya pikir sangat kecil kemungkinan sintaks yang tidak menyertakan string karakter await akan diterima untuk sintaks ini. Saya pikir pada titik ini, setelah satu tahun mengerjakan fitur ini, akan lebih produktif untuk mencoba menimbang pro dan kontra dari alternatif yang diketahui untuk mencoba menemukan mana yang terbaik daripada mengusulkan sintaks baru. Sintaksnya menurut saya layak, mengingat posting saya sebelumnya:

  • Awalan menunggu dengan pembatas wajib. Di sini ini juga merupakan keputusan tentang pembatas apa (baik kurung kurawal atau kurung kurawal atau menerima keduanya; semua ini memiliki pro dan kontra). Yaitu, await(future) atau await { future } . Ini benar-benar menyelesaikan masalah prioritas, tetapi berisik secara sintaksis dan kedua opsi pembatas menghadirkan kemungkinan sumber kebingungan.
  • Awalan menunggu dengan prioritas "berguna" tentang ? . (Artinya, menunggu mengikat lebih erat dari?). Ini mungkin mengejutkan beberapa pengguna yang membaca kode, tetapi saya percaya fungsi yang mengembalikan hasil masa depan akan jauh lebih umum daripada fungsi yang mengembalikan hasil masa depan.
  • Awalan menunggu dengan prioritas yang "jelas" tentang ? . (Artinya, itu? Mengikat lebih erat dari menunggu). Gula sintaks tambahan await? untuk menunggu gabungan dan? operator. Saya pikir gula sintaks ini diperlukan untuk membuat urutan prioritas ini dapat dijalankan sama sekali, jika tidak semua orang akan menulis (await future)? sepanjang waktu, yang merupakan varian yang lebih buruk dari opsi pertama yang saya sebutkan.
  • Postfix menunggu dengan sintaks spasi menunggu. Ini memecahkan masalah prioritas dengan memiliki urutan visual yang jelas antara kedua operator. Saya merasa tidak nyaman dengan solusi ini dalam banyak hal.

Peringkat saya sendiri di antara pilihan ini berubah setiap kali saya memeriksa masalah. Untuk saat ini, saya pikir menggunakan presedensi yang jelas dengan gula sepertinya merupakan keseimbangan terbaik antara ergonomi, keakraban, dan pemahaman. Tapi di masa lalu saya menyukai salah satu dari dua sintaks prefiks lainnya.

Demi pembahasan, saya akan memberikan empat opsi ini nama-nama ini:

Nama | Masa Depan | Hasil Masa Depan | Hasil Masa Depan
--- | --- | --- | ---
Pembatas wajib | await(future) atau await { future } | await(future)? atau await { future }? | await(future?) atau await { future? }
Prioritas yang berguna | await future | await future? | await (future?)
Diutamakan yang jelas dengan gula | await future | await? future atau (await future)? | await future?
Kata kunci Postfix | future await | future await? | future? await

(Saya secara khusus menggunakan "kata kunci postfix" untuk membedakan opsi ini dari sintaks postfix lain seperti "makro postfix".)

Salah satu kekurangan dari 'blessing' await future? di Prioritas yang berguna tetapi juga kekurangan lainnya yang tidak berfungsi sebagai perbaikan pasca adalah pola biasa dari mengonversi ekspresi secara manual dengan ? mungkin tidak lagi berlaku, atau mengharuskan Future mereplikasi metode Result jelas dengan cara yang kompatibel. Menurut saya ini mengejutkan. Jika mereka direplikasi, tiba-tiba menjadi membingungkan kombinator mana yang bekerja di masa depan yang kembali dan mana yang bersemangat. Dengan kata lain, akan sulit untuk memutuskan apa yang sebenarnya dilakukan penggabung seperti dalam kasus menunggu implisit. (Sunting: sebenarnya, lihat dua komentar di bawah ini di mana saya memiliki perspektif yang lebih teknis apa yang saya maksud dengan penggantian yang mengejutkan dari ? )

Contoh di mana kami dapat memulihkan dari kasus kesalahan:

async fn previously() -> Result<_, lib::Error> {
    let _ = await get_result()?;
}

async fn with_recovery() -> Result<_, lib::Error> {
    // Does `or_recover` return a future or not? Suddenly very important but not visible.
    let _ = await get_result().unwrap_or_else(or_recover);
    // If `or_recover` is sync, this should still work as a pattern of replacing `?` imho.
    // But we also want `or_recover` returning a future to work, as a combinator for futures?

    // Resolving sync like this just feel like wrong precedence in a number of ways
    // Also, conflicts with `Result of future` depending on choice.
    let _ = await get_result()?.unwrap_or_else(or_recover);
}

Masalah ini tidak terjadi untuk operator pasca-perbaikan yang sebenarnya:

async fn with_recovery() -> Result<_, lib::Error> {
    // Also possible in 'space' delimited post-fix await route, but slightly less clear
    let _ = get_result()(await)
        // Ah, this is sync
        .unwrap_or_else(or_recover);
    // This would be future combinator.
    // let _ = get_result().unwrap_or_else(or_recover)(await);
}
// Obvious precedence syntax
let _ = await get_result().unwrap_or_else(or_recover);
// Post-fix function argument-like syntax
let _ = get_result()(await).unwrap_or_else(or_recover);

Ini adalah ekspresi yang berbeda, operator titik lebih diutamakan daripada operator "prioritas yang jelas" await , jadi yang setara adalah:

let _ = get_result().unwrap_or_else(or_recover)(await);

Ini memiliki ambiguitas yang sama persis tentang apakah or_recover asinkron atau tidak. (Yang menurut saya tidak masalah, Anda tahu ekspresi secara keseluruhan adalah async, dan Anda dapat melihat definisi or_recover jika karena alasan tertentu Anda perlu tahu apakah bagian tertentu itu asinkron).

Ini memiliki ambiguitas yang sama persis tentang apakah or_recover asinkron atau tidak.

Tidak persis sama. unwrap_or_else harus menghasilkan coroutine karena ditunggu, jadi ambiguitiy adalah apakah get_result adalah coroutine (jadi kombinator dibuat) atau Result<impl Future, _> (dan Ok sudah berisi coroutine, dan Err membuat satu). Keduanya tidak memiliki masalah yang sama untuk dapat secara sekilas mengidentifikasi perolehan efisiensi dengan memindahkan titik urutan await ke join , yang merupakan salah satu perhatian utama dari menunggu implisit. Alasannya, bagaimanapun juga, komputasi perantara ini harus sinkron dan harus diterapkan ke jenis sebelum menunggu dan harus menghasilkan coroutine yang ditunggu. Ada satu sama lain, perhatian yang lebih besar di sini:

Ini adalah ekspresi yang berbeda, operator titik lebih diutamakan daripada operator menunggu "prioritas jelas", jadi padanannya adalah

Itu bagian dari kebingungan, mengganti ? dengan operasi pemulihan mengubah posisi await fundamental. Dalam konteks ? sintaks, diberi ekspresi parsial expr tipe T , saya mengharapkan semantik berikut dari transformasi (dengan asumsi T::unwrap_or_else ada) :

  • expr? -> expr.unwrap_or_else(or_recover)
  • <T as Try>::into_result(expr)? -> T::unwrap_or_else(expr, or_recover)

Namun, di bawah 'Prioritas yang berguna' dan await expr? ( await expr menghasilkan T ) sebagai gantinya kami mendapatkan

  • await expr? -> await expr.unwrap_or_else(or_recover)
  • <T as Try>::into-result(await expr) -> await Future::unwrap_or_else(expr, or_recover)

sedangkan dalam preseden yang jelas transformasi ini tidak lagi berlaku sama sekali tanpa parantesis tambahan, tetapi setidaknya intuisi masih berfungsi untuk 'Hasil Masa Depan'.

Dan bagaimana dengan kasus yang lebih menarik di mana Anda menunggu di dua titik berbeda dalam urutan kombinator? Dengan sintaks prefiks apa pun, menurut saya, ini membutuhkan tanda kurung. Bahasa Rust lainnya mencoba untuk menghindari ini secara panjang lebar untuk membuat 'ekspresi mengevaluasi dari kiri ke kanan' berfungsi, salah satu contohnya adalah sihir ref otomatis.

Contoh untuk menunjukkan bahwa ini menjadi lebih buruk untuk rantai yang lebih panjang dengan beberapa titik tunggu / coba / kombinasi.

// Chain such that we
// 1. Create a future computing some partial result
// 2. wait for a result 
// 3. then recover to a new future in case of error, 
// 4. then try its awaited result. 
async fn await_chain() -> Result<usize, Error> {
    // Mandatory delimiters
    let _ = await(await(partial_computation()).unwrap_or_else(or_recover))?
    // Useful precedence requires paranthesis nesting afterall
    let _ = await { await partial_computation() }.unwrap_or_else(or_recover)?;
    // Obivious precendence may do slightly better, but I think confusing left-right-jumps after all.
    let _ = await? (await partial_computation()).unwrap_or_else(or_recover);
    // Post-fix
    let _ = partial_computation()(await).unwrap_or_else(or_recover)(await)?;
}

Apa yang ingin saya hindari, adalah membuat analog Rust dari penguraian tipe C tempat Anda beralih di antaranya
sisi kiri dan kanan ekspresi untuk kombinator 'pointer' dan 'array'.

Entri tabel dengan gaya @withoutboats :

| Nama | Masa Depan | Hasil Masa Depan | Hasil Masa Depan |
| - | - | - | - |
| Pembatas wajib | await(future) | await(future)? | await(future?) |
| Prioritas yang berguna | await future | await future? | await (future?) |
| Prioritas yang jelas | await future | await? future | await future? |
| Telepon Postfix | future(await) | future(await)? | future?(await) |

| Nama | Dirantai |
| - | - |
| Pembatas wajib | await(await(foo())?.bar())? |
| Prioritas yang berguna | await(await foo()?).bar()? |
| Prioritas yang jelas | await? (await? foo()).bar() |
| Telepon Postfix | foo()(await)?.bar()(await) |

Saya sangat mendukung postfix menunggu karena berbagai alasan tetapi saya tidak suka varian yang ditunjukkan oleh @withoutboats , terutama karena alasan yang sama. Misalnya. foo await.method() membingungkan.

Pertama, mari kita lihat tabel serupa tetapi menambahkan beberapa varian postfix:

| Nama | Masa Depan | Hasil Masa Depan | Hasil Masa Depan |
| ---------------------- | -------------------- | ----- ---------------- | --------------------- |
| Pembatas wajib | await { future } | await { future }? | await { future? } |
| Prioritas yang berguna | await future | await future? | await (future?) |
| Prioritas yang jelas | await future | await? future | await future? |
| Kata kunci Postfix | future await | future await? | future? await |
| Bidang Postfix | future.await | future.await? | future?.await |
| Metode Postfix | future.await() | future.await()? | future?.await() |

Sekarang mari kita lihat ekspresi masa depan yang dirantai:

| Nama | Hasil Berjangka yang Dirantai |
| ---------------------- | -------------------------- ----------- |
| Pembatas wajib | await { await { foo() }?.bar() }? |
| Prioritas yang berguna | await (await foo()?).bar()? |
| Prioritas yang jelas | await? (await? foo()).bar() |
| Kata kunci Postfix | foo() await?.bar() await? |
| Bidang Postfix | foo().await?.bar().await? |
| Metode Postfix | foo().await()?.bar().await()? |

Dan sekarang untuk contoh dunia nyata, dari reqwests , di mana Anda mungkin ingin menunggu hasil masa depan yang dirantai (menggunakan formulir menunggu pilihan saya).

let res: MyResponse = client.get("https://my_api").send().await?.json().await?;

Sebenarnya saya pikir setiap pemisah terlihat baik-baik saja untuk sintaks postfix, misalnya:
let res: MyResponse = client.get("https://my_api").send()/await?.json()/await?;
Tetapi saya tidak memiliki pendapat yang kuat tentang mana yang akan digunakan.

Apakah makro postfix (yaitu future.await!() ) masih bisa menjadi pilihan? Jelas, ringkas, dan tidak ambigu:

| Masa Depan | Hasil Masa Depan | Hasil Masa Depan |
| --- | --- | --- |
| masa depan. tunggu! () | masa depan. tunggu! ()? | masa depan? .await! () |

Juga makro postfix membutuhkan lebih sedikit usaha untuk diimplementasikan, dan mudah dimengerti dan digunakan.

Juga makro postfix membutuhkan lebih sedikit usaha untuk diimplementasikan, dan mudah dimengerti dan digunakan.

Juga hanya menggunakan fitur bahasa biasa (atau setidaknya akan terlihat seperti makro postfix normal).

Makro postfix akan bagus karena menggabungkan kesederhanaan dan keterkaitan postfix dengan properti non-magis dan keberadaan makro yang jelas, dan akan cocok dengan makro pengguna pihak ketiga, seperti .await_debug!() , .await_log!(WARN) atau .await_trace!()

Makro postfix akan bagus karena menggabungkan [...] properti non-magis [...] dari makro

@novacrazy masalah dengan argumen ini adalah bahwa setiap await! macro _would_ menjadi ajaib, itu melakukan operasi yang tidak mungkin dalam kode yang ditulis pengguna (saat ini implementasi berbasis generator yang mendasarinya agak terbuka, tetapi pemahaman saya adalah bahwa sebelum stabilisasi ini akan benar-benar disembunyikan (dan benar-benar berinteraksi dengannya saat ini membutuhkan penggunaan beberapa rustc -fitur malam internal pula)).

@ Nemo7 Saya tidak sadar itu dimaksudkan untuk menjadi sangat buram.

Apakah sudah terlambat untuk mempertimbangkan kembali menggunakan makro prosedural seperti #[async] untuk melakukan transformasi dari fungsi "asinkron" ke fungsi generator, daripada menggunakan kata kunci ajaib? Ini adalah tiga karakter tambahan untuk diketik, dan dapat ditandai di dokumen dengan cara yang sama seperti #[must_use] atau #[repr(C)] .

Saya benar-benar tidak menyukai gagasan menyembunyikan begitu banyak lapisan abstraksi yang secara langsung mengontrol aliran eksekusi. Rasanya berlawanan dengan apa itu Rust. Pengguna harus dapat sepenuhnya melacak kode dan mencari tahu bagaimana semuanya bekerja dan ke mana pelaksanaannya. Mereka harus didorong untuk meretas berbagai hal dan menipu sistem, dan jika mereka menggunakan Rust yang aman itu harus aman. Ini tidak akan memperbaiki apa pun jika kita kehilangan kendali tingkat rendah, dan saya mungkin akan tetap berpegang pada kontrak berjangka mentah.

Saya sangat yakin Rust, bahasa (bukan std / core ), harus menyediakan abstraksi dan sintaks hanya jika tidak mungkin (atau sangat tidak praktis) untuk dilakukan oleh pengguna atau std . Seluruh hal asinkron ini telah lepas kendali dalam hal itu. Apakah kita benar - rustc ?

@novacrazy Saya biasanya setuju dengan sentimen tetapi tidak dengan kesimpulan.

harus menyediakan abstraksi dan sintaks hanya jika tidak mungkin (atau sangat tidak praktis) untuk dilakukan oleh pengguna atau std.

Apa alasan memiliki for -loops dalam bahasa ketika mereka juga bisa menjadi makro yang berubah menjadi loop dengan jeda. Apa alasan untuk || closure ketika itu bisa menjadi sifat khusus dan konstruktor objek. Mengapa kami memperkenalkan ? padahal kami sudah memiliki try!() . Alasan mengapa saya tidak setuju dengan pertanyaan dan kesimpulan Anda itu, adalah karena konsistensi. Inti dari abstraksi-abstraksi ini tidak hanya perilaku yang mereka enkapsulasi tetapi juga aksesibilitasnya. for -replacement rusak dalam mutabilitas, jalur kode utama, dan keterbacaan. || -replacement dipecah dalam pernyataan verbositas – mirip dengan Futures saat ini. try!() dipecah dalam urutan ekspresi dan komposabilitas yang diharapkan.

Pertimbangkan bahwa async bukan hanya dekorator pada suatu fungsi, tetapi ada pemikiran lain untuk memberikan pola tambahan dengan aync-blocks dan async || . Karena ini berlaku untuk item bahasa yang berbeda, kegunaan makro tampaknya kurang optimal. Bahkan tidak memikirkan implementasi jika harus terlihat oleh pengguna.

Pengguna harus dapat sepenuhnya melacak kode dan mencari tahu bagaimana semuanya bekerja dan ke mana pelaksanaannya. Mereka harus didorong untuk meretas berbagai hal dan menipu sistem, dan jika mereka menggunakan Rust yang aman itu harus aman.

Saya rasa argumen ini tidak berlaku karena menerapkan coroutine yang seluruhnya menggunakan std api kemungkinan besar akan sangat bergantung pada unsafe . Dan kemudian ada argumen terbalik karena sementara itu bisa dilakukan-dan Anda tidak akan berhenti bahkan jika ada cara sintaksis dan semantik dalam bahasa melakukannya-perubahan akan menjadi sangat beresiko melanggar asumsi yang dibuat dalam unsafe -code. Saya berpendapat bahwa Rust seharusnya tidak membuatnya terlihat seperti mencoba menawarkan antarmuka standar untuk implementasi bit yang tidak dimaksudkan untuk segera distabilkan, termasuk internal Coroutines. Analoginya adalah extern "rust-call" yang berfungsi sebagai keajaiban saat ini untuk memperjelas bahwa pemanggilan fungsi tidak memiliki jaminan seperti itu. Kita mungkin ingin tidak pernah benar-benar harus return , meskipun nasib coroutine yang bertumpuk belum diputuskan. Kami mungkin ingin mengaitkan pengoptimalan lebih dalam ke kompiler.

Selain: Ngomong-ngomong, secara teori, bukan ide yang sepenuhnya serius, dapatkah coroutine menunggu dilambangkan sebagai hipotesis extern "await-call" fn () -> T ? Jika demikian, ini akan memungkinkan di pendahuluan a

trait std::ops::Co<T> {
    extern "rust-await" fn await(self) -> T;
}

impl<T> Co<T> for Future<Output=T> { }

alias. future.await() dalam item ruang pengguna yang didokumentasikan. Atau dalam hal ini, sintaks operator lain juga dimungkinkan.

@Tokopedia

Mengapa kami memperkenalkan ? padahal kami sudah memiliki try!()

Agar adil, saya juga menentang ini, meskipun itu telah berkembang pada saya. Akan lebih dapat diterima jika Try pernah distabilkan, tapi itu topik lain.

Masalah dengan contoh "gula" yang Anda berikan adalah bahwa mereka adalah gula yang sangat, sangat encer. Bahkan impl MyStruct kurang lebih adalah gula untuk impl <anonymous trait> for MyStruct . Ini adalah gula kualitas hidup yang menambahkan nol overhead sama sekali.

Sebaliknya, generator dan fungsi asinkron menambahkan overhead yang tidak sepenuhnya tidak signifikan dan overhead mental yang signifikan. Generator secara khusus sangat sulit untuk diterapkan sebagai pengguna, dan dapat lebih efektif dan mudah digunakan sebagai bagian dari bahasa itu sendiri, sementara async dapat diimplementasikan di atasnya dengan relatif mudah.

Poin tentang blok atau penutupan asinkron menarik, dan saya akui bahwa kata kunci akan lebih berguna di sana, tetapi saya masih menentang ketidakmampuan untuk mengakses item tingkat yang lebih rendah jika perlu.

Idealnya, itu akan menjadi indah untuk mendukung async kata kunci dan #[async] atribut / prosedural makro, dengan mantan memungkinkan akses tingkat rendah ke yang dihasilkan (no pun intended) Generator. Sementara itu yield harus dilarang dalam blok atau fungsi yang menggunakan async sebagai kata kunci. Saya yakin mereka bahkan dapat membagikan kode implementasi.

Adapun await , jika kedua hal di atas memungkinkan, kita dapat melakukan sesuatu yang serupa, dan membatasi kata kunci await menjadi async fungsi kata kunci / blok, dan menggunakan beberapa jenis await!() makro di #[async] fungsi.

Pseudocode:

// imaginary generator syntax stolen from JavaScript
fn* my_generator() -> T {
    yield some_value;

    // explicit return statements are only included to 
    // make it clear the generator/async functions are finished.
    return another_value;
}

// `await` keyword would not be allowed here, but the `yield` keyword is
#[async]
fn* my_async_generator() -> Result<T, E> {
    let item = some_op().await!()?; // uses the `.await!()` macro
    // which would really just use `yield` internally, but with the pinning API

    yield future::ok(item.clone());

    return Ok(item);
}

// `yield` would not be allowed here, but the `await` keyword is.
async fn regular_async() -> Result<T, E> {
   let some_op = async || { /*...*/ };

   let item = some_op() await?;

   return Ok(item);
}

Terbaik dari kedua dunia.

Ini terasa seperti perkembangan kompleksitas yang lebih alami untuk disajikan kepada pengguna, dan dapat digunakan secara lebih efektif untuk lebih banyak aplikasi.

Harap diingat bahwa edisi ini khusus membahas tentang sintaks await . Percakapan lain tentang bagaimana async fungsi dan blok diterapkan berada di luar ruang lingkup, kecuali untuk tujuan mengingatkan orang-orang bahwa await! bukanlah sesuatu yang Anda dapat atau akan pernah dapat tulis di Rust's bahasa permukaan.

Saya ingin secara khusus menimbang pro dan kontra dari semua proposal sintaks pasca-perbaikan. Jika salah satu sintaks menonjol dengan sedikit kontra, mungkin kita harus melakukannya. Namun jika tidak ada, akan lebih baik untuk mendukung sintaksis yang dipisahkan prefiks yang meneruskan compatbile ke post-fix yang belum ditentukan jika diperlukan. Karena Postfix tampaknya beresonansi sebagai yang paling ringkas untuk beberapa anggota, tampaknya praktis untuk mengevaluasi ini terlebih dahulu sebelum pindah ke yang lain.

Perbandingannya akan menjadi syntax , example ( reqwest dari concerns , dan opsional resolution , misal jika disepakati bisa turun ke pengajaran). Jangan ragu untuk menambahkan sintaks dan / atau masalah, saya akan mengeditnya ke dalam posting kolektif ini. Seperti yang saya pahami, sintaks apa pun yang tidak melibatkan await kemungkinan besar akan terasa asing bagi pendatang baru dan pengguna berpengalaman, tetapi semua yang terdaftar saat ini menyertakannya.

Contoh sintaks satu prefiks untuk referensi saja, tolong jangan bikeshed bagian ini:

let sent = (await client.get("https://my_api").send())?;
let res: MyResponse = (await sent.json())?;
  • Kata kunci Postfix foo() await?

    • Contoh: client.get("https://my_api").send() await?.json() await?

    • | Kepedulian | Resolusi |

      | - | - |

      | Berantai tanpa ? mungkin membingungkan atau tidak diizinkan | |

  • Bidang postfix foo().await?

    • Contoh: client.get("https://my_api").send().await?.json().await?

    • | Kepedulian | Resolusi |

      | - | - |

      | Sepertinya lapangan | |

  • Metode postfix foo().await()?

    • Contoh: client.get("https://my_api").send().await()?.json().await()?

    • | Kepedulian | Resolusi |

      | - | - |

      | Sepertinya metode atau sifat | Ini dapat didokumentasikan sebagai ops:: sifat? |

      | Bukan panggilan fungsi | |

  • Panggilan Postfix foo()(await)?

    • Contoh: client.get("https://my_api").send()(await)?.json()(await)?

    • | Kepedulian | Resolusi |

      | - | - |

      | Bisa dibingungkan dengan argumen sebenarnya | kata kunci + penyorotan + tidak tumpang tindih |

  • Makro Postfix foo().await!()?

    • Contoh: client.get("https://my_api").send().await!()?.json().await!()?

    • | Kepedulian | Resolusi |

      | - | - |

      | Tidak akan benar-benar menjadi makro… | |

      | … Atau, await bukan lagi kata kunci | |

Pemikiran tambahan tentang post-fix vs. prefix dari pandangan kemungkinan menggabungkan generator: mempertimbangkan nilai, yield dan await menempati dua jenis pernyataan yang berlawanan. Yang pertama memberikan nilai dari fungsi Anda ke luar, yang terakhir menerima nilai.

Catatan: Yah, Python memiliki generator interaktif di mana yield dapat mengembalikan nilai. Secara simetris, panggilan ke generator atau aliran semacam itu membutuhkan argumen tambahan dalam pengaturan tipe yang kuat. Mari kita tidak mencoba menggeneralisasi terlalu jauh, dan kita akan melihat bahwa argumen tersebut kemungkinan besar berpindah dalam kedua kasus tersebut.

Kemudian, saya berpendapat bahwa tidak wajar jika pernyataan ini dibuat serupa. Di sini, mirip dengan ekspresi tugas, mungkin kita harus menyimpang dari norma yang ditetapkan oleh bahasa lain ketika norma itu kurang konsisten dan kurang ringkas untuk Rust. Seperti yang dinyatakan di tempat lain, selama kita menyertakan await dan kesamaan dengan ekspresi lain dengan urutan argumen yang sama ada, seharusnya tidak ada hambatan besar untuk transisi bahkan dari model lain.

Sejak implisit tampaknya dari meja.

Dari menggunakan async / await dalam bahasa lain dan melihat opsi di sini, saya tidak pernah merasa menyenangkan secara sintaksis untuk rantai masa depan.

Apakah varian yang tidak dapat dirantai ada di meja?

// TODO: Better variable names.
await response = client.get("https://my_api").send();
await response = response?.json();
await response = response?;

Saya menyukai ini karena Anda dapat membuat argumen bahwa itu adalah bagian dari pola.

Masalah dengan menunggu pengikatan adalah cerita kesalahannya kurang bagus.

// Error comes _after_ future is awaited
let await res = client.get("http://my_api").send()?;

// Ok
let await res = client.get("http://my_api").send();
let res = res?;

Kita perlu mengingat bahwa hampir semua masa depan yang tersedia di komunitas untuk menunggu bisa salah dan harus digabungkan dengan ? .

Jika kita benar-benar membutuhkan gula sintaks:

await? response = client.get("https://my_api").send();
await? response = response.json();

Baik await dan await? perlu ditambahkan sebagai kata kunci atau kami juga memperpanjangnya menjadi let , yaitu let? result = 1.divide(0);

Mempertimbangkan seberapa sering chaining digunakan dalam kode Rust, saya sepenuhnya setuju bahwa penting untuk chained menunggu sejelas mungkin bagi pembaca. Dalam kasus varian postfix dari await:

client.get("https://my_api").send().await()?.json().await()?;

Ini umumnya berperilaku mirip dengan bagaimana saya mengharapkan kode Rust berperilaku. Saya memiliki masalah dengan fakta bahwa await() dalam konteks ini terasa seperti pemanggilan fungsi, tetapi memiliki perilaku magis (tidak seperti fungsi) dalam konteks ekspresi.

Versi makro postfix akan membuat ini lebih jelas. Orang-orang terbiasa dengan tanda seru dalam karat yang berarti "ada keajaiban di sini" dan saya pasti memiliki preferensi untuk versi ini karena alasan itu.

client.get("https://my_api").send().await!()?.json().await!()?;

Dengan demikian, perlu dipertimbangkan bahwa kita sudah memiliki try!(expr) dalam bahasa dan itu adalah pendahulu kita untuk ? . Menambahkan makro await!(expr) sekarang akan sepenuhnya konsisten dengan bagaimana try!(expr) dan ? diperkenalkan ke bahasa.

Dengan versi await!(expr) dari await, kami memiliki opsi untuk bermigrasi ke makro postfix nanti , atau menambahkan operator bergaya ? sehingga merangkai menjadi mudah. Contoh yang mirip dengan ? tetapi untuk menunggu:

// Not proposing this syntax at the moment. Just an example.
let a = perform()^;

client.get("https://my_api").send()^?.json()^?;

Saya pikir kita harus menggunakan await!(expr) atau await!{expr} untuk saat ini karena keduanya sangat masuk akal dan pragmatis. Kemudian kita dapat merencanakan migrasi ke versi postfix dari await (yaitu .await! atau .await!() ) nanti jika / setelah makro postfix menjadi sesuatu. (Atau akhirnya pergi ke rute menambahkan operator gaya ? ... setelah banyak bikeshedding tentang subjek: P)

FYI, sintaks Scala bukan Await.result karena itu adalah panggilan pemblokiran. Scala's Futures adalah monad, dan oleh karena itu gunakan pemanggilan metode normal atau pemahaman monad for :

for {
  result <- future.map(further_computation)
  a = result * 2
  _ <- future_fn2(result)
} yield 123

Sebagai hasil dari notasi yang mengerikan ini, perpustakaan bernama scala-async dibuat dengan sintaks yang paling saya sukai, yaitu sebagai berikut:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.async.Async.{async, await}

val future = async {
  val f1 = async { ...; true }
  val f2 = async { ...; 42 }
  if (await(f1)) await(f2) else 0
}

Ini sangat mencerminkan apa yang saya inginkan dari kode karat, dengan penggunaan pembatas wajib, dan, karena itu, saya ingin setuju dengan yang lain untuk tetap menggunakan sintaks saat ini await!() . Karat awal adalah simbol yang berat, dan dipindahkan untuk alasan yang baik, saya kira. Penggunaan gula sintaksis dalam bentuk operator postfix (atau apa pun yang Anda miliki), seperti biasa, kompatibel ke belakang, dan kejelasan await!(future) tidak ambigu. Ini juga mencerminkan perkembangan yang kita miliki dengan try! , seperti yang disebutkan sebelumnya.

Manfaat mempertahankannya sebagai makro adalah bahwa secara sekilas terlihat lebih jelas bahwa ini adalah fitur bahasa daripada pemanggilan fungsi normal. Tanpa tambahan ! , penyorotan sintaks editor / viewer akan menjadi cara terbaik untuk dapat mengenali panggilan, dan menurut saya mengandalkan implementasi tersebut adalah pilihan yang lebih lemah.

Dua sen saya (bukan kontributor tetap, fwiw) Saya paling suka menyalin model try! . Ini pernah dilakukan sebelumnya, ini bekerja dengan baik dan setelah menjadi sangat populer ada cukup banyak pengguna untuk mempertimbangkan operator postfix.

Jadi pilihan saya adalah: stabilkan dengan await!(...) dan punt pada operator postfix untuk perangkaian yang bagus berdasarkan polling dari pengembang Rust. Await adalah sebuah kata kunci, tetapi ! menunjukkan bahwa itu adalah sesuatu yang "ajaib" bagi saya dan tanda kurung membuatnya tidak ambigu.

Juga perbandingan:

| Postfix | Ekspresi |
| --- | --- |
| .await | client.get("https://my_api").send().await?.json().await? |
| .await! | client.get("https://my_api").send().await!?.json().await!? |
| .await() | client.get("https://my_api").send().await()?.json().await()? |
| ^ | client.get("https://my_api").send()^?.json()^? |
| # | client.get("https://my_api").send()#?.json()#? |
| @ | client.get("https://my_api").send()@?.json()@? |
| $ | client.get("https://my_api").send()$?.json()$? |

Sen ketiga saya adalah bahwa saya suka @ (untuk "menunggu") dan # (untuk mewakili multi-threading / konkurensi).

Saya suka postfix @ juga! Saya pikir itu sebenarnya bukan pilihan yang buruk, meskipun tampaknya ada beberapa sentimen bahwa itu tidak layak.

  • _ @ for await_ adalah mnemonic yang bagus dan mudah diingat
  • ? dan @ akan sangat mirip, jadi mempelajari @ setelah mempelajari ? seharusnya tidak menjadi lompatan seperti itu
  • Ini membantu memindai rangkaian ekspresi dari kiri ke kanan, tanpa harus memindai ke depan untuk menemukan pembatas penutup untuk memahami ekspresi

Saya sangat menyukai sintaks await? foo , dan menurut saya ini mirip dengan beberapa sintaks yang terlihat dalam matematika, misalnya. sin² x dapat digunakan sebagai arti (sin x) ². Ini terlihat agak canggung pada awalnya, tapi saya rasa sangat mudah untuk membiasakannya.

Seperti yang dikatakan di atas, saya lebih suka menambahkan await!() sebagai makro, seperti try!() , untuk saat ini dan akhirnya memutuskan bagaimana cara mempostingnya. Jika kita dapat mengingat dukungan untuk rustfix yang secara otomatis mengubah await!() panggilan ke postfix menunggu itu belum diputuskan, bahkan lebih baik.

Pilihan kata kunci postfix adalah pemenang yang jelas bagi saya.

  • Tidak ada masalah prioritas / urutan, namun urutan masih bisa dibuat eksplisit dengan tanda kurung. Tetapi sebagian besar tidak memerlukan penumpukan yang berlebihan (argumen serupa untuk memilih postfix '?' Sebagai pengganti 'try ()!').

  • Ini terlihat bagus dengan rangkaian multi-baris (lihat komentar sebelumnya oleh @earthengine), dan sekali lagi, tidak ada kebingungan tentang pemesanan atau apa yang sedang ditunggu. Dan tidak ada tanda kurung bersarang / ekstra untuk ekspresi dengan beberapa penggunaan await:

let x = x.do_something() await
         .do_another_thing() await;
let x = x.foo(|| ...)
         .bar(|| ...)
         .baz() await;
  • Ini cocok untuk makro menunggu! () (Lihat komentar sebelumnya oleh @novacrazy):
macro_rules! await {
    ($e:expr) => {{$e await}}
}
  • Bahkan satu baris, telanjang (tanpa '?'), Postfix menunggu rangkaian kata kunci tidak mengganggu saya karena terbaca dari kiri ke kanan dan kami sedang menunggu kembalinya nilai yang kemudian dioperasikan oleh metode berikutnya (meskipun saya hanya akan lebih suka kode rustfmt'ed multi-baris). Spasi memecah garis dan cukup sebagai indikator / isyarat visual yang menunggu sedang terjadi:
client.get("https://my_api").send() await.unwrap().json() await.unwrap()

Untuk menyarankan kandidat lain yang belum saya lihat diajukan (mungkin karena tidak dapat diurai), bagaimana dengan operator postfix double-dot '..' yang menyenangkan? Itu mengingatkan saya bahwa kita sedang menunggu sesuatu (hasilnya!) ...

client.get("https://my_api").send()..?.json()..?

Saya suka postfix @ juga! Saya pikir itu sebenarnya bukan pilihan yang buruk, meskipun tampaknya ada beberapa sentimen bahwa itu tidak layak.

  • _ @ for await_ adalah mnemonic yang bagus dan mudah diingat
  • ? dan @ akan sangat mirip, jadi mempelajari @ setelah mempelajari ? seharusnya tidak menjadi lompatan seperti itu
  • Ini membantu memindai rangkaian ekspresi dari kiri ke kanan, tanpa harus memindai ke depan untuk menemukan pembatas penutup untuk memahami ekspresi

Saya bukan penggemar menggunakan @ untuk menunggu. Rasanya canggung untuk mengetik pada keyboard tata letak sirip / swe karena saya harus menekan alt-gr dengan ibu jari kanan saya dan kemudian menekan tombol 2 pada baris nomor. Juga, @ memiliki arti yang mapan (at) jadi saya tidak mengerti mengapa kita harus menyamakan artinya.

Saya lebih suka hanya mengetik await , ini lebih cepat karena tidak memerlukan akrobat keyboard.

Ini penilaian saya sendiri, yang sangat subjektif. Saya juga menambahkan future@await , yang menurut saya menarik.

| sintaks | catatan |
| --- | --- |
| await { f } | kuat:

  • sangat mudah
  • paralel for , loop , async dll.
lemah:
  • sangat bertele-tele (5 huruf, 2 tanda kurung, 3 opsional, tapi mungkin spasi berserabut)
  • hasil berantai di banyak kurung kurawal ( await { await { foo() }?.bar() }? )
|
| await f | kuat:
  • paralel await sintaks dari Python, JS, C # dan Dart
  • terus terang, pendek
  • kedua prioritas yang berguna vs. prioritas yang jelas berperilaku baik dengan ? ( await fut? vs. await? fut )
lemah:
  • ambigu: prioritas yang berguna vs. jelas harus dipelajari
  • merangkai juga sangat rumit ( await (await foo()?).bar()? vs. await? (await? foo()).bar() )
|
| fut.await
fut.await()
fut.await!() | kuat:
  • memungkinkan perangkaian yang sangat mudah
  • pendek
  • penyelesaian kode yang bagus
lemah:
  • membodohi pengguna dengan berpikir bahwa ini adalah bidang / fungsi / makro yang ditentukan di suatu tempat. Edit: Saya setuju dengan @jplatte bahwa await!() terasa paling tidak ajaib
|
| fut(await) | kuat:
  • memungkinkan perangkaian yang sangat mudah
  • pendek
lemah:
  • membodohi pengguna dengan berpikir bahwa ada variabel await didefinisikan di suatu tempat dan bahwa masa depan dapat disebut seperti fungsi
|
| f await | kuat:
  • memungkinkan perangkaian yang sangat mudah
  • pendek
lemah:
  • tidak ada paralel dalam sintaksis Rust, tidak jelas
  • otak saya mengelompokkan client.get("https://my_api").send() await.unwrap().json() await.unwrap() menjadi client.get("https://my_api").send() , await.unwrap().json() dan await.unwrap() (dikelompokkan berdasarkan dulu, lalu . ) mana yang tidak benar
  • untuk Haskellers: terlihat seperti sedang memasak, tapi sebenarnya tidak
|
| f@ | kuat:
  • memungkinkan perangkaian yang sangat mudah
  • sangat singkat
lemah:
  • terlihat sedikit canggung (setidaknya pada awalnya)
  • mengkonsumsi @ yang mungkin lebih cocok untuk sesuatu yang lain
  • mungkin mudah untuk dilupakan, terutama dalam ekspresi besar
  • menggunakan @ dengan cara yang berbeda dari semua bahasa lainnya
|
| f@await | kuat:
  • memungkinkan perangkaian yang sangat mudah
  • pendek
  • penyelesaian kode yang bagus
  • await tidak perlu menjadi kata kunci
  • kompatibel ke depan: memungkinkan operator postfix baru ditambahkan dalam bentuk @operator . Misalnya, ? dapat dilakukan sebagai @try .
  • otak saya mengelompokkan client.get("https://my_api").send()@await.unwrap().json()@await.unwrap() ke dalam kelompok yang benar (dikelompokkan oleh . dulu, lalu @ )
lemah:
  • menggunakan @ dengan cara yang berbeda dari semua bahasa lainnya
  • mungkin mendorong penambahan terlalu banyak operator postfix yang tidak perlu
|

Skor saya:

  • familiarity (fam): Seberapa dekat sintaks ini dengan sintaks yang diketahui (Rust dan lainnya, seperti Python, JS, C #)
  • clearness (obv): Jika Anda membaca ini di kode orang lain untuk pertama kalinya, apakah Anda dapat menebak artinya, diutamakan, dll.?
  • verbosity (vrb): Berapa banyak karakter yang diperlukan untuk menulis
  • visibility (vis): Betapa mudahnya menemukan (vs. mengabaikan) dalam kode
  • chaining (cha): Betapa mudahnya untuk merantai dengan . dan await s lainnya
  • grouping (grp): Apakah otak saya mengelompokkan kode ke dalam bagian yang benar
  • kompatibilitas ke depan (fwd): Apakah ini memungkinkan untuk disesuaikan nanti dengan cara yang tidak putus

| sintaks | fam | obv | vrb | vis | cha | grp | fwd |
| --------------------- | ----- | ----- | ----- | ----- | --- - | ----- | ----- |
| await!(fut) | ++ | + | - | ++ | - | 0 | ++ |
| await { fut } | ++ | ++ | - | ++ | - | 0 | + |
| await fut | ++ | - | + | ++ | - | 0 | - |
| fut.await | 0 | - | + | ++ | ++ | + | - |
| fut.await() | 0 | - | - | ++ | ++ | + | - |
| fut.await!() | 0 | 0 | - | ++ | ++ | + | - |
| fut(await) | - | - | 0 | ++ | ++ | + | - |
| fut await | - | - | + | ++ | ++ | - | - |
| fut@ | - | - | ++ | - | ++ | ++ | - |
| fut@await | - | 0 | + | ++ | ++ | ++ | 0 |

Bagi saya rasanya kita harus mencerminkan sintaks try!() pada potongan pertama dan mendapatkan beberapa penggunaan nyata dari penggunaan await!(expr) sebelum memperkenalkan sintaks lainnya.

Namun, jika / ketika kita membuat sintaks alternatif ..

Saya pikir @ terlihat jelek, "di" untuk "async" tidak terasa begitu intuitif bagi saya, dan simbol sudah digunakan untuk pencocokan pola.

async awalan tanpa tanda kurung mengarah ke prioritas yang tidak jelas atau tanda kurung di sekitarnya saat digunakan dengan ? (yang akan sering).

Postfix .await!() berantai dengan baik, terasa cukup jelas dalam maknanya, menyertakan ! untuk memberitahu saya bahwa ini akan melakukan sihir, dan secara sintaksis kurang novel, jadi pendekatan "pemotongan berikutnya" saya secara pribadi akan menyukai yang ini. Yang mengatakan, bagi saya masih harus dilihat seberapa besar itu akan meningkatkan kode nyata selama potongan pertama await! (expr) .

Saya lebih suka operator awalan untuk kasus sederhana:
let result = await task;
Rasanya jauh lebih alami mengingat kita tidak menulis jenis hasil, jadi menunggu membantu secara mental saat membaca kiri ke kanan untuk memahami bahwa hasil adalah tugas dengan menunggu.
Bayangkan seperti ini:
let result = somehowkindoflongtask await;
hingga Anda tidak mencapai akhir tugas, Anda tidak menyadari bahwa jenis yang dikembalikan harus ditunggu. Perlu diingat juga (meskipun ini dapat berubah dan tidak secara langsung terkait dengan masa depan bahasa) bahwa IDE sebagai Intellij menyebariskan jenisnya (tanpa personalisasi apa pun, jika itu mungkin) antara nama dan yang sederajat.
Bayangkan seperti ini:
6voler6ykj

Itu tidak berarti pendapat saya seratus persen terhadap awalan. Saya sangat menyukai versi postfix masa depan ketika hasil terlibat, karena itu terasa jauh lebih alami. Tanpa konteks apa pun, saya dapat dengan mudah membedakan mana dari ini yang berarti apa:
future await?
future? await
Alih-alih lihat yang ini, mana di antara keduanya yang benar, dari sudut pandang seorang pemula:
await future? === await (future?)
await future? === (await future)?

Saya menyukai kata kunci awalan: await future .

Ini adalah salah satu yang digunakan oleh sebagian besar bahasa pemrograman yang memiliki async / await, dan oleh karena itu langsung akrab bagi orang yang mengetahui salah satunya.

Adapun diutamakan await future? , apa kasus umumnya?

  • Fungsi yang mengembalikan Result<Future> yang harus ditunggu.
  • Masa depan yang harus ditunggu yang mengembalikan Result : Future<Result> .

Saya pikir kasus kedua jauh lebih umum ketika berhadapan dengan skenario tipikal, karena operasi I / O mungkin gagal. Karena itu:

await future? <=> (await future)?

Dalam kasus pertama yang kurang umum, boleh saja memiliki tanda kurung: await (future?) . Ini bahkan mungkin dapat digunakan dengan baik untuk makro try! jika tidak digunakan lagi: await try!(future) . Dengan cara ini, operator await dan tanda tanya tidak berada di sisi yang berbeda di masa depan.

Mengapa tidak mengambil await sebagai parameter fungsi async ?

async fn await_chain() -> Result<usize, Error> {
    let _ = partial_computation(await)
        .unwrap_or_else(or_recover)
        .run(await)?;
}

client.get("https://my_api")
    .send(await)?
    .json(await)?

let output = future
    .run(await);

Di sini future.run(await) adalah alternatif dari await future .
Bisa jadi hanya fungsi biasa async yang membutuhkan masa depan dan menjalankan makro await!() di atasnya.

C ++ (Concurrency TR)

auto result = co_await task;

Ini ada di Coroutines TS, bukan konkurensi.

Opsi lain dapat menggunakan become kata kunci alih-alih await :

async fn become_chain() -> Result<usize, Error> {
    let _ = partial_computation_future(become)
        .unwrap_or_else(or_recover_future)
        .start(become)?;
}

client.get("https://my_api")
    .send_future(become)?
    .json_future(become)?

let output = future.start(become);

become bisa menjadi kata kunci untuk jaminan TCO

Terima kasih @EyeOfPython untuk [ikhtisar] itu. Itu sangat berguna bagi orang yang baru saja bergabung dengan gudang sepeda.

Secara pribadi saya berharap kita menjauh dari f await , hanya karena sintaksnya sangat tidak sederhana dan membuatnya tampak agak istimewa dan ajaib. Ini akan menjadi salah satu hal yang membuat pengguna baru Rust menjadi bingung tentang banyak hal dan saya merasa itu tidak menambahkan banyak kejelasan, bahkan untuk veteran Rust, menjadi sepadan.

@novacrazy Masalah dengan argumen ini adalah bahwa setiap await! macro _would_ menjadi ajaib, itu melakukan operasi yang tidak mungkin dalam kode yang ditulis pengguna

@ Nemo157 Saya setuju bahwa makro await! akan menjadi keajaiban, tetapi saya berpendapat bahwa ini bukan masalah. Ada beberapa makro seperti itu di std , misalnya compile_error! dan saya belum melihat ada yang mengeluh tentang mereka. Saya pikir itu normal untuk menggunakan makro hanya memahami apa yang dilakukannya, bukan bagaimana melakukannya.

Saya setuju dengan komentator sebelumnya bahwa postfix akan menjadi yang paling ergonomis, tetapi saya lebih suka memulai dengan awalan-makro await!(expr) dan berpotensi bertransisi ke postfix-macro sekali itu lebih baik daripada memiliki expr.await (bidang kompiler-builtin ajaib) atau expr.await() (metode builtin kompilator ajaib). Keduanya akan memperkenalkan sintaks yang benar-benar baru untuk fitur ini, dan IMO yang hanya membuat bahasanya terasa tidak konsisten.

@EyeOfPython Pikiran menambahkan future(await) ke daftar dan tabel Anda? Semua hal positif dari penilaian Anda tentang future.await() tampaknya ditransfer tanpa kelemahan

Karena beberapa orang berpendapat bahwa meskipun penyorotan sintaks, foo.await terlihat terlalu mirip dengan akses bidang, kita dapat mengubah token . menjadi # dan sebagai gantinya menulis foo#await . Sebagai contoh:

let foo = alpha()#await?
    .beta#await
    .some_other_stuff()#await?
    .even_more_stuff()#await
    .stuff_and_stuff();

Untuk mengilustrasikan bagaimana GitHub akan membuat ini dengan penyorotan sintaks, mari kita ganti await dengan match karena panjangnya sama:

let foo = alpha()#match?
    .beta#match
    .some_other_stuff()#match?
    .even_more_stuff()#match
    .stuff_and_stuff();

Ini tampak jelas dan ergonomis.

Alasan untuk # bukan beberapa token lain tidak spesifik, tetapi token cukup terlihat yang membantu.

Jadi, konsep lain: jika masa depan adalah referensi :

async fn log_service(&self) -> T {
   let service = self.myService.foo(); // Only construction
   *self.logger.log("beginning service call");
   let output = *service.exec(); // Actually wait for its result
   *self.logger.log("foo executed with result {}.", output);
   output
}

async fn try_log(message: String) -> Result<usize, Error> {
    let logger = *acquire_lock();
    // Very terse, construct the future, wait on it, branch on its result.
    let length = (*logger.log_into(message))?;
    *logger.timestamp();
    Ok(length)
}

async fn await_chain() -> Result<usize, Error> {
    *(*partial_computation()).unwrap_or_else(or_recover);
}

(*(*client.get("https://my_api").send())?.json())?

let output = *future;

Ini akan sangat buruk dan tidak konsisten. Biarkan setidaknya disambiguasi sintaks itu:

async fn log_service(&self) -> T {
   let service = self.myService.foo(); // Only construction
   $self.logger.log("beginning service call");
   let output = $service.exec(); // Actually wait for its result
   $self.logger.log("foo executed with result {}.", output);
   output
}

async fn try_log(message: String) -> Result<usize, Error> {
    let logger = $acquire_lock();
    // Very terse, construct the future, wait on it, branch on its result.
    let length = ($logger.log_into(message))?;
    $logger.timestamp();
    Ok(length)
}

async fn await_chain() -> Result<usize, Error> {
    $($partial_computation()).unwrap_or_else(or_recover);
}

($($client.get("https://my_api").send())?.json())?

let output = $future;

Lebih baik, tapi tetap jelek (dan lebih buruk lagi itu membuat penyorotan sintaks github). Namun, untuk mengatasinya mari perkenalkan kemampuan untuk menunda operator awalan :

async fn log_service(&self) -> T {
   let service = self.myService.foo(); // Only construction
   self.logger.$log("beginning service call");
   let output = service.$exec(); // Actually wait for its result
   self.logger.$log("foo executed with result {}.", output);
   output
}

async fn try_log(message: String) -> Result<usize, Error> {
    let logger = $acquire_lock();
    // Very terse, construct the future, wait on it, branch on its result.
    let length = logger.$log_into(message)?;
    logger.$timestamp();
    Ok(length)
}

async fn await_chain() -> Result<usize, Error> {
    ($partial_computation()).$unwrap_or_else(or_recover);
}

client.get("https://my_api").$send()?.$json()?

let output = $future;

Inilah yang saya inginkan! Tidak hanya untuk menunggu ( $ ) tetapi juga untuk deref ( * ) dan meniadakan ( ! ).

Beberapa peringatan dalam sintaks ini:

  1. Prioritas operator: bagi saya sudah jelas, tetapi untuk pengguna lain?
  2. Interop makro: akankah simbol $ menyebabkan masalah di sini?
  3. Ketidakkonsistenan ekspresi: operator awalan terdepan berlaku untuk seluruh ekspresi sementara operator awalan tertunda hanya berlaku untuk ekspresi dipisahkan . ( await_chain fn menunjukkan itu), apakah itu membingungkan?
  4. Parsing dan implementasi: apakah sintaks itu valid sama sekali?

@Sentril
IMO # terlalu dekat dengan sintaks literal mentah r#"A String with "quotes""#

IMO # terlalu dekat dengan sintaks literal mentah r#"A String with "quotes""#

Tampaknya cukup jelas dari konteksnya apa perbedaan dalam kasus ini.

@Sentril
Memang, tetapi sintaksnya juga sangat asing dengan gaya Rust menggunakan IMHO. Itu tidak menyerupai sintaks yang ada dengan fungsi serupa.

@Laaas Juga tidak ? saat diperkenalkan. Saya akan senang menggunakan .await ; tetapi yang lain tampak tidak senang dengan itu jadi saya mencoba menemukan sesuatu yang lain yang bekerja (yaitu jelas, ergonomis, cukup mudah untuk mengetik, dapat dirantai, memiliki prioritas yang baik) dan foo#await tampaknya memenuhi semua itu.

? memiliki banyak poin bagus lainnya, sementara #await tampak agak sewenang-wenang. Bagaimanapun, jika Anda menginginkan sesuatu seperti .await , mengapa tidak .await! ?

@Centril Itulah alasan utama di balik future(await) , untuk menghindari akses bidang sementara tidak harus menambahkan operator tambahan atau sintaks asing.

sementara #await tampak agak sembarangan.

# sewenang-wenang, ya - apakah itu berhasil atau gagal? Ketika sintaks baru ditemukan di beberapa titik, itu harus sewenang-wenang karena seseorang menganggapnya bagus atau masuk akal.

kenapa tidak .await! ?

Itu sama-sama sewenang-wenang tetapi saya tidak menentangnya.

@Centril Itulah alasan utama di balik future(await) , untuk menghindari akses bidang sementara tidak harus menambahkan operator tambahan atau sintaks asing.

Alih-alih itu tampak seperti aplikasi fungsi di mana Anda meneruskan await ke future ; yang menurut saya lebih membingungkan daripada "akses lapangan".

Sebaliknya, tampaknya aplikasi fungsi yang Anda lewati menunggu masa depan; yang menurut saya lebih membingungkan daripada "akses lapangan".

Bagi saya itu adalah bagian dari keringkasan. Await dalam banyak aspek seperti pemanggilan fungsi (callee dijalankan antara Anda dan hasil Anda), dan meneruskan kata kunci ke fungsi itu memperjelas bahwa ini adalah jenis panggilan yang berbeda. Tetapi saya melihat bagaimana ini bisa membingungkan bahwa kata kunci tersebut mirip dengan argumen lain. (Minus syntax highlighting dan compiler error messages mencoba memperkuat pesan itu. Futures tidak dapat disebut dengan cara lain selain menunggu dan sebaliknya).

@Sentril

Menjadi sewenang-wenang bukanlah hal yang negatif , tetapi memiliki kemiripan dengan sintaks yang ada adalah hal yang positif. .await! tidak sembarangan, karena ini bukan sintaks yang sepenuhnya baru; lagipula, itu hanya makro pasca-perbaikan bernama await .

Untuk memperjelas / menambah poin tentang sintaks baru khusus untuk await , yang saya maksud dengan itu adalah memperkenalkan sintaks baru yang tidak seperti sintaks yang sudah ada. Ada banyak kata kunci awalan, beberapa di antaranya telah ditambahkan setelah Rust 1.0 (mis. union ), tetapi AFAIK bukan satu kata kunci postfix (tidak peduli apakah dipisahkan oleh spasi, titik, atau yang lainnya) . Saya juga tidak dapat memikirkan bahasa lain yang memiliki kata kunci postfix.

IMHO satu-satunya sintaks postfix yang tidak secara signifikan meningkatkan keanehan Rust adalah makro postfix, karena itu mencerminkan panggilan metode tetapi dapat dengan jelas diidentifikasi sebagai makro oleh siapa saja yang telah melihat makro Rust sebelumnya.

Saya juga menyukai makro standar await!() . Jelas dan sederhana.
Saya lebih suka akses seperti field (atau postfix lain) untuk hidup berdampingan sebagai awaited .

  • Menunggu terasa Anda akan menunggu apa yang datang berikutnya / benar. Ditunggu, untuk apa yang datang sebelum / pergi.
  • Koheren dengan metode cloned() di iterator.

Dan saya juga menyukai @? sebagai ? seperti gula.
Ini bersifat pribadi, tetapi saya sebenarnya lebih suka kombinasi &? , karena @ cenderung terlalu tinggi dan mengurangi garis, yang sangat tidak dapat dilacak. & juga tinggi (bagus) tetapi tidak merusak (juga bagus). Sayangnya & sudah memiliki arti, meskipun akan selalu diikuti oleh ? .. Kurasa?

Misalnya.

Lorem@?
  .ipsum@?
  .dolor()@?
  .sit()@?
  .amet@?
Lorem@?.ipsum@?.dolor()@?.sit()@?.amet@?

Lorem&?
  .ipsum&?
  .dolor()&?
  .sit()&?
  .amet&?
Lorem&?.ipsum&?.dolor()&?.sit()&?.amet&?

Bagi saya, @ terasa seperti karakter yang membengkak, mengganggu alur membaca. Di sisi lain, &? terasa menyenangkan, tidak mengganggu bacaan (saya) dan memiliki ruang atas yang bagus antara & dan ? .

Saya pribadi berpikir makro await! harus digunakan. Jika makro pasca-perbaikan masuk ke bahasa tersebut, maka makro dapat diperluas dengan mudah, bukan?

@Laaas Sebanyak yang saya inginkan, belum ada makro postfix di Rust, jadi mereka adalah sintaks baru. Perhatikan juga bahwa foo.await!.bar dan foo.await!().bar bukanlah sintaks permukaan yang sama. Dalam kasus terakhir akan ada postfix dan makro bawaan yang sebenarnya (yang mengharuskan menyerah pada await sebagai kata kunci).

@tokopedia

Ada banyak kata kunci awalan, beberapa di antaranya telah ditambahkan setelah Rust 1.0 (mis. union ),

union bukanlah operator ekspresi unary sehingga tidak relevan dalam perbandingan ini. Ada tepat 3 operator awalan unary stabil di Rust ( return , break , dan continue ) dan semuanya diketik dengan ! .

Hmm ... Dengan @ proposal saya

client.get("https://my_api").@send()?.@json()?

let output = @future;

let foo = (@alpha())?
    .<strong i="8">@beta</strong>
    .@some_other_stuff()?
    .@even_more_stuff()
    .stuff_and_stuff();

@Centril Itulah maksud saya, karena kami belum memiliki makro pasca-perbaikan, seharusnya hanya await!() . Juga maksud saya .await!() FYI. Saya tidak berpikir await perlu menjadi kata kunci, meskipun Anda dapat memesannya jika Anda merasa itu bermasalah.

union bukanlah operator ekspresi unary sehingga tidak relevan dalam perbandingan ini. Ada tepat 3 operator awalan unary stabil di Rust ( return , break , dan continue ) dan semuanya diketik dengan ! .

Ini tetap berarti bahwa variasi awalan-kata kunci cocok dengan grup operator ekspresi unary, meskipun diketik secara berbeda. Varian kata kunci postfix adalah perbedaan yang jauh lebih besar dari sintaks yang ada, salah satu yang terlalu besar dalam kaitannya dengan pentingnya await dalam bahasa menurut saya.

Mengenai makro awalan dan kemungkinan dialihkan ke pasca-perbaikan: await harus tetap menjadi kata kunci agar ini berfungsi. Makro kemudian akan menjadi item bahasa khusus di mana ia diizinkan untuk menggunakan kata kunci sebagai nama tanpa tambahan r# , dan r#await!() dapat tetapi mungkin tidak menggunakan makro yang sama. Selain itu, tampaknya solusi paling pragmatis untuk membuatnya tersedia.

Artinya, simpan await sebagai kata kunci tapi buat await! menyelesaikan makro item-lang.

@HeroicKatora mengapa harus tetap menjadi kata kunci agar dapat berfungsi?

@Laaas Karena jika kita ingin menjaga kemungkinan transisi tetap terbuka, kita harus tetap terbuka untuk sintaks pasca-perbaikan di masa mendatang yang menggunakannya sebagai kata kunci. Oleh karena itu, kami perlu menjaga reservasi kata kunci sehingga kami tidak memerlukan jeda edisi untuk transisi.

@Sentril

Perhatikan juga bahwa foo.await! .Bar dan foo.await! (). Bar bukanlah sintaks permukaan yang sama. Dalam kasus terakhir akan ada postfix aktual dan makro bawaan (yang mengharuskan berhenti menunggu sebagai kata kunci).

Apakah ini tidak dapat diselesaikan dengan membuat kata kunci await digabungkan dengan ! menyelesaikan makro internal (yang tidak dapat didefinisikan melalui cara normal)? Kemudian itu tetap menjadi kata kunci tetapi memutuskan ke makro dalam sintaks makro sebaliknya.

@HeroicKatora Mengapa await dalam x.await!() menjadi kata kunci yang dipesan?

Ini tidak akan terjadi, tetapi jika kami tetap mempertahankan pasca-perbaikan tidak terselesaikan, itu tidak perlu menjadi solusi yang kami dapatkan dalam diskusi selanjutnya. Jika itu adalah kemungkinan terbaik yang disepakati secara unik, maka kita harus mengadopsi sintaks pasca-perbaikan yang tepat ini sejak awal.

Ini adalah waktu lain kami menemukan sesuatu yang bekerja jauh lebih baik sebagai operator postfix. Contoh besar dari ini adalah try! yang akhirnya kami berikan simbolnya sendiri ? . Namun menurut saya ini bukan terakhir kalinya operator postfix lebih optimal dan kami tidak dapat memberikan semua karakter khususnya sendiri. Jadi saya pikir kita setidaknya tidak harus memulai dengan @ . Akan jauh lebih baik jika kita memiliki cara untuk melakukan hal-hal seperti ini. Itulah mengapa saya mendukung gaya makro postfix .await!() .

let x = foo().try!();
let y = bar().await!();

Tetapi untuk membuat makro postfix masuk akal, mereka sendiri harus diperkenalkan. Oleh karena itu saya pikir akan lebih baik untuk memulai dengan sintaks makro normal await!(foo) . Kami kemudian dapat mengembangkan ini menjadi foo.await!() atau bahkan foo@ jika kami merasa ini cukup penting untuk menjamin simbolnya sendiri.
Makro ini membutuhkan sedikit keajaiban bukanlah hal baru bagi saya dan bagi saya bukanlah masalah besar.
Seperti yang dikatakan @jplatte :

@ Nemo157 Saya setuju bahwa makro await! akan menjadi keajaiban, tetapi saya berpendapat bahwa ini bukan masalah. Ada beberapa makro seperti itu di std , misalnya compile_error! dan saya belum melihat ada yang mengeluh tentang mereka. Saya pikir itu normal untuk menggunakan makro hanya memahami apa yang dilakukannya, bukan bagaimana melakukannya.

Masalah berulang yang saya lihat dibahas di sini adalah tentang rantai dan cara menggunakan
menunggu dalam ekspresi rantai. Mungkinkah ada solusi lain?

Jika kita ingin tidak menggunakan menunggu untuk dirantai dan hanya menunggu untuk digunakan
tugas, kita bisa memiliki sesuatu seperti mengganti let dengan await:
await foo = future

Kemudian untuk chaining kita bisa membayangkan beberapa jenis operasi seperti await res = fut1 -> fut2 atau await res = fut1 >>= fut2 .

Satu-satunya kasus yang hilang adalah menunggu dan mengembalikan hasilnya, beberapa jalan pintas
untuk await res = fut; res .
Ini dapat dengan mudah dilakukan dengan await fut

Saya tidak berpikir saya telah melihat proposal lain seperti ini (setidaknya untuk
chaining), jadi lepaskan di sini karena menurut saya akan menyenangkan untuk digunakan.

@HeroicKatora Saya telah menambahkan fut(await) ke daftar dan memberi peringkat menurut pendapat saya.

Jika ada yang merasa penilaian saya tidak tepat, tolong beri tahu saya!

Dari segi sintaks, menurut saya .await!() bersih dan memungkinkan untuk dirangkai tanpa menambahkan keanehan apa pun pada sintaks.

Akan tetapi, jika kita mendapatkan makro postfix "nyata", itu akan menjadi sedikit aneh, karena mungkin .r#await!() bisa dibayangi, sedangkan .await!() tidak bisa.

Saya sangat menentang opsi "kata kunci postfix" (dipisahkan dengan spasi seperti: foo() await?.bar() await? ), karena saya menemukan fakta bahwa await digabungkan ke bagian ekspresi berikut, dan bukan bagian tempatnya beroperasi. Saya lebih suka simbol apa pun selain spasi di sini, dan bahkan lebih suka sintaks awalan daripada ini meskipun ada kerugian dengan rantai panjang.

Menurut saya "prioritas yang jelas" (dengan await? gula) jelas merupakan opsi awalan terbaik, karena pembatas wajib adalah masalah yang sangat umum untuk kasus yang sangat umum yaitu menunggu pernyataan tunggal, dan "prioritas yang berguna" sama sekali tidak intuitif, dan dengan demikian membingungkan.

Dari segi sintaks, menurut saya .await!() bersih dan memungkinkan untuk dirangkai tanpa menambahkan keanehan apa pun pada sintaks.

Akan tetapi, jika kita mendapatkan makro postfix "nyata", itu akan menjadi sedikit aneh, karena mungkin .r#await!() bisa dibayangi, sedangkan .await!() tidak bisa.

Jika kita mendapatkan makro postfix dan kita menggunakan .await!() maka kita dapat membatalkan cadangan await sebagai kata kunci dan membuatnya menjadi makro postfix. Implementasi makro ini masih membutuhkan sedikit keajaiban tetapi itu akan menjadi makro nyata seperti compiler_error! saat ini.

@EyeOfPython Bisakah Anda menjelaskan secara rinci perubahan mana yang Anda pertimbangkan di forward-compatibility ? Saya tidak yakin dengan cara mana fut@await akan menilai lebih tinggi dari fut.await!() dan await { future } lebih rendah dari await!(future) . Kolom verbosity juga tampak agak aneh, beberapa ekspresi pendek tetapi memiliki peringkat yang lebih rendah, apakah ini mempertimbangkan pernyataan yang dirantai, dll. Segala sesuatu yang lain tampak seimbang dan sejauh ini seperti penilaian ekstensif yang paling ringkas.

Setelah membaca diskusi ini, tampaknya banyak orang ingin menambahkan makro await!() normal dan mencari tahu versi postfix nanti. Itu dengan asumsi kita sebenarnya menginginkan versi postfix, yang akan saya anggap benar untuk sisa komentar ini.

Jadi saya hanya ingin mengumpulkan pendapat semua orang di sini: Haruskah kita semua menyetujui sintaks await!(future) UNTUK SEKARANG? Keunggulannya adalah tidak ada ambiguitas penguraian dengan sintaks itu, dan ini juga makro, jadi tidak ada perubahan sintaks bahasa untuk mendukung perubahan ini. Kekurangannya adalah akan terlihat jelek untuk dirantai, tetapi itu tidak masalah karena sintaks ini dapat dengan mudah diganti dengan versi postfix secara otomatis.

Setelah kita menyelesaikannya dan mengimplementasikannya ke dalam bahasa, kita dapat melanjutkan diskusi untuk sintaks menunggu postfix dengan pengalaman, ide, dan mungkin fitur kompilator lainnya yang diharapkan lebih matang.

@HeroicKatora Saya memberi peringkat lebih tinggi karena dapat membebaskan await untuk digunakan sebagai pengenal biasa secara alami dan itu akan memungkinkan operator postfix lain untuk ditambahkan, sedangkan saya pikir fut.await!() akan lebih baik jika await telah dipesan. Namun, saya tidak yakin apakah ini masuk akal, tampaknya juga valid bahwa fut.await!() bisa lebih tinggi.

Untuk await { future } vs await!(future) , yang terakhir membuat opsi tetap terbuka untuk mengubah hampir semua opsi lain, sedangkan yang pertama hanya benar-benar mengizinkan salah satu dari varian await future ( seperti yang tercantum dalam entri blog @withoutboats ). Jadi saya pikir itu pasti fwd(await { future }) < fwd(await!(future)) .

Mengenai verbositas, Anda benar, setelah membagi kasus menjadi subkelompok, saya tidak mengevaluasi ulang verbositasnya, yang seharusnya menjadi yang paling obyektif dari semuanya.

Saya akan mengeditnya untuk mempertimbangkan komentar Anda, terima kasih!

Menstabilkan await!(future) adalah tentang opsi terburuk yang dapat saya bayangkan:

  1. Itu berarti kita harus membebaskan await yang selanjutnya berarti desain bahasa masa depan semakin sulit.
  2. Itu secara sadar mengambil rute yang sama seperti dengan try!(result) yang tidak kami gunakan lagi (dan yang mengharuskan penulisan r#try!(result) di Rust 2018).

    • Jika kita tahu bahwa await!(future) adalah sintaks yang buruk yang pada akhirnya ingin kita hentikan, ini dengan sengaja menciptakan hutang teknis.

    • Selain itu, try!(..) didefinisikan di Rust sedangkan await!(future) tidak bisa dan malah akan menjadi sihir kompiler.

    • Menghentikan try!(..) tidaklah mudah dan merugikan pergaulan. Mengalami cobaan itu lagi sepertinya tidak menarik.

  3. Ini akan menggunakan sintaks makro untuk salah satu bagian sentral dan penting dari bahasa; yang sepertinya bukan kelas satu.
  4. await!(future) berisik ; tidak seperti await future Anda perlu menulis !( ... ) .
  5. Rust API, dan terutama pustaka standar, berpusat di sekitar sintaks pemanggilan metode. Misalnya, adalah hal yang umum untuk metode berantai ketika berhadapan dengan Iterator s. Mirip dengan await { future } dan await future , sintaks await!(future) akan membuat perangkaian metode menjadi sulit dan menyebabkan ikatan sementara let . Ini buruk untuk ergonomi dan menurut pandangan saya untuk keterbacaan.

@Centril Saya ingin setuju tetapi ada beberapa pertanyaan terbuka. Apakah Anda yakin tentang 1. ? Jika kita membuatnya 'ajaib' tidak bisakah kita membuatnya lebih ajaib dengan membiarkan ini merujuk ke makro tanpa menjatuhkannya sebagai kata kunci?

Saya terlambat bergabung dengan Rust untuk mengevaluasi perspektif sosial dari 2. . Mengulangi kesalahan tidak terdengar menarik tetapi karena beberapa kode masih tidak dikonversi dari try! harus jelas bahwa itu adalah solusi. Itu memunculkan pertanyaan apakah kita bermaksud untuk memiliki async/await sebagai insentif untuk bermigrasi ke edisi 2018 atau lebih tepatnya bersabar dan tidak mengulangi ini.

Dua fitur utama (imho) lainnya sangat mirip dengan 3. : vec![] dan format! / println! . Yang pertama sangat banyak karena tidak ada konstruksi kotak yang stabil afaik, yang terakhir karena konstruksi format string dan tidak memiliki ekspresi yang diketik secara dependen. Saya pikir perbandingan ini juga menempatkan 4. ke dalam perspektif lain.

Saya menentang sintaks yang tidak terbaca seperti bahasa Inggris. IE, "await x" berbunyi seperti bahasa Inggris. "x # !! @! &" tidak. "x.await" dibaca dengan menggoda seperti bahasa Inggris, tetapi tidak akan bisa jika x adalah baris non-sepele, seperti pemanggilan fungsi anggota dengan nama yang panjang, atau sekumpulan metode iterator berantai, dll.

Lebih khusus lagi, saya mendukung "kata kunci x", di mana kata kuncinya mungkin await . Saya datang dari menggunakan C ++ coroutines TS dan unity's c # coroutines, yang keduanya menggunakan sintaks yang sangat mirip dengan itu. Dan setelah bertahun-tahun menggunakannya dalam kode produksi, keyakinan saya adalah mengetahui di mana titik hasil Anda, secara sekilas, sangatlah penting . Saat Anda menelusuri baris indentasi jika fungsi Anda, Anda dapat memilih setiap co_await / yield return dalam fungsi 200 baris dalam hitungan detik, tanpa beban kognitif.

Hal yang sama tidak berlaku pada operator titik dengan menunggu sesudahnya, atau sintaks "tumpukan simbol" postfix lainnya.

Saya percaya bahwa await adalah operasi aliran kontrol yang fundamental. Itu harus diberi tingkat penghormatan yang sama seperti 'if , while , match , dan return . Bayangkan jika salah satu dari mereka adalah operator postfix - membaca kode Rust akan menjadi mimpi buruk. Seperti argumen saya untuk menunggu, karena Anda dapat menelusuri baris indentasi dari fungsi Rust dan segera memilih semua aliran kontrol. Ada pengecualian, tapi itu pengecualian, dan bukan sesuatu yang harus kita perjuangkan.

Saya setuju dengan @ejmahler. Kita tidak boleh melupakan sisi lain dari pengembangan - review kode. File dengan kode sumber lebih sering dibaca daripada ditulis, oleh karena itu menurut saya file dengan kode sumber lebih mudah dibaca dan dipahami daripada menulis. Menemukan poin hasil sangat penting dalam tinjauan kode. Dan saya pribadi akan memilih Useful precedence .
Saya percaya bahwa ini:

...
let response = await client.get("https://my_api").send()?;
let body: MyResponse = await response.into_json()?;

lebih mudah untuk dipahami daripada ini:

...
let body: MyResponse = client.get("https://my_api").send().await?.into_json().await?;

@Tokopedia

@Centril Saya ingin setuju tetapi ada beberapa pertanyaan terbuka. Apakah Anda yakin tentang 1. ? Jika kita membuatnya 'ajaib' tidak bisakah kita membuatnya lebih ajaib dengan membiarkan ini merujuk ke makro tanpa menjatuhkannya sebagai kata kunci?

Secara teknis? Mungkin. Namun, harus ada pembenaran yang kuat untuk kasus-kasus khusus dan dalam kasus ini memiliki sihir di atas sihir tampaknya tidak dibenarkan.

Itu memunculkan pertanyaan apakah kita bermaksud untuk memiliki async/await sebagai insentif untuk bermigrasi ke edisi 2018 atau lebih tepatnya bersabar dan tidak mengulangi ini.

Saya tidak tahu apakah kami pernah mengatakan kami bermaksud untuk sistem modul baru, async / await , dan try { .. } untuk menjadi insentif; tetapi terlepas dari niat kami, dan saya pikir itu hal yang baik. Kami ingin orang-orang pada akhirnya mulai menggunakan fitur bahasa baru untuk menulis perpustakaan yang lebih baik dan lebih idiomatis.

Dua fitur utama (imho) lainnya sangat mirip dengan 3. : vec![] dan format! / println! . Bekas sangat banyak karena tidak ada konstruksi boks yang stabil afaik,

Yang pertama ada, dan ditulis vec![1, 2, 3, ..] , untuk meniru ekspresi literal array, misalnya [1, 2, 3, ..] .

@ejmah

"x.await" dibaca dengan menggoda seperti bahasa Inggris, tetapi tidak akan bisa jika x adalah baris non-sepele, seperti pemanggilan fungsi anggota dengan nama yang panjang, atau sekumpulan metode iterator berantai, dll.

Apa yang salah dengan sekumpulan metode iterator yang dirantai? Itu jelas Rust idiomatik.
Alat rustfmt juga akan memformat rantai metode pada baris yang berbeda sehingga Anda mendapatkan (sekali lagi menggunakan match untuk menampilkan penyorotan sintaks):

let foo = alpha().match?  // or `alpha() match?`, `alpha()#match?`, `alpha().match!()?`
    .beta
    .some_other_stuff().match?
    .even_more_stuff().match
    .stuff_and_stuff();

Jika Anda membaca .await sebagai "maka tunggu" itu terbaca dengan sempurna, setidaknya bagi saya.

Dan setelah bertahun-tahun menggunakannya dalam kode produksi, keyakinan saya adalah mengetahui di mana titik hasil Anda, secara sekilas, sangatlah penting .

Saya tidak melihat bagaimana postfix await meniadakannya, terutama dalam format rustfmt atas. Selain itu, Anda dapat menulis:

let foo = alpha().match?;
let bar = foo.beta.some_other_stuff().match?;
let baz = bar..even_more_stuff().match;
let quux = baz.stuff_and_stuff();

jika Anda suka itu.

dalam fungsi 200 baris dalam hitungan detik, tanpa beban kognitif.

Tanpa mengetahui terlalu banyak tentang fungsi tertentu, menurut saya 200 LOC mungkin melanggar prinsip tanggung jawab tunggal dan melakukan terlalu banyak. Solusinya adalah membuatnya bekerja lebih sedikit dan membaginya. Faktanya, saya pikir itu adalah hal terpenting untuk pemeliharaan dan keterbacaan.

Saya percaya bahwa await adalah operasi aliran kontrol yang mendasar.

Jadi adalah ? . Nyatanya, await dan ? keduanya adalah operasi aliran kontrol yang efektif yang mengatakan "ekstrak nilai di luar konteks". Dengan kata lain, dalam konteks lokal, Anda dapat membayangkan operator ini memiliki tipe await : impl Future<Output = T> -> T dan ? : impl Try<Ok = T> -> T .

Ada pengecualian, tapi itu pengecualian, dan bukan sesuatu yang harus kita perjuangkan.

Dan pengecualian di sini adalah ? ?

@dyteachman

Saya setuju dengan @ejmahler. Kita tidak boleh melupakan sisi lain dari pengembangan - review kode. File dengan kode sumber lebih sering dibaca daripada ditulis, oleh karena itu menurut saya file dengan kode sumber lebih mudah dibaca dan dipahami daripada menulis.

Ketidaksepakatan adalah tentang apa yang terbaik untuk keterbacaan dan ergonomi.

lebih mudah untuk dipahami daripada ini:

...
let body: MyResponse = client.get("https://my_api").send().await?.into_json().await?;

Ini bukanlah bagaimana itu akan diformat; menjalankannya melalui rustfmt memberi Anda:

let body: MyResponse = client
    .get("https://my_api")
    .send()
    .match?
    .into_json()
    .match?;

@ejmahler @andreytkachenko Saya setuju dengan @Centril di sini, perubahan terbesar (beberapa mungkin mengatakan peningkatan, saya tidak akan) yang Anda peroleh dari sintaks awalan adalah bahwa pengguna diberi insentif untuk membagi pernyataan mereka ke beberapa baris karena yang lainnya tidak dapat dibaca. Itu bukan Rust-y dan aturan pemformatan biasa menggantikannya dalam sintaks pasca-perbaikan. Saya juga menganggap titik hasil menjadi lebih tidak jelas dalam sintaks awalan karena await sebenarnya tidak ditempatkan pada titik kode tempat Anda menghasilkan, bukan sebaliknya.

Jika Anda melakukan cara ini, mari bereksperimen untuk mengejanya, bukan dengan await sebagai pengganti let dalam semangat ide @Keruspe untuk benar-benar menerapkannya. Tanpa ekstensi sintaks lainnya karena tampak seperti peregangan.

await? response = client.get("https://my_api").send();
await? body: MyResponse = response.into_json();

Tetapi untuk semua ini, saya melihat manfaat yang cukup untuk menjelaskan kerugian komposabilitas dan komplikasi dalam tata bahasa.

Hm ... apakah diinginkan memiliki bentuk awalan dan sufiks menunggu? Atau hanya bentuk sufiks?

Panggilan metode rantai bersama adalah idiomatik khusus ketika berbicara tentang
iterator dan pilihan yang jauh lebih sedikit, tetapi selain itu, karat adalah
bahasa imperatif pertama dan bahasa fungsional kedua.

Saya tidak berpendapat bahwa postfix benar-benar tidak bisa dipahami, saya membuat
argumen beban kognitif yang menyembunyikan operasi aliran kontrol tingkat atas,
membawa kepentingan yang sama dengan 'kembali', meningkatkan beban kognitif saat
dibandingkan dengan meletakkannya sedekat mungkin dengan awal baris - dan saya
membuat argumen ini berdasarkan pengalaman produksi bertahun-tahun.

Pada Sabtu, 19 Jan 2019 pukul 11:59 Mazdak Farrokhzad [email protected]
menulis:

@HeroicKatora https://github.com/HeroicKatora

@Centril https://github.com/Centril Saya ingin setuju tetapi ada
beberapa pertanyaan terbuka. Apakah Anda yakin tentang 1.? Kalau kita membuatnya 'ajaib' bisa
kami tidak membuatnya lebih ajaib dengan membiarkan ini merujuk ke makro tanpa
menjatuhkannya sebagai kata kunci?

Secara teknis? Mungkin. Namun, harus ada pembenaran yang kuat
kasus khusus dan dalam kasus ini memiliki sihir di atas sihir tampaknya tidak
dibenarkan.

Itu menimbulkan pertanyaan apakah kita berniat untuk memiliki async / await as
insentif untuk pindah ke edisi 2018 atau lebih tepatnya bersabar dan tidak
ulangi ini.

Saya tidak tahu apakah kami pernah mengatakan kami bermaksud untuk sistem modul baru, async /
tunggu, dan coba {..} menjadi insentif; tapi terlepas dari niat kami
mereka, dan saya pikir itu hal yang baik. Kami ingin orang-orang pada akhirnya
mulai gunakan fitur bahasa baru untuk menulis lebih baik dan lebih idiomatis
perpustakaan.

Dua fitur utama (imho) lainnya seperti 3 .: vec! [] Dan format! /
println !. Bekas sangat banyak karena tidak ada kotak kandang
konstruksi afaik,

Yang pertama ada, dan ditulis vec! [1, 2, 3, ..], untuk meniru larik
ekspresi literal, misalnya [1, 2, 3, ..].

@ejmahler https://github.com/ejmahler

"x.await" dibaca dengan menggoda seperti bahasa Inggris, tetapi tidak akan bisa jika x adalah a
baris non-sepele, seperti panggilan fungsi anggota dengan nama panjang, atau sekelompok
metode iterator berantai, dll.

Apa yang salah dengan sekumpulan metode iterator yang dirantai? Itu jelas
Karat idiomatik.
Alat Rustfmt juga akan memformat rantai metode pada baris yang berbeda jadi Anda
get (sekali lagi menggunakan pencocokan untuk menampilkan penyorotan sintaks):

biarkan foo = alpha (). cocok? // atau alpha() match? , alpha()#match? , alpha().match!()?
.beta
.some_other_stuff (). cocok?
.even_more_stuff (). cocok
.stuff_and_stuff ();

Jika Anda membaca .await sebagai "maka tunggu" itu terbaca dengan sempurna, setidaknya bagi saya.

Dan setelah bertahun-tahun menggunakannya dalam kode produksi, keyakinan saya adalah mengetahui itudi mana poin hasil Anda, sekilas, sangatlah penting .

Saya tidak melihat bagaimana postfix menunggu meniadakannya, terutama di rustfmt
format di atas. Selain itu, Anda dapat menulis:

let foo = alpha (). match?; let bar = foo.beta.some_other_stuff (). match?; let baz = bar..even_more_stuff (). match; let quux = baz.stuff_and_stuff ();

jika Anda suka itu.

dalam fungsi 200 baris dalam hitungan detik, tanpa beban kognitif.

Tanpa mengetahui terlalu banyak tentang fungsi tertentu, menurut saya
bahwa 200 LOC mungkin melanggar prinsip tanggung jawab tunggal dan melakukannya
terlalu banyak. Solusinya adalah membuatnya bekerja lebih sedikit dan membaginya. Faktanya, saya
pikir itu adalah hal terpenting untuk pemeliharaan dan keterbacaan.

Saya percaya bahwa menunggu adalah operasi aliran kontrol yang fundamental.

Begitu juga ?. Sebenarnya, tunggu dan? keduanya adalah operasi aliran kontrol yang efektif
yang mengatakan "ekstrak nilai di luar konteks". Dengan kata lain, di lokal
konteksnya, Anda dapat membayangkan operator ini memiliki tipe await: impl
Masa depan-> T dan? : impl Coba-> T.

Ada pengecualian, tapi itu pengecualian, dan bukan sesuatu yang harus kita lakukan
berusaha untuk.

Dan pengecualiannya di sini? ?

@andreytkachenko https://github.com/andreytkachenko

Saya setuju dengan @ejmahler https://github.com/ejmahler . Seharusnya tidak
lupakan sisi lain dari pengembangan - review kode. File dengan kode sumber adalah
jauh lebih sering dibaca kemudian ditulis, maka menurutku itu harus lebih mudah
membaca dan memahami kemudian menulis.

Ketidaksepakatan tentang apa yang terbaik untuk dibaca dan
ergonomi.

lebih mudah untuk dipahami daripada ini:

... let body: MyResponse = client.get ("https: // my_api") .send (). await? .into_json (). await ?;

Ini bukanlah bagaimana itu akan diformat; format karatfmt idiomatik
adalah:

biarkan tubuh: MyResponse = klien
.get ("https: // my_api")
.Kirim()
.pertandingan?
.into_json ()
.pertandingan?;

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

@ejmah Kami tidak setuju kembali. "bersembunyi"; argumen yang sama dibuat wrt.

Operator ? sangat pendek, dan telah dikritik di masa lalu karena "menyembunyikan" pengembalian. Sering kali kode dibaca, bukan ditulis. Mengganti namanya menjadi ?! akan membuatnya dua kali lebih lama dan karena itu lebih sulit untuk dilewatkan.

Namun demikian, kami akhirnya menstabilkan ? dan sejak itu saya pikir ramalan itu gagal terwujud.
Bagi saya, postfix await mengikuti urutan membaca alami (setidaknya untuk penutur bahasa kiri-ke-kanan). Secara khusus, ini mengikuti urutan aliran data.

Belum lagi penyorotan sintaks: apa pun yang terkait dengan menunggu dapat disorot dengan warna cerah, sehingga dapat ditemukan dalam sekejap. Jadi bahkan jika kita memiliki simbol dan bukan await kata yang sebenarnya, itu masih akan sangat mudah dibaca dan ditemukan dalam kode yang disorot sintaks. Dengan itu, saya masih lebih suka penggunaan kata await hanya untuk alasan grep - lebih mudah untuk grep kode untuk apa pun yang sedang ditunggu jika kita hanya menggunakan kata await daripada simbol seperti @ atau #, yang artinya bergantung pada tata bahasa.

Kalian ini bukan ilmu roket

let body: MyResponse = client.get("https://my_api").send()...?.into_json()...?;

postfix ... sangat mudah dibaca, sekilas sulit untuk dilewatkan dan super intuitif karena Anda secara alami membacanya sebagai jenis kode yang tertinggal sementara menunggu hasil yang akan datang tersedia. tidak perlu prioritas / makro shenanigans dan tidak ada derau baris tambahan dari sigils asing, karena semua orang telah melihat elipsis sebelumnya.

(maaf kepada @solson)

@ ben0x539 Apakah itu berarti saya dapat mengakses anggota hasil saya seperti future()....start ? Atau menunggu hasil kisaran seperti range()..... ? Dan bagaimana tepatnya maksud Anda no precedence/macro shenanigans necessary and karena saat ini elipsis .. membutuhkan operator biner atau paranthesis di sebelah kanan dan ini sekilas sangat dekat.

Ya, itu? Operator ada. Saya sudah mengakui bahwa ada
pengecualian. Tapi itu pengecualian. Sebagian besar aliran kontrol di mana pun
Program Rust terjadi melalui kata kunci awalan.

Pada Sabtu, 19 Jan 2019 pukul 13.51 Benjamin Herr [email protected]
menulis:

Kalian ini bukan ilmu roket

let body: MyResponse = client.get ("https: // my_api") .send () ...?. into_json () ...?;

postfix ... sangat mudah dibaca, sulit untuk dilewatkan dan super
intuitif karena Anda secara alami membacanya sebagai jenis kode yang tertinggal
sementara menunggu hasil masa depan tersedia. tidak
prioritas / kejahatan makro diperlukan dan tidak ada suara saluran tambahan dari
sigils asing, karena semua orang pernah melihat elips sebelumnya.

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

@HeroicKatora Itu terlihat sedikit artifisial tapi tentu saja. Maksud saya karena ini adalah operasi postfix, seperti solusi postfix lain yang disarankan, ini menghindari kebutuhan akan presedensi yang berlawanan dengan intuisi untuk await x? , dan ini bukan makro.

@ejmah

Ya, itu? Operator ada. Saya sudah mengakui bahwa ada pengecualian. Tapi itu pengecualian.

Ada dua bentuk ekspresi yang cocok dengan keyword expr , yaitu return expr dan break expr . Yang pertama lebih umum daripada yang terakhir. Formulir continue 'label tidak benar-benar dihitung karena, meskipun ini adalah ekspresi, ini bukan bentuk keyword expr . Jadi sekarang Anda memiliki 2 kata kunci seluruh awalan bentuk ekspresi unary dan 1 bentuk ekspresi unary postfix. Bahkan sebelum kita memperhitungkan bahwa ? dan await lebih mirip dari await dan return , saya hampir tidak akan menelepon return/break expr aturan untuk ? menjadi pengecualian.

Sebagian besar aliran kontrol dalam program Rust terjadi melalui kata kunci awalan.

Seperti yang telah disebutkan sebelumnya, break expr tidak semuanya umum ( break; lebih umum dan return; lebih umum dan bukan bentuk ekspresi yang tidak sama). Yang tersisa adalah return expr; s awal dan sama sekali tidak jelas bagi saya bahwa ini jauh lebih umum daripada match , ? , hanya bertumpuk if let s dan else s, dan for pengulangan. Setelah try { .. } stabil, saya berharap ? digunakan lebih banyak lagi.

@ ben0x539 Saya rasa kita harus memesan ... untuk variadic generics, setelah kita siap memilikinya

Saya sangat menyukai ide berinovasi dengan sintaks postfix di sini. Alurnya jauh lebih masuk akal dan saya ingat betapa jauh lebih baik kode berubah ketika kita beralih dari awalan try! ke postfix ? . Saya pikir ada banyak orang yang membuat pengalaman di komunitas Rust tentang seberapa banyak peningkatan pada kode yang dibuat.

Jika kita tidak menyukai ide .await Saya yakin beberapa kreativitas dapat dibuat untuk menemukan operator postfix yang sebenarnya. Salah satu contoh bisa saja menggunakan ++ atau @ untuk menunggu.

:( Saya hanya tidak ingin menunggu lagi.

Semua orang merasa nyaman dengan sintaks makro, kebanyakan orang di utas ini yang memulai dengan opini lain tampaknya menyukai sintaks makro.

Tentu ini akan menjadi "makro ajaib" tetapi pengguna jarang peduli tentang seperti apa perluasan makro itu dan bagi mereka yang melakukannya, cukup mudah untuk menjelaskan nuansanya di dokumen.

Sintaks makro biasa mirip seperti pai apel, ini adalah pilihan favorit kedua semua orang tetapi sebagai hasilnya adalah pilihan favorit keluarga [0]. Yang penting, seperti mencoba! kita selalu bisa mengubahnya nanti. Namun yang terpenting, semakin cepat kita semua setuju, semakin cepat kita semua dapat mulai benar-benar menggunakannya dan menjadi produktif!

[0] (Dirujuk pada menit pertama) https://www.ted.com/talks/kenneth_cukier_big_data_is_better_data/transcript?language=en

cocok, jika, jika membiarkan, sementara, sementara membiarkan, dan untuk semua adalah aliran kontrol yang menyebar
yang menggunakan prefiks. Berpura-pura istirahat dan lanjutkan adalah satu-satunya aliran kontrol
kata kunci sangat menyesatkan.

Pada Sabtu, 19 Jan 2019 pukul 15.37 Yazad Daruvala [email protected]
menulis:

:( Saya hanya tidak ingin menunggu lagi.

Setiap orang merasa nyaman dengan sintaks makro, kebanyakan orang di utas ini
mulai dengan pendapat lain tampaknya berakhir dengan sintaks makro.

Tentu itu akan menjadi "makro ajaib" tetapi pengguna jarang peduli tentang apa makro
ekspansi terlihat seperti dan bagi mereka yang melakukannya, cukup mudah untuk menjelaskan
nuansa di dokumen.

Sintaks makro biasa mirip seperti pai apel, ini adalah yang kedua bagi semua orang
pilihan favorit tetapi sebagai hasilnya pilihan favorit keluarga [0].
Yang penting, seperti mencoba! kita selalu bisa mengubahnya nanti. Tapi kebanyakan
yang terpenting, semakin cepat kita semua bisa setuju, semakin cepat kita semua bisa mulai
benar-benar menggunakannya dan menjadi produktif!

[0] (Dirujuk di menit pertama)
https://www.ted.com/talks/kenneth_cukier_big_data_is_better_data/transcript?language=en

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

@mitsuhiko Saya setuju! Postfix terasa lebih kasar karena chaining. Menurut saya sintaks fut@await saya usulkan adalah opsi menarik lainnya yang tampaknya tidak memiliki banyak kerugian seperti proposal lainnya. Saya tidak yakin apakah itu terlalu jauh di luar sana dan versi yang lebih sederhana akan lebih disukai.

@ejmah

match, if, if let, while, while let, dan for adalah semua aliran kontrol yang menggunakan prefiks. Berpura-pura berhenti dan lanjutkan adalah satu-satunya kata kunci aliran kontrol yang menyesatkan.

Itu tidak menyesatkan sama sekali. Tata bahasa yang relevan untuk konstruksi ini kira-kira:

Expr = kind:ExprKind;
ExprKind =
  | If:{ "if" cond:Cond then:Block { "else" else_expr:ElseExpr }? };
  | Match:{ "match" expr:Expr "{" arms:MatchArm* "}" }
  | While:{ { label:LIFETIME ":" }? "while" cond:Cond body:Block }
  | For:{ { label:LIFETIME ":" }? "for" pat:Pat "in" expr:Expr body:Block }
  ;

Cond =
  | Bool:Expr
  | Let:{ "let" pat:Pat "=" expr:Expr }
  ;

ElseExpr =
  | Block:Block
  | If:If
  ;

MatchArm = pats:Pat+ % "|" { "if" guard:Expr }? "=>" body:Expr ","?;

Di sini, formulirnya adalah if/while expr block , for pat in expr block , dan match expr { pat0 => expr0, .., patn => exprn } . Ada kata kunci yang mendahului apa yang mengikuti di semua bentuk ini. Saya rasa inilah yang Anda maksud dengan "menggunakan awalan". Namun, ini semua adalah bentuk blok dan bukan operator prefiks unary. Perbandingan dengan await expr oleh karena itu menyesatkan karena tidak ada konsistensi atau aturan untuk dibicarakan. Jika Anda ingin konsistensi dengan formulir blok, bandingkan dengan await block , bukan await expr .

@mitsuhiko Saya setuju! Postfix terasa lebih kasar karena chaining.

Karat bersifat dualistik. Ini mendukung pendekatan imperatif dan fungsional. Dan saya pikir tidak apa-apa, karena dalam kasus yang berbeda, masing-masing bisa lebih cocok.

Saya tidak tahu. Rasanya akan menyenangkan memiliki keduanya:

await foo.bar();
foo.bar().await;

Setelah menggunakan Scala untuk sementara waktu, saya juga menyukai banyak hal yang bekerja seperti itu. Terutama match dan if akan menyenangkan untuk ditempatkan di posisi postfix di Rust.

foo.bar().await.match {
   Bar1(x, y) => {x==y},
   Bar2(y) => {y==7},
}.if {
   bazinga();
}

Ringkasan sejauh ini

Matriks opsi:

Ringkasan matriks opsi (menggunakan @ sebagai sigil, tetapi bisa jadi apa saja):

| Nama | Future<T> | Future<Result<T, E>> | Result<Future<T>, E> |
| --- | --- | --- | --- |
| PREFIX | - | - | - |
| Makro Kata Kunci | await!(fut) | await!(fut)? | await!(fut?) |
| Fungsi Kata Kunci | await(fut) | await(fut)? | await(fut?) |
| Diutamakan yang Berguna | await fut | await fut? | await (fut?) |
| Diutamakan yang Jelas | await fut | await? fut | await fut? |
| POSTFIX | - | - | - |
| Fn Dengan Kata Kunci | fut(await) | fut(await)? | fut?(await) |
| Bidang Kata Kunci | fut.await | fut.await? | fut?.await |
| Metode Kata Kunci | fut.await() | fut.await()? | fut?.await() |
| Makro Kata Kunci Postfix | fut.await!() | fut.await!()? | fut?.await!() |
| Kata Kunci Spasi | fut await | fut await? | fut? await |
| Sigil Kata Kunci | fut@await | fut@await? | fut?@await |
| Sigil | fut@ | fut@? | fut?@ |

"Sigil Keyword" sigil _cannot_ menjadi # , karena itu Anda tidak dapat melakukannya dengan masa depan yang disebut r . ... karena sigil tidak perlu mengubah tokenisasi seperti kekhawatiran pertama saya .

Lebih banyak penggunaan kehidupan nyata (PM saya kasus penggunaan _real_ lainnya dengan beberapa await di urlo dan saya akan menambahkannya):

| Nama | (barat) Client |> Client::get |> RequestBuilder::send |> await |> ? |> Response::json | > ? |
| --- | --- |
| PREFIX | - |
| Makro Kata Kunci | await!(client.get("url").send())?.json()? |
| Fungsi Kata Kunci | await(client.get("url").send())?.json()? |
| Diutamakan yang Berguna | (await client.get("url").send()?).json()? |
| Diutamakan yang Jelas | (await? client.get("url").send()).json()? |
| POSTFIX | - |
| Fn Dengan Kata Kunci | client.get("url").send()(await)?.json()? |
| Bidang Kata Kunci | client.get("url").send().await?.json()? |
| Metode Kata Kunci | client.get("url").send().await()?.json()? |
| Makro Kata Kunci Postfix | client.get("url").send().await!()?.json()? |
| Kata Kunci Spasi | client.get("url").send() await?.json()? |
| Sigil Kata Kunci | client.get("url").send()@await?.json()? |
| Sigil | client.get("url").send()@?.json()? |

EDIT CATATAN: Telah ditunjukkan kepada saya bahwa mungkin masuk akal untuk Response::json juga mengembalikan Future , di mana send menunggu IO keluar dan json (atau interpretasi lain dari hasil) menunggu IO yang masuk. Saya akan membiarkan contoh ini sebagaimana adanya, karena menurut saya penting untuk menunjukkan bahwa masalah perangkaian berlaku bahkan dengan hanya satu titik tunggu IO dalam ekspresi.

Tampaknya ada konsensus kasar bahwa dari opsi awalan, prioritas yang jelas (bersama dengan gula await? ) adalah yang paling diinginkan. Namun, banyak orang yang mendukung solusi postfix, untuk membuat perangkaian seperti di atas lebih mudah. Meskipun pilihan prefiks memiliki konsensus kasar, tampaknya tidak ada konsensus mengenai solusi postfix mana yang terbaik. Semua opsi yang diusulkan menyebabkan kebingungan yang mudah (dikurangi dengan penyorotan kata kunci):

  • Fn Dengan Kata Kunci => memanggil fn dengan argumen yang disebut await
  • Bidang Kata Kunci => akses bidang
  • Metode Kata Kunci => pemanggilan metode
  • Makro (prefix atau postfix) => adalah await kata kunci atau bukan?
  • Spasi Kata Kunci => memecah pengelompokan dalam satu baris (lebih baik dari beberapa baris?)
  • Sigil => menambahkan sigil baru ke bahasa yang sudah dianggap sigil-heavy

Saran lain yang lebih drastis:

  • Izinkan awalan (diutamakan yang jelas) dan "kolom" postfix (ini dapat diterapkan ke lebih banyak kata kunci seperti match , if , dll di masa mendatang untuk membuat pola ini digeneralisasi, tetapi tidak diperlukan adendum debat ini) [[referensi] (https://github.com/rust-lang/rust/issues/57640#issuecomment-455827164)]
  • await dalam pola untuk menyelesaikan masa depan (tanpa rantai sama sekali) [[referensi] (https://github.com/rust-lang/rust/issues/57640)]
  • Gunakan operator awalan tetapi izinkan penundaan [[referensi] (https://github.com/rust-lang/rust/issues/57640#issuecomment-455782394)]

Menstabilkan dengan makro kata kunci await!(fut) tentu saja kompatibel di masa depan dengan semua hal di atas pada dasarnya, meskipun itu memerlukan pembuatan makro menggunakan kata kunci alih-alih pengenal biasa.

Jika seseorang memiliki contoh sebagian besar-real-ish contoh yang menggunakan dua await dalam satu rantai, saya ingin melihatnya; belum ada yang membagikannya sejauh ini. Bagaimanapun, postfix await juga berguna meskipun anda tidak perlu await lebih dari sekali dalam sebuah rangkaian, seperti yang ditunjukkan pada contoh reqwest.

Jika saya melewatkan sesuatu yang penting di atas komentar ringkasan ini, PM saya di urlo dan saya akan mencoba menambahkannya. (Meskipun saya akan memerlukannya untuk menambahkan komentar orang lain untuk menghindari pilih kasih suara yang keras.)

Secara pribadi, saya secara historis mendukung kata kunci awalan dengan prioritas yang jelas. Saya masih berpikir menstabilkan dengan makro kata kunci await!(fut) akan berguna untuk mengumpulkan informasi dunia nyata tentang di mana menunggu terjadi dalam kasus penggunaan dunia nyata, dan akan tetap memungkinkan kita untuk menambahkan prefiks non-makro atau opsi postfix nanti .

Namun, dalam proses penulisan ringkasan di atas, saya mulai menyukai "Bidang Kata Kunci". "Kata Kunci Spasi" terasa menyenangkan bila dibagi menjadi beberapa baris:

client
    .get("url")
    .send() await?
    .json()?

tetapi pada satu baris, itu membuat pemutusan yang canggung yang mengelompokkan ekspresi dengan buruk: client.get("url").send() await?.json()? . Namun, dengan bidang kata kunci, ini terlihat bagus dalam kedua bentuk: client.get("url").send().await?.json()?

client
    .get("url")
    .send()
    .await?
    .json()?

meskipun saya kira "metode kata kunci" akan mengalir lebih baik, karena ini adalah tindakan. Kami _dapat_ bahkan membuatnya menjadi metode "nyata" pada Future jika kami ingin:

trait Future<..> {
    ..
    extern "rust-await" fn r#await(self) -> _;
}

( extern "rust-await" tentu saja akan menyiratkan semua keajaiban yang diperlukan untuk benar-benar melakukan menunggu dan itu tidak akan benar-benar menjadi fn nyata, itu terutama hanya ada di sana karena sintaksnya terlihat seperti metode, jika kata kunci metode digunakan.)

Izinkan kedua awalan (diutamakan yang jelas) dan "bidang" postfix ...

Jika ada sintaks postfix yang dipilih (tidak peduli apakah bersamaan atau bukan prefiksnya), itu pasti akan menjadi argumen untuk diskusi selanjutnya: kita sekarang memiliki kata kunci yang berfungsi baik dalam awalan dan notasi postfix, tepatnya karena kadang-kadang lebih disukai daripada yang lain, jadi mungkin kita bisa mengizinkan keduanya jika masuk akal, dan meningkatkan fleksibilitas sintaks, sambil menyatukan aturan. Mungkin itu ide yang buruk, mungkin akan ditolak, tapi pasti akan menjadi diskusi di masa depan, jika notasi postix digunakan untuk await .

Saya pikir sangat kecil kemungkinan sintaks yang tidak menyertakan string karakter await akan diterima untuk sintaks ini.

: +1:


Pikiran acak yang saya miliki setelah melihat banyak contoh di sini ( seperti @mehcode 's ): Salah satu keluhan yang saya ingat tentang .await adalah terlalu sulit untuk dilihat †, tetapi mengingat hal-hal yang menunggu adalah biasanya salah, fakta bahwa sering kali .await? membantu menarik perhatian ekstra padanya.

† Jika Anda menggunakan sesuatu yang tidak menyoroti kata kunci


@ejmah

Saya menentang sintaks yang tidak terbaca seperti bahasa Inggris

Sesuatu seperti request.get().await dibaca serta sesuatu seperti body.lines().collect() . Dalam "sekumpulan metode iterator yang dirantai", menurut saya _prefix_ sebenarnya terbaca lebih buruk, karena Anda harus ingat bahwa mereka mengatakan "tunggu" di awal, dan tidak pernah tahu kapan Anda mendengar sesuatu jika itu akan menjadi apa yang Anda menunggu, seperti kalimat jalur taman .

Dan setelah bertahun-tahun menggunakannya dalam kode produksi, keyakinan saya adalah mengetahui di mana titik hasil Anda, secara sekilas, sangatlah penting. Saat Anda menelusuri garis indentasi jika fungsi Anda, Anda dapat memilih setiap co_await / yield kembali dalam fungsi 200-baris dalam hitungan detik, tanpa beban kognitif.

Ini menyiratkan bahwa tidak pernah ada di dalam ekspresi, yang merupakan batasan yang benar-benar tidak akan saya dukung, mengingat sifat berorientasi ekspresi Rust. Dan setidaknya dengan C # await , sangat masuk akal untuk memiliki CallSomething(argument, await whatever.Foo() .

Mengingat bahwa async _will_ muncul di tengah ekspresi, saya tidak mengerti mengapa lebih mudah untuk melihat di prefiks daripada di postfix.

Itu harus diberi tingkat penghormatan yang sama seperti 'jika, sementara, cocok, dan kembali. Bayangkan jika salah satu dari mereka adalah operator postfix - membaca kode Rust akan menjadi mimpi buruk.

return (dan continue dan break ) dan while terkenal sebagai _completely_ tidak berguna untuk rantai, karena mereka selalu mengembalikan ! dan () . Dan sementara untuk beberapa alasan Anda menghilangkan for , kami telah melihat kode ditulis dengan baik menggunakan .for_each() tanpa efek buruk, terutama di rayon .

Kita mungkin perlu berdamai dengan fakta bahwa async/await akan menjadi fitur bahasa utama. Ini akan muncul di semua jenis kode. Ini akan merambah ekosistem - di beberapa tempat akan menjadi biasa seperti ? . Orang harus mempelajarinya.

Akibatnya, kita mungkin ingin secara sadar fokus pada bagaimana pilihan sintaks akan terasa setelah menggunakannya untuk waktu yang lama daripada bagaimana rasanya pada awalnya.

Kita juga perlu memahami bahwa sejauh konstruksi aliran kontrol berjalan, await adalah jenis hewan yang berbeda. Konstruksi seperti return , break , continue , dan bahkan yield dapat dipahami secara intuitif dalam istilah jmp . Saat kita melihatnya, mata kita memantul ke layar karena aliran kendali yang kita pedulikan sedang bergerak ke tempat lain. Namun, sementara await mempengaruhi aliran kontrol mesin, itu tidak memindahkan aliran kontrol yang penting bagi mata kita dan pemahaman intuitif kita tentang kode.

Kami tidak tergoda untuk melakukan panggilan tanpa syarat ke return atau break karena itu tidak masuk akal. Untuk alasan serupa, kami tidak tergoda untuk mengubah aturan prioritas kami untuk mengakomodasi mereka. Operator ini memiliki prioritas rendah. Mereka mengambil semuanya ke kanan dan mengembalikannya ke suatu tempat, mengakhiri eksekusi dalam fungsi atau blok itu. Namun, operator await ingin dirantai. Itu adalah bagian integral dari sebuah ekspresi, bukan akhirnya.

Setelah mempertimbangkan diskusi dan contoh-contoh di utas ini, saya pergi dengan perasaan menggerogoti bahwa kita akan hidup untuk menyesali aturan prioritas yang mengejutkan.

Calon kuda penguntit tampaknya akan mendapatkan await!(expr) untuk saat ini dan berharap sesuatu yang lebih baik akan berhasil nanti. Sebelum membaca komentar @Centril , saya mungkin akan mendukung ini untuk kepentingan mengeluarkan fitur penting ini dengan hampir semua sintaks. Namun, argumennya meyakinkan saya bahwa ini akan berakhir begitu saja. Kami tahu bahwa metode call chaining penting di Rust. Itu mendorong penggunaan kata kunci ? , yang sangat populer dan sangat sukses. Menggunakan sintaks yang kita tahu akan mengecewakan kita memang hanya menambah hutang teknis.

Di awal utas ini, @withoutboats menunjukkan bahwa hanya empat opsi yang ada yang tampak layak. Dari semua itu, hanya sintaks expr await postfix yang mungkin membuat kita bahagia dalam jangka panjang. Sintaks ini tidak membuat kejutan prioritas yang aneh. Itu tidak memaksa kita untuk membuat versi awalan dari operator ? . Ia bekerja dengan baik dengan rangkaian metode dan tidak merusak aliran kontrol kiri-ke-kanan. Operator ? kami yang berhasil berfungsi sebagai preseden untuk operator postfix, dan await lebih seperti ? dalam praktiknya daripada return , break , atau yield . Meskipun operator non-simbol postfix mungkin baru di Rust, penggunaan async/await akan cukup luas untuk membuatnya cepat dikenal.

Meskipun semua opsi untuk sintaks postfix bisa diterapkan, expr await memiliki beberapa keuntungan. Sintaks ini menjelaskan bahwa await adalah kata kunci, yang membantu untuk menekankan aliran kendali ajaib. Dibandingkan dengan expr.await , expr.await() expr.await! , expr.await!() , dll., Ini untuk menghindari keharusan menjelaskan bahwa ini tampak seperti bidang / metode / makro, tetapi sebenarnya tidak ada dalam kasus khusus ini. Kita semua akan terbiasa dengan pemisah ruang di sini.

Mengeja await sebagai @ atau menggunakan simbol lain yang tidak menyebabkan masalah penguraian sangatlah menarik. Ini tentu saja merupakan operator yang cukup penting untuk menjaminnya. Tetapi jika, pada akhirnya, harus dieja await , itu tidak masalah. Asalkan dalam posisi postfix.

Seperti seseorang menyebutkan contoh _real_ ... Saya mempertahankan (menurut tokei) 23.858 baris basis kode karat yang sangat asinkron dan menggunakan futures 0.1 await (saya tahu sangat eksperimental). Let's go (redacted) spelunking (catat semuanya telah dijalankan melalui rustfmt):

// A
if !await!(db.is_trusted_identity(recipient.clone(), message.key.clone()))? {
    info!("recipient: {}", recipient);
}

// B
match await!(db.load(message.key))? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = await!(client
    .get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .send())?
.error_for_status()?;

// D
let mut res =
    await!(client.get(inbox_url).headers(inbox_headers).send())?.error_for_status()?;

let mut res: InboxResponse = await!(res.json())?;

// E
let mut res = await!(client
    .post(url)
    .multipart(form)
    .headers(headers.clone())
    .send())?
.error_for_status()?;

let res: Response = await!(res.json())?;

// F
#[async]
fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let (_, mut res) = await!(self.request(url, Method::GET, None, true))?;
    let user = await!(res.json::<UserResponse>())?
        .user
        .into();

    Ok(user)
}

Sekarang mari kita ubah ini menjadi varian awalan yang paling populer, diutamakan yang jelas dengan gula. Untuk alasan yang jelas ini belum dijalankan melalui rustfmt jadi mohon maaf jika ada cara yang lebih baik untuk menulisnya.

// A
if await? db.is_trusted_identity(recipient.clone(), message.key.clone()) {
    info!("recipient: {}", recipient);
}

// B
match await? db.load(message.key) {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = (await? client
    .get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .send())
.error_for_status()?;

// D
let mut res =
    (await? client.get(inbox_url).headers(inbox_headers).send()).error_for_status()?;

let mut res: InboxResponse = await? res.json();

// E
let mut res = (await? client
    .post(url)
    .multipart(form)
    .headers(headers.clone())
    .send())
.error_for_status()?;

let res: Response = await? res.json();

// F
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let (_, mut res) = await? self.request(url, Method::GET, None, true);
    let user = (await? res.json::<UserResponse>())
        .user
        .into();

    Ok(user)
}

Akhirnya, mari ubah ini menjadi varian postfix favorit saya, "field postfix".

// A
if db.is_trusted_identity(recipient.clone(), message.key.clone()).await? {
    info!("recipient: {}", recipient);
}

// B
match db.load(message.key).await? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = client.get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .send().await?
    .error_for_status()?;

// D
let mut res: InboxResponse = client.get(inbox_url)
    .headers(inbox_headers)
    .send().await?
    .error_for_status()?
    .json().await?;

// E
let mut res: Response = client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .send().await?
    .error_for_status()?
    .json().await?;

// F
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.request(url, Method::GET, None, true).await?
        .res.json::<UserResponse>().await?
        .user
        .into();

    Ok(user)
}

Setelah latihan ini, saya menemukan beberapa hal.

  • Saya sekarang menentang keras await? foo . Bunyinya bagus untuk ekspresi sederhana tetapi ? terasa hilang pada ekspresi kompleks. Jika kita harus membuat awalan, saya lebih suka memiliki prioritas yang "berguna".

  • Menggunakan notasi postfix membawa saya untuk menggabungkan pernyataan dan mengurangi ikatan let yang tidak perlu.

  • Menggunakan notasi _field_ postfix membuat saya lebih memilih .await? untuk muncul di baris hal yang menunggu daripada di barisnya sendiri dalam bahasa rustfmt.

Saya menghargai notasi postfix elipsis "..." di atas, baik karena ringkasnya maupun secara simbolis dalam bahasa Inggris yang mewakili jeda untuk mengantisipasi hal lain. (Seperti cara kerja perilaku asinkron!), Itu juga terhubung dengan baik.

let resultValue = doSomethingAndReturnResult()...?;
let resultValue = doSomethingAndReturnResult()...?.doSomethingOnResult()...?;
let value = doSomethingAndReturnValue()....doSomethingOnValue()...;
let arrayOfValues = vec![doSomethingA(),doSomethingB()]...?;
// Showing stacking
let value = doSomethingWithVeryLongFunctionName()...?
                 .doSomethingWithResult()...?;

Saya ragu opsi lain akan sesingkat dan bermakna secara visual.

SEBUAH

let mut res: Response = (await client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .send().await?
    .error_for_status()?
    .json())?;

B

let mut res: Response = await client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .send().await?
    .error_for_status()?
    .json());
let res = res.unwrap();

Haruskah itu dianggap sebagai bentuk yang baik untuk memiliki rantai panjang menunggu?

Mengapa tidak menggunakan kombinator biasa Future ?

Faktanya, beberapa ekspresi tidak diterjemahkan dengan baik ke dalam rantai menunggu jika Anda ingin memiliki perilaku cadangan saat gagal.

Secara pribadi saya pikir ini:

let value = await some_op()
                 .and_then(|v| v.another_op())
                 .and_then(|v2| v2.final_op())
                 .or_else(|| backup_op());

value.unwrap()

membaca jauh lebih alami dari ini:

let value = match await some_op() {
    Ok(v) => match await v.another_op() {
        Ok(v2) => await v2.final_op(),
        Err(_) => await backup_op(),
    },
    Err(_) => await backup_op(),
};

value.unwrap()

Bagaimanapun, kita masih memiliki kekuatan penuh dari masa depan tanpa biaya di tangan kita.

Pertimbangkan itu.

@EyeOfPython Saya ingin menjelaskan bahwa kami memiliki pilihan lain selain @ dalam future@await . Kita dapat menulis future~await , dimana ~ bekerja seperti tanda hubung semi, dan akan bekerja untuk semua operator postfix yang memungkinkan.

Tanda hubung - sudah digunakan sebagai operator minus dan operator negatif. Tidak bagus lagi. Tapi ~ digunakan untuk menunjukkan objek heap di Rust, dan sebaliknya itu hampir tidak digunakan dalam bahasa pemrograman apa pun. Ini bisa mengurangi kebingungan bagi orang-orang dari bahasa lain.

@earthengine Ide bagus, tapi mungkin gunakan saja future~ yang berarti menunggu masa depan, dimana ~ bekerja seperti kata kunci await . (seperti simbol ?

Masa Depan | Hasil Masa Depan | Hasil Masa Depan
- | - | -
masa depan ~ | masa depan ~? | masa depan? ~

Kontrak berjangka juga seperti:

let res: MyResponse = client.get("https://my_api").send()~?.json()~?;

Saya telah melihat bagaimana Go mengimplementasikan pemrograman asinkron dan menemukan sesuatu yang menarik di sana. Alternatif terdekat untuk futures di Go adalah saluran. Dan alih-alih await atau sintaks menjerit lainnya untuk menunggu nilai, saluran Go hanya menyediakan operator <- untuk tujuan itu. Bagi saya itu terlihat cukup bersih dan lugas. Sebelumnya saya sering melihat bagaimana orang memuji Go karena sintaksnya yang sederhana dan fasilitas asinkronnya yang bagus, jadi mempelajari sesuatu dari pengalamannya adalah ide yang bagus.

Sayangnya, kami tidak dapat memiliki sintaks yang persis sama karena ada lebih banyak kurung sudut di kode sumber Rust daripada di Go, sebagian besar karena obat generik. Ini membuat operator <- benar-benar halus dan tidak menyenangkan untuk digunakan. Kerugian lainnya adalah itu bisa dilihat sebagai kebalikan dari -> dalam tanda tangan fungsi dan tidak ada alasan untuk menganggapnya seperti itu. Dan kelemahan lainnya adalah bahwa <- sigil dimaksudkan untuk diimplementasikan sebagai placement new , sehingga orang dapat salah menafsirkannya.

Jadi, setelah beberapa percobaan dengan sintaks saya berhenti di <-- sigil:

let output = <-- future;

Dalam async konteks <-- cukup mudah, meskipun kurang dari <- . Tetapi sebaliknya ia memberikan keuntungan besar lebih dari <- serta lebih dari awalan await - itu berfungsi dengan baik dengan lekukan.

async fn log_service(&self) -> T {
   let service = self.myService.foo();
   <-- self.logger.log("beginning service call");
   let output = <-- service.exec();
   <-- self.logger.log("foo executed with result {}.", output));
   output
}

async fn try_log(message: String) -> Result<usize, Error> {
    let logger = <-- acquire_lock();
    let length = <-- logger.log_into(message)?;
    <-- logger.timestamp();
    Ok(length)
}

async fn await_chain() -> Result<usize, Error> {
    <-- (<-- partial_computation()).unwrap_or_else(or_recover);
}

Bagi saya operator ini terlihat lebih unik dan lebih mudah dikenali daripada await (yang lebih mirip kata kunci atau variabel lain dalam konteks kode). Sayangnya, di Github terlihat lebih tipis daripada di editor kode saya, tetapi saya pikir itu tidak fatal dan kita bisa menerimanya. Bahkan jika seseorang merasa tidak nyaman, penyorotan sintaks yang berbeda atau font yang lebih baik (terutama dengan ligatur) akan menyelesaikan semua masalah.

Alasan lain untuk menyukai sintaks ini adalah karena secara konseptual dapat diekspresikan sebagai "sesuatu yang belum ada di sini". Arah panah kanan ke kiri berlawanan dengan arah cara kita membaca teks, yang memungkinkan kita untuk menggambarkannya sebagai "hal yang datang dari masa depan". Bentuk panjang operator <-- juga menunjukkan bahwa "beberapa operasi yang tahan lama dimulai". Tanda kurung sudut dan dua tanda hubung yang diarahkan dari future bisa melambangkan "polling berkelanjutan". Dan kami masih bisa membacanya sebagai "menunggu" seperti sebelumnya.
Bukan semacam pencerahan, tapi mungkin menyenangkan.


Hal terpenting dalam proposal ini adalah bahwa perangkaian metode ergonomis juga dimungkinkan. Ide tentang operator awalan tertunda yang saya usulkan sebelumnya sangat cocok di sini. Dengan cara ini kita akan mendapatkan yang terbaik dari kedua dunia prefiks dan postfix await sintaks. Saya sangat berharap bahwa ada juga yang akan diperkenalkan beberapa tambahan berguna yang secara pribadi saya inginkan dalam banyak kesempatan sebelumnya: dereferensi tertunda dan sintaks negasi tertunda .

Melalui, saya tidak yakin apakah kata tertunda tepat di sini, mungkin kita harus menamainya berbeda.

client.get("https://my_api").<--send()?.<--json()?

let not_empty = some_vec.!is_empty();

let deref = value.*as_ref();

Prioritas operator terlihat cukup jelas: dari kiri ke kanan.

Saya berharap sintaks ini akan mengurangi kebutuhan dalam menulis fungsi is_not_* yang tujuannya hanya untuk meniadakan dan mengembalikan properti bool . Dan ekspresi boolean / dereferencing dalam beberapa kasus akan lebih bersih saat menggunakannya.


Akhirnya, saya telah menerapkannya pada contoh dunia nyata yang diposting oleh @mehcode dan saya suka bagaimana <-- membuat penekanan yang tepat pada fungsi async di dalam rantai panggilan metode. Sebaliknya, postfix await hanya terlihat seperti akses bidang biasa atau pemanggilan fungsi (tergantung pada sintaks) dan hampir tidak mungkin untuk membedakannya tanpa penyorotan atau pemformatan sintaks khusus.

// A
if db.<--is_trusted_identity(recipient.clone(), message.key.clone())? {
    info!("recipient: {}", recipient);
}

// B
match db.<--load(message.key)? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = client.get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .<--send()?
    .error_for_status()?;

// D
let mut res: InboxResponse = client.get(inbox_url)
    .headers(inbox_headers)
    .<--send()?
    .error_for_status()?
    .<--json()?;

// E
let mut res: Response = client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .<--send()?
    .error_for_status()?
    .<--json()?;

// F
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.<--request(url, Method::GET, None, true)?
        .res.<--json::<UserResponse>()?
        .user
        .into();

    Ok(user)
}

Lagipula: itulah sintaks yang ingin saya gunakan.

@gila_gila

Haruskah itu dianggap sebagai bentuk yang baik untuk memiliki rantai panjang menunggu? Mengapa tidak menggunakan kombinator Future biasa?

Saya tidak yakin apakah saya tidak salah paham tetapi itu tidak eksklusif, Anda masih dapat menggunakan kombinator juga

let value = some_op()
    .and_then(|v| v.another_op())
    .and_then(|v2| v2.final_op())
    .or_else(|| backup_op())
    .await;

Tetapi agar ini bekerja, klausa and_then perlu diketik pada FnOnce(T) -> impl Future<_> , tidak hanya pada FnOnce(T) -> U . Melakukan kombinator berantai di masa depan dan hasilnya hanya berfungsi dengan baik tanpa tanda kurung di postfix:

let result = load_local_file()
    .or_else(|_| request_from_server()) // Async combinator
    .await
    .and_then(|body| serde_json::from_str(&body)); // Sync combinator

Dalam posting ini saya akan fokus pada pertanyaan tentang prioritas operator dalam kasus pasca-perbaikan. Sejauh yang saya tahu, kami memiliki tiga alternatif yang layak yang setidaknya berfungsi sampai batas tertentu, masing-masing dicontohkan oleh satu ekspresi pasca-perbaikan yang memiliki prioritas ini dalam sintaks saat ini. Saya sangat yakin bahwa tidak ada proposal yang mengubah prioritas operator saat ini.

  • Panggilan metode ( future.await() )
  • Ekspresi bidang ( future.await )
  • Panggilan fungsi ( future(await) )

Perbedaan fungsionalitas agak kecil tetapi ada. Perhatikan bahwa saya akan menerima semua ini, ini sebagian besar adalah penyempurnaan. Untuk menunjukkan semuanya, kita membutuhkan beberapa tipe. Tolong jangan mengomentari pembuatan contoh, ini adalah versi paling terkompresi yang menunjukkan semua perbedaan sekaligus.

struct Foo<A, F, S> where A: Future<Output=F>, F: FnOnce(usize) -> S {
    member: A,
}

// What we want to do, in macro syntax:
let foo: Foo<_, _, _> = …;
(await!(foo.member))(42)
  • Metode panggilan: foo.member.await()(42)
    Mengikat paling kuat, jadi tidak ada parantesis sama sekali
  • Anggota: (foo.member.await)(42)
    Membutuhkan paranthesis di sekitar hasil yang ditunggu saat ini adalah callable, ini konsisten dengan callable sebagai anggota, jika tidak kebingungan dengan fungsi panggilan ke anggota. Ini juga menyarankan seperti seseorang dapat merusak dengan pola: let … { await: value } = foo.member; value(42) entah bagaimana?
  • Panggilan fungsi: (foo.member)(await)(42)
    Membutuhkan paranthesis untuk merusak (kami memindahkan anggota) karena berperilaku sebagai pemanggilan fungsi.

Semuanya terlihat sama ketika kita tidak merusak struktur input melalui perpindahan anggota, atau memanggil hasilnya sebagai callable karena ketiga kelas prioritas ini datang langsung setelah satu sama lain. Bagaimana kita ingin masa depan berperilaku?

Paralel terbaik untuk pemanggilan metode seharusnya hanyalah pemanggilan metode lain yang mengambil self .

Paralel terbaik untuk anggota adalah struct yang hanya memiliki anggota implisit await dan dengan demikian dihancurkan dengan pindah dari ini, dan langkah ini secara implisit menunggu masa depan. Yang ini terasa paling tidak obivous.

Paralel dengan fungsi panggilan adalah perilaku penutupan. Saya lebih suka solusi ini sebagai tambahan terbersih untuk korpus bahasa (seperti dalam sintaks terbaik paralel dengan kemungkinan jenis) tetapi beberapa poin positif untuk pemanggilan metode dan .await tidak pernah lebih lama dari yang lain.

@HeroicKatora Kita dapat menggunakan kasus khusus .await di libsyntax untuk memungkinkan foo.await(42) tetapi ini akan menjadi tidak konsisten / ad-hoc. Namun, (foo.await)(42) tampaknya bisa digunakan karena sementara penutupan keluaran berjangka ada, mereka mungkin tidak terlalu umum. Jadi, jika kita mengoptimalkan untuk kasus umum, tidak harus menambahkan () menjadi .await kemungkinan besar menang dengan saldo.

@Centril Saya setuju, konsistensi itu penting. Saat saya melihat beberapa perilaku yang serupa, saya ingin menyimpulkan perilaku lain melalui sintesis. Tetapi di sini .await tampak canggung, terutama dengan contoh di atas yang secara jelas menunjukkan bahwa itu paralel dengan penghancuran implisit (kecuali Anda dapat menemukan sintaks yang berbeda di mana efek ini terjadi?). Ketika saya melihat penghancuran, saya langsung bertanya-tanya apakah saya dapat menggunakan ini dengan let-binding dll. Ini, bagaimanapun, tidak akan mungkin karena kami akan merusak tipe asli yang tidak memiliki anggota seperti itu atau terutama ketika tipe hanya impl Future<Output=F> (Catatan kaki yang paling tidak relevan: membuat ini berfungsi akan membawa kita kembali ke prefiks alternatif await _ = menggantikan let _ = , lucunya¹).

Itu tidak melarang kita untuk menggunakan sintaks itu sendiri, saya pikir saya bisa berurusan dengan mempelajarinya dan jika ternyata yang terakhir saya akan menggunakannya dengan semangat, tetapi sepertinya itu kelemahan yang jelas.


¹ Ini bisa jadi konsisten sementara mengizinkan ? dengan mengizinkan ? belakang nama dalam pola

  • await value? = failing_future();

untuk mencocokkan Ok bagian dari Result . Hal ini tampaknya menarik untuk dieksplorasi dalam konteks lain juga tetapi di luar topik. Ini juga akan mengarah pada pencocokan prefiks dan sintaks sufiks untuk await pada waktu yang sama.

Itu tidak melarang kita untuk menggunakan sintaks itu sendiri, saya pikir saya bisa berurusan dengan mempelajarinya dan jika ternyata yang terakhir saya akan menggunakannya dengan semangat, tetapi sepertinya itu kelemahan yang jelas.

Saya pikir setiap solusi akan memiliki beberapa kelemahan dalam beberapa dimensi atau kasus ulang. konsistensi, ergonomi, kemampuan rantai, keterbacaan, ... Ini menjadikannya pertanyaan tentang derajat, pentingnya kasing, kesesuaian dengan kode dan API Rust yang khas, dll.

Dalam kasus pengguna menulis foo.await(42) ...

struct HasClosure<F: FnOnce(u8)> { closure: F, }
fn _foo() {
    let foo: HasClosure<_> = HasClosure { closure: |x| {} };

    foo.closure(42);
}

... kami sudah menyediakan diagnostik yang baik:

5 |     foo.closure(42);
  |         ^^^^^^^ field, not a method
  |
  = help: use `(foo.closure)(...)` if you meant to call the function stored in the
          `closure` field

Menyesuaikan ini agar sesuai dengan foo.await(42) tampaknya cukup bisa dicapai. Faktanya, sejauh yang saya bisa lihat, kita tahu bahwa pengguna bermaksud (foo.await)(42) ketika foo.await(42) ditulis sehingga ini bisa menjadi cargo fix ed dalam MachineApplicable cara. Memang, jika kita menstabilkan foo.await tetapi tidak mengizinkan foo.await(42) Saya yakin kita bahkan dapat mengubah prioritasnya nanti jika kita perlu karena foo.await(42) tidak akan legal pada awalnya.

Penumpukan lebih lanjut akan berfungsi (mis. Masa depan hasil penutupan - bukan berarti ini akan umum):
`` karat
struct HasClosure fn _foo () -> Hasil <(), ()> {
let foo: HasClosure <_ i = "27"> = HasClosure {penutupan: Ok (| x | {})};

foo.closure?(42);

Ok(())

}

misalnya masa depan hasil penutupan - bukan berarti ini akan umum

Akhiran tambahan ? operator membuat ini tidak ambigu tanpa modifikasi sintaks – dalam contoh pasca-perbaikan manapun. Tidak perlu mengutak-atik . Masalahnya hanya pada .member secara eksplisit menjadi bidang dan kebutuhan untuk memindahkan-destructuring untuk diterapkan terlebih dahulu. Dan saya benar-benar tidak ingin mengatakan bahwa ini akan sulit untuk ditulis. Saya sebagian besar ingin mengatakan bahwa ini tampak tidak konsisten dengan penggunaan .member yang misalnya dapat diubah menjadi pencocokan. Posting asli menimbang positif dan negatif dalam hal itu.

Sunting: Menyesuaikan agar sesuai future.await(42) memiliki, kemungkinan besar tidak disengaja, risiko ekstra membuat hal ini a) tidak sesuai dengan penutupan di mana hal ini tidak terjadi karena metode dengan nama yang sama dengan anggota yang diizinkan; b) menghambat perkembangan masa depan di mana kami ingin memberikan argumen untuk await . Tapi, seperti yang Anda sebutkan sebelumnya, mengutak-atik Future mengembalikan penutupan seharusnya bukan masalah yang paling mendesak.

@novacrazy Mengapa tidak menggunakan kombinator masa depan biasa saja?

Saya tidak yakin berapa banyak pengalaman yang Anda miliki dengan Futures 0.3, tetapi ekspektasi umumnya adalah bahwa kombinator tidak akan banyak digunakan, dan penggunaan primer / idiomatik akan menjadi async / menunggu.

Async / await memiliki beberapa keunggulan dibandingkan kombinator, misalnya mendukung peminjaman di seluruh titik hasil.

Kombinator sudah ada jauh sebelum async / await, tetapi async / await telah ditemukan, dan untuk alasan yang bagus!

Async / await akan tetap ada, yang artinya harus ergonomis (termasuk dengan rantai metode).

Tentu saja orang bebas menggunakan kombinator jika mereka mau, tetapi tidak diperlukan untuk mendapatkan ergonomi yang baik.

Seperti yang dikatakan @cramertj , mari kita coba agar diskusi tetap fokus pada async / await, bukan alternatif selain async / await.

Faktanya, beberapa ekspresi tidak diterjemahkan dengan baik ke dalam rantai menunggu jika Anda ingin memiliki perilaku cadangan saat gagal.

Contoh Anda dapat disederhanakan secara signifikan:

let value = try {
    let v = await some_op()?;
    let v2 = await v.another_op()?;
    await v2.final_op()?
};

match value {
    Ok(value) => Ok(value),
    Err(_) => await backup_op(),
}.unwrap()

Ini memperjelas bagian mana yang menangani kesalahan, dan bagian mana yang berada di jalur bahagia normal.

Ini adalah salah satu hal hebat tentang async / await: ini berfungsi dengan baik dengan bagian lain bahasa, termasuk loop, cabang, match , ? , try , dll.

Faktanya, selain await , ini adalah kode yang sama yang akan Anda tulis jika Anda tidak menggunakan Futures.

Cara lain untuk menulisnya, jika Anda lebih suka menggunakan kombinator or_else :

let value = await async {
    try {
        let v = await some_op()?;
        let v2 = await v.another_op()?;
        await v2.final_op()?
    }
}.or_else(|_| backup_op());

value.unwrap()

Dan yang terbaik dari semuanya adalah memindahkan kode normal ke fungsi terpisah, membuat kode penanganan kesalahan lebih jelas:

async fn doit() -> Result<Foo, Bar> {
    let v = await some_op()?;
    let v2 = await v.another_op()?;
    await v2.final_op()
}
let value = await doit().or_else(|_| backup_op());

value.unwrap()

(Ini adalah balasan untuk komentar @joshtriplett ).

Supaya jelas, Anda tidak perlu tanda kurung, saya sebutkan karena beberapa orang mengatakan terlalu sulit untuk membaca tanpa tanda kurung. Jadi, tanda kurung adalah pilihan gaya

Semua sintaks mendapat manfaat dari tanda kurung dalam beberapa situasi, tidak ada sintaks yang sempurna, ini pertanyaan tentang situasi mana yang ingin kami optimalkan.

Selain itu, setelah membaca ulang komentar Anda, mungkin Anda mengira saya menganjurkan penggunaan awalan await ? Saya tidak, contoh saya menggunakan postfix await . Secara keseluruhan saya menyukai postfix await , meskipun saya juga menyukai beberapa sintaks lainnya.

Saya mulai merasa nyaman dengan fut.await , saya pikir reaksi awal orang-orang adalah "Tunggu, begitulah cara Anda menunggu? Aneh." tapi nanti mereka akan menyukainya untuk kenyamanan. Tentu saja, hal yang sama berlaku untuk @await , yang lebih menonjol dari .await .

Dengan sintaks tersebut, kita dapat mengabaikan beberapa allow pada contoh:

`.await``@ await`
let value = try {
    some_op().await?
        .another_op().await?
        .final_op().await?
};

match value {
    Ok(value) => Ok(value),
    Err(_) => backup_op().await,
}.unwrap()
let value = try {
    some_op()@await?
        .another_op()@await?
        .final_op()@await?
};

match value {
    Ok(value) => Ok(value),
    Err(_) => backup_op()<strong i="21">@await</strong>,
}.unwrap()

Ini juga memperjelas apa yang dibuka dengan ? , untuk await some_op()? , tidak jelas apakah some_op() akan dibuka atau hasil yang ditunggu.

@Tokopedia

Saya tidak mencoba mengalihkan fokus dari topik di sini, saya mencoba menunjukkan bahwa itu tidak ada dalam gelembung. Kita harus mempertimbangkan bagaimana segala sesuatunya bekerja sama .

Bahkan jika sintaks yang ideal dipilih, saya masih ingin menggunakan kontrak berjangka dan kombinator khusus dalam beberapa situasi. Gagasan bahwa itu bisa lembut tidak berlaku lagi membuat saya mempertanyakan seluruh arah Rust.

Contoh yang Anda berikan masih terlihat buruk dibandingkan dengan kombinator, dan dengan overhead generator mungkin akan sedikit lebih lambat dan menghasilkan lebih banyak kode mesin.

Sejauh menunggu pergi, semua awalan / postfix sigil / kata kunci bikeshedding ini luar biasa, tapi mungkin kita harus pragmatis dan pergi dengan opsi paling sederhana yang paling akrab bagi pengguna yang datang ke Rust. Yaitu: kata kunci awalan

Tahun ini akan berlalu lebih cepat dari yang kita pikirkan. Bahkan Januari sudah banyak yang selesai. Jika ternyata pengguna tidak senang dengan kata kunci awalan, itu bisa diubah pada edisi 2019/2020. Kita bahkan bisa membuat lelucon "melihat ke belakang adalah tahun 2020".

@gila_gila

Konsensus umum yang saya lihat adalah bahwa _earliest_ yang kami inginkan edisi ketiga adalah 2022. Kami jelas tidak ingin merencanakan edisi lain; edisi 2018 sangat bagus tetapi bukannya tanpa biaya. (Dan salah satu poin dari edisi 2018 adalah membuat async / menunggu menjadi mungkin, bayangkan mengambilnya kembali dan berkata "tidak, Anda perlu meningkatkan ke edisi 2020 sekarang!")

Bagaimanapun, saya tidak berpikir bahwa kata kunci awalan -> transisi kata kunci postfix dimungkinkan dalam sebuah edisi, bahkan jika itu diinginkan. Aturan seputar edisi adalah bahwa perlu ada cara untuk menulis kode idiomatik di edisi X sedemikian rupa sehingga terkompilasi tanpa peringatan dan berfungsi sama di edisi X + 1.

Itu alasan yang sama bahwa kami lebih suka tidak menstabilkan dengan makro kata kunci jika kami dapat mendorong konsensus pada solusi yang berbeda; menstabilkan solusi yang kita tahu tidak diinginkan dengan sengaja itu sendiri bermasalah.

Saya rasa kami telah menunjukkan bahwa solusi postfix lebih optimal, bahkan untuk ekspresi dengan hanya satu titik tunggu. Tetapi saya ragu bahwa solusi postfix yang diusulkan jelas lebih baik daripada yang lain.

Hanya dua sen saya (saya bukan siapa-siapa, tetapi saya mengikuti diskusi untuk waktu yang cukup lama). Solusi favorit saya adalah versi @await postfix. Mungkin Anda dapat mempertimbangkan sebuah postfix !await , seperti sintaks makro postfix baru?

Contoh:

let mut res: InboxResponse = client.get(inbox_url)
    .headers(inbox_headers)
    .send()!await?
    .error_for_status()?
    .json()!await?;

Setelah beberapa iterasi bahasa, bisa mengimplementasikan makro postfix kita sendiri akan sangat mengagumkan.

... semua proposal sygils baru

Rust sudah sintaks / sygil berat, kami memiliki await kata kunci yang dipesan, stuff@await (atau sygil lainnya) terlihat aneh / jelek (subjektif, saya tahu), dan hanya sintaks ad-hoc yang tidak terintegrasi dengan apa pun dalam bahasa yang merupakan bendera merah besar.

Saya telah melihat bagaimana Go mengimplementasikan
... <- ... proposal

@ I60R : Go memiliki sintaks yang buruk yang penuh dengan solusi ad-hoc, dan sangat penting, sangat berbeda dengan Rust. Proposal ini sekali lagi sygil / sintaks berat dan benar-benar ad-hoc hanya untuk fitur khusus ini.

@ I60R : Go memiliki sintaks yang buruk

Mari kita menahan diri untuk tidak mencela bahasa lain di sini. "X memiliki sintaks yang buruk" tidak mengarah pada pencerahan dan konsensus.

Sebagai pengguna Python / JavaScript / Rust dan mahasiswa ilmu komputer, saya pribadi lebih memilih awalan await + f.await() untuk keduanya dalam bahasa.

  1. Baik Python maupun JavaScript memiliki awalan await . Saya berharap melihat await muncul di awal. Jika saya harus membaca jauh ke baris untuk menyadari ini adalah kode asynchronous, saya merasa sangat tidak nyaman. Dengan kapabilitas Rust's WASM, ini mungkin menarik banyak pengembang JS. Saya percaya keakraban dan kenyamanan itu sangat penting, mengingat Rust sudah memiliki banyak konsep baru lainnya.

  2. Postfix await tampaknya nyaman dalam pengaturan rangkaian. Namun, saya tidak menyukai solusi seperti .await , @await , f await karena mereka terlihat seperti solusi ad-hoc untuk sintaks await sementara masuk akal untuk berpikir .await() sebagai memanggil metode pada future .

Rust sudah keluar dari javascript dan await tidak seperti fungsi pemanggil (yaitu fungsionalitas tidak dapat ditiru melalui fungsi) menggunakan fungsi untuk menunjukkan await membuatnya membingungkan bagi timer pertama yang diperkenalkan ke async-await. Karenanya saya pikir sintaksnya harus berbeda.

Saya telah meyakinkan diri saya sendiri bahwa .await() mungkin secara signifikan lebih diinginkan daripada .await , meskipun sisa posting ini sedikit melindungi posisi itu.

Alasannya adalah karena await!(fut) harus _mengonsumsi fut dengan nilai_. Membuatnya tampak seperti akses bidang adalah _bad_, karena itu tidak memiliki konotasi perpindahan seperti yang dilakukan kata kunci awalan, atau potensi perpindahan seperti makro atau pemanggilan metode.

Menariknya, sintaks metode kata kunci membuatnya hampir terlihat seperti desain menunggu implisit . Sayangnya, "Asinkron Eksplisit, Tunggu Tersirat" tidak dimungkinkan untuk Rust (jadi _please_ jangan lakukan litigasi ulang di utas ini) karena kami ingin async fn() -> T digunakan secara identik dengan fn() -> Future<T> , daripada mengaktifkan perilaku "menunggu implisit".

Fakta bahwa sintaks .await() _looks_ seperti sistem menunggu implisit (seperti yang digunakan Kotlin) bisa menjadi pencela, dan hampir akan memberikan kesan "Asinkron Tersirat, Penantian Tersirat" karena keajaiban di sekitar tidak- benar-benar-metode-panggil sintaks .await() . Apakah Anda dapat menggunakannya sebagai await(fut) dengan UFCS? Apakah UFCS akan menjadi Future::await(fut) ? Setiap sintaksis yang terlihat seperti dimensi lain dari bahasa menimbulkan masalah kecuali jika sintaks itu dapat disatukan dengan itu setidaknya secara sintaksis, bahkan jika tidak secara fungsional.

Saya tetap skeptis jika manfaat dari solusi postfix _individual_ lebih besar daripada kekurangan solusi yang sama, meskipun konsep solusi postfix lebih diinginkan daripada awalan pada umumnya.

Saya agak terkejut bahwa utas ini penuh dengan saran yang tampaknya dibuat karena mungkin - dan bukan karena tampaknya menghasilkan manfaat yang signifikan daripada saran awal.
Bisakah kita berhenti berbicara tentang $ , # , @ , ! , ~ dll, tanpa mengemukakan argumen yang signifikan apa yang salah dengan await , yang sangat dipahami dan telah terbukti dalam berbagai bahasa pemrograman lainnya?

Saya pikir pos dari https://github.com/rust-lang/rust/issues/57640#issuecomment -455361619 sudah mencantumkan semua opsi yang bagus.

Dari itu:

  • Pembatas pembatas wajib tampaknya baik-baik saja, setidaknya sudah jelas apa yang diutamakan dan kita dapat membaca kode lain tanpa kejelasan lebih lanjut. Dan mengetik dua tanda kurung bukanlah hal yang buruk. Mungkin satu-satunya downside adalah bahwa itu terlihat seperti pemanggilan fungsi, meskipun itu beberapa operasi aliran kontrol yang berbeda.
  • Prioritas yang berguna mungkin merupakan opsi yang lebih disukai. Tampaknya itulah jalan yang dilalui sebagian besar bahasa lain, jadi sudah dikenal dan terbukti.
  • Saya pribadi berpikir kata kunci postfix dengan spasi terlihat aneh dengan banyak menunggu dalam satu pernyataan:
    client.get("url").send() await?.json()? . Ruang putih di antaranya terlihat tidak pada tempatnya. Dengan tanda kurung, ini akan menjadi sedikit lebih masuk akal bagi saya: (client.get("url").send() await)?.json()?
    Tapi saya menemukan aliran kontrol masih lebih sulit untuk diikuti dibandingkan dengan varian awalan.
  • Saya tidak suka kolom postfix. await melakukan operasi yang sangat kompleks - Rust tidak memiliki properti yang dapat dihitung dan akses bidang sebaliknya adalah operasi yang sangat sederhana. Sehingga sepertinya memberikan kesan yang salah tentang kompleksitas operasi ini.
  • Metode postfix mungkin baik-baik saja. Mungkin mendorong beberapa orang untuk menulis pernyataan yang sangat panjang dengan banyak menunggu di dalamnya, yang mungkin lebih menyembunyikan poin hasil. Itu juga sekali lagi membuat hal-hal terlihat seperti pemanggilan metode, meskipun itu adalah sesuatu yang berbeda.

Untuk alasan ini saya lebih suka "prioritas yang berguna" diikuti dengan "pembatas wajib"

Go memiliki sintaks yang buruk yang penuh dengan solusi ad-hoc, dan sangat penting, sangat berbeda dengan Rust. Proposal ini sekali lagi sygil / sintaks berat dan benar-benar ad-hoc hanya untuk fitur khusus ini.

@dpc , jika Anda membaca <-- proposal secara lengkap, Anda akan melihat bahwa sintaks ini hanya terinspirasi oleh Go, namun sangat berbeda dan dapat digunakan baik dalam konteks imperatif dan rangkaian fungsi. Saya juga gagal untuk melihat bagaimana sintaks await bukanlah solusi ad-hoc, bagi saya itu jauh lebih spesifik dan jauh lebih kikuk daripada <-- . Ini mirip dengan memiliki deref reference / reference.deref / dll daripada *reference , atau memiliki try result / result.try / dll dan bukan result? . Saya juga tidak melihat keuntungan menggunakan await kata kunci selain keakraban dengan JS / Python / etc yang bagaimanapun seharusnya kurang signifikan daripada memiliki sintaks yang konsisten dan dapat disusun. Dan saya tidak melihat kerugian memiliki <-- sigil selain itu bukan await yang tidak sesederhana bahasa Inggris biasa dan pengguna harus memahami apa yang dilakukannya terlebih dahulu.

Sunting: ini juga bisa menjadi jawaban yang bagus untuk posting @ Matthias247 , karena ini memberikan beberapa argumen terhadap await dan mengusulkan kemungkinan alternatif yang tidak terpengaruh dengan masalah yang sama


Sangat menarik bagi saya, membaca kritik terhadap sintaks <-- , bebas dari argumen yang menarik bagi alasan historis dan prasangka.

Mari kita sebutkan spesifik sebenarnya di sekitar prioritas:

Bagan prioritas seperti saat ini :

Operator / Ekspresi | Asosiatif
- | -
Jalur |
Metode panggilan |
Ekspresi lapangan | kiri ke kanan
Panggilan fungsi, pengindeksan array |
? |
Unary - * ! & &mut |
as | kiri ke kanan
* / % | kiri ke kanan
+ - | kiri ke kanan
<< >> | kiri ke kanan
& | kiri ke kanan
^ | kiri ke kanan
\| | kiri ke kanan
== != < > <= >= | Wajibkan tanda kurung
&& | kiri ke kanan
\|\| | kiri ke kanan
.. ..= | Wajibkan tanda kurung
= += -= *= /= %= &= \|= ^= <<= >>= | kanan ke kiri
return break penutupan |

Prioritas yang berguna menempatkan await sebelum ? sehingga mengikat lebih erat dari ? . A ? dalam rantai sehingga mengikat `menunggu untuk semua yang sebelumnya.

let res = await client
    .get("url")
    .send()?
    .json();

Ya, dengan prioritas yang berguna, itu "berhasil". Apakah Anda tahu sekilas tentang apa itu? Apakah itu gaya yang buruk (mungkin)? Jika ya, dapatkah Rustfmt memperbaikinya secara otomatis?

Prioritas yang jelas menempatkan await _somewhere_ di bawah ? . Saya tidak yakin di mana tepatnya, meskipun hal-hal spesifik itu mungkin tidak terlalu penting.

let res = await? (client
    .get("url")
    .send())
    .json();

Apakah Anda tahu sekilas tentang apa itu? Bisakah rustfmt membuatnya menjadi gaya yang berguna yang tidak terlalu boros ruang vertikal dan horizontal secara otomatis?


Dimana kata kunci postfix termasuk disini? Mungkin Metode Kata Kunci dengan panggilan Metode dan Bidang Kata Kunci dengan ekspresi Bidang, tapi saya tidak yakin bagaimana yang lain harus mengikat. Opsi apa yang mengarah ke konfigurasi yang paling mungkin di mana await menerima "argumen" yang mengejutkan?

Untuk perbandingan ini, saya menduga "pembatas wajib" (yang saya sebut Fungsi Kata Kunci dalam ringkasan ) menang dengan mudah, karena akan setara dengan pemanggilan fungsi normal.

@ CAD97 Untuk lebih jelasnya, ingatlah bahwa .json() juga merupakan masa depan (setidaknya dalam reqwests ).

let res = await await client
    .get("url")
    .send()?
    .json()?;
let res = await? await? (client
    .get("url")
    .send())
    .json();

Semakin saya bermain dengan mengubah ekspresi karat yang kompleks (bahkan yang hanya perlu 1 menunggu, bagaimanapun, perhatikan bahwa dalam 20.000+ basis kode masa depan saya, hampir setiap ekspresi asinkron tunggal menunggu, langsung diikuti oleh menunggu lainnya), semakin saya tidak suka awalan untuk Rust.

Ini _all_ karena operator ? . Tidak ada bahasa lain yang memiliki operator aliran kontrol postfix _ dan_ await yang pada dasarnya selalu dipasangkan dalam kode sebenarnya.


Preferensi saya masih bidang postfix . Sebagai operator kontrol postfix, saya merasa perlu pengelompokan visual yang ketat bahwa future.await menyediakan lebih dari future await . Dan membandingkan dengan .await()? , saya lebih suka tampilan .await? _weird_ sehingga akan _noticed_ dan pengguna tidak akan menganggapnya sebagai fungsi sederhana (dan karenanya tidak akan bertanya mengapa UFCS tidak berfungsi) .


Sebagai satu lagi poin data yang mendukung postfix, ketika ini distabilkan, rustfix beralih dari await!(...) ke apa pun yang kami putuskan akan sangat dihargai. Saya tidak melihat bagaimana apapun kecuali sintaks postfix dapat diterjemahkan dengan jelas tanpa membungkus barang dalam ( ... ) tidak perlu.

Saya pikir pertama-tama kita harus menjawab pertanyaan "apakah kita ingin mendorong penggunaan await dalam konteks rangkaian?". Saya yakin jawaban umum adalah "ya", jadi ini menjadi argumen yang kuat untuk varian postfix. Meskipun await!(..) akan menjadi yang termudah untuk ditambahkan, saya yakin kita tidak harus mengulangi cerita try!(..) . Juga saya pribadi tidak setuju dengan argumen bahwa "perangkaian menyembunyikan operasi yang berpotensi mahal", kita sudah memiliki banyak metode perangkaian yang bisa _very_ berat, jadi perangkaian tidak memerlukan kemalasan.

Meskipun awalan await kata kunci akan menjadi yang paling familiar bagi pengguna yang berasal dari bahasa lain, menurut saya kita tidak harus membuat keputusan berdasarkan itu dan sebaliknya kita harus berkonsentrasi pada jangka panjang, yaitu kegunaan, kemudahan dan keterbacaan . @withoutboats berbicara tentang "keakraban anggaran", tetapi saya sangat yakin kita tidak boleh memperkenalkan solusi sub-optimal hanya demi keakraban.

Sekarang kita mungkin tidak ingin dua cara melakukan hal yang sama, jadi kita sebaiknya tidak memperkenalkan varian postfix dan prefix. Jadi katakanlah kita telah mempersempit pilihan kita ke varian postfix.

Pertama mari kita mulai dengan fut await , saya sangat tidak menyukai varian ini, karena ini akan sangat mengacaukan cara manusia mengurai kode dan itu akan menjadi sumber kebingungan yang konstan saat membaca kode. (Jangan lupa bahwa kode sebagian besar untuk dibaca)

Berikutnya fut.await , fut.await() dan fut.await!() . Saya rasa varian yang paling konsisten dan tidak terlalu membingungkan adalah makro postfix. Saya rasa tidak ada gunanya memperkenalkan entitas "fungsi kata kunci" atau "metode kata kunci" baru hanya untuk menyimpan beberapa karakter.

Varian berbasis sigil terakhir: fut@await dan fut@ . Saya tidak suka varian fut@await , jika kita memperkenalkan sigil mengapa repot-repot dengan bagian await ? Apakah kami memiliki rencana untuk ekstensi masa depan fut@something ? Jika tidak, itu akan terasa mubazir. Jadi saya suka varian fut@ , ini memecahkan masalah prioritas, kode menjadi mudah dimengerti, ditulis dan dibaca. Masalah visibilitas dapat diselesaikan dengan penyorotan kode. Tidak sulit untuk menjelaskan fitur ini sebagai "@ untuk menunggu". Tentu saja kelemahan terbesarnya adalah kami akan membayar fitur ini dari "anggaran sigil" yang sangat terbatas, tetapi mengingat pentingnya fitur tersebut dan seberapa sering fitur ini akan digunakan dalam basis kode asinkron, saya yakin fitur ini akan bermanfaat dalam jangka panjang . Dan tentu saja kita dapat menggambar paralel tertentu dengan ? . (Meskipun kita harus siap untuk lelucon Perl dari kritikus Rust)

Kesimpulannya: menurut saya jika kita siap untuk membebani "sigil budget" kita harus menggunakan fut@ , dan jika tidak dengan fut.await!() .

Ketika berbicara tentang keakraban, saya tidak berpikir bahwa kita harus terlalu peduli tentang keakraban dengan JS / Python / C #, karena Rust berada di ceruk yang berbeda dan sudah terlihat berbeda dalam banyak hal. Menyediakan sintaks yang mirip dengan bahasa-bahasa ini bersifat jangka pendek dan tujuan imbalan rendah. Tidak ada yang akan memilih Rust hanya untuk menggunakan kata kunci yang sudah dikenal ketika di bawah tenda berfungsi sangat berbeda.

Tetapi keakraban dengan Go penting, karena Rust berada di ceruk yang sama dan bahkan secara filosofi, Rust lebih dekat dengan Go daripada bahasa lain. Dan terlepas dari semua prasangka kebencian, salah satu poin terkuat dari keduanya adalah bahwa mereka tidak menyalin fitur secara membabi buta, tetapi menerapkan solusi yang benar-benar memiliki alasan untuk itu.

IMO, dalam pengertian ini sintaks <-- paling kuat di sini

Dengan semua itu, ingatlah bahwa ekspresi Rust dapat menghasilkan beberapa metode yang dirantai. Kebanyakan bahasa cenderung tidak melakukan itu.

Saya ingin mengingatkan pengalaman tim pengembang C #:

Pertimbangan utama terhadap sintaks C # adalah prioritas operator menunggu foo?

Ini adalah sesuatu yang saya rasa dapat saya komentari. Kami banyak memikirkan tentang prioritas dengan 'menunggu' dan kami mencoba banyak bentuk sebelum menetapkan bentuk yang kami inginkan. Salah satu hal inti yang kami temukan adalah bagi kami, dan pelanggan (internal dan eksternal) yang ingin menggunakan fitur ini, jarang sekali orang benar-benar ingin 'menghubungkan' apa pun melewati panggilan asinkron mereka.

Kecenderungan orang untuk ingin 'melanjutkan' dengan 'menunggu' di dalam expr jarang terjadi. Kami kadang-kadang melihat hal-hal seperti (await expr] .M (), tetapi hal itu tampaknya kurang umum dan kurang diinginkan daripada jumlah orang yang melakukan await expr.M ().

dan

Ini juga mengapa kami tidak menggunakan bentuk 'implisit' untuk 'menunggu'. Dalam praktiknya, ini adalah sesuatu yang orang ingin pikirkan dengan sangat jelas, dan yang mereka inginkan di bagian depan dan tengah dalam kode mereka sehingga mereka dapat memperhatikannya. Yang cukup menarik, bahkan bertahun-tahun kemudian, kecenderungan ini tetap ada. yaitu kadang-kadang kita menyesali beberapa tahun kemudian bahwa ada sesuatu yang terlalu bertele-tele. Beberapa fitur bagus pada awalnya, tetapi begitu orang merasa nyaman dengannya, lebih cocok dengan sesuatu yang lebih singkat. Itu tidak terjadi dengan 'menunggu'. Orang-orang tampaknya masih sangat menyukai sifat berat kata kunci tersebut dan prioritas yang kami pilih.

Merupakan poin yang baik melawan sigil daripada kata (kunci) khusus.

https://github.com/rust-lang/rust/issues/50547#issuecomment -388939886

Anda harus benar-benar mendengarkan orang-orang dengan jutaan pengguna.

Jadi Anda tidak ingin merantai apa pun, Anda hanya ingin memiliki beberapa await , dan pengalaman saya sama. Menulis kode async/await selama lebih dari 6 tahun, dan saya tidak pernah menginginkan fitur seperti itu. Sintaks Postfix terlihat sangat asing dan dianggap dapat menyelesaikan situasi yang mungkin tidak akan pernah terjadi. Async panggilan benar-benar hal yang berani sehingga beberapa menunggu di satu baris terlalu berat.

Kecenderungan orang untuk ingin 'melanjutkan' dengan 'menunggu' di dalam expr jarang terjadi. Kami kadang-kadang melihat hal-hal seperti (await expr] .M (), tetapi hal itu tampaknya kurang umum dan kurang diinginkan daripada jumlah orang yang melakukan await expr.M ().

Itu sepertinya analisis a-posteriori. Mungkin salah satu alasan mengapa mereka tidak melanjutkan adalah karena sangat canggung untuk melakukannya dalam sintaks prefiks (Sebanding dengan tidak ingin try! beberapa kali dalam sebuah pernyataan karena itu tetap dapat dibaca melalui operator ? ). Hal di atas kebanyakan mempertimbangkan (sejauh yang saya tahu) yang diutamakan, bukan posisi. Dan saya ingin mengingatkan Anda bahwa C # bukan Rust, dan anggota trait dapat sedikit mengubah keinginan untuk memanggil metode pada hasil.

@ I60 ,

  1. Saya pikir keakraban itu penting. Rust adalah bahasa yang relatif baru dan orang-orang akan berpindah dari bahasa lain dan jika Rust akan terlihat familiar maka akan lebih mudah bagi mereka untuk membuat keputusan untuk memilih Rust.
  2. Saya tidak terlalu menyukai metode perangkaian - jauh lebih sulit untuk men-debug rantai panjang dan saya menganggap rantai hanya mempersulit pembacaan kode dan mungkin hanya diizinkan sebagai opsi tambahan (seperti makro .await!() ). Bentuk awalan akan memaksa pengembang untuk mengekstrak kode ke dalam metode daripada merangkai, seperti:
let resp = await client.get("http://api")?;
let body: MyResponse = await resp.into_json()?;

menjadi seperti ini:

let body: MyResponse = await client.get_json("http://api")?;

Itu sepertinya analisis a-posteriori. Mungkin salah satu alasan mengapa mereka tidak melanjutkan adalah karena sangat canggung untuk melakukannya dalam sintaks prefiks. Di atas hanya mempertimbangkan prioritas, bukan posisi. Dan saya ingin mengingatkan Anda bahwa C # bukan Rust, dan anggota trait dapat sedikit mengubah keinginan untuk memanggil metode pada hasil.

Tidak, ini tentang eksperimen tim C # internal yang memiliki bentuk awalan / postfix / implisit. Dan saya berbicara tentang pengalaman saya yang bukan hanya sebuah kebiasaan dimana saya tidak dapat melihat kelebihan formulir postfix.

@mehcode Contoh Anda tidak memotivasi saya. reqwest sadar memutuskan untuk membuat siklus permintaan / respons awal dan penanganan selanjutnya dari respons (aliran tubuh) proses bersamaan yang terpisah, oleh karena itu mereka harus ditunggu dua kali, seperti acara @andreytkachenko .

reqwest benar-benar dapat mengekspos API berikut:

let res = await client
    .get("url")
    .json()
    .send();

atau

let res = await client
    .get("url")
    .send()
    .json();

(Yang terakhir adalah gula sederhana di atas and_then ).

Saya merasa bermasalah bahwa banyak contoh postfix di sini menggunakan rantai ini sebagai contoh, karena keputusan api terbaik reqwest s dan hyper s adalah memisahkan hal-hal ini.

Sejujurnya saya percaya bahwa sebagian besar contoh postfix menunggu di sini harus ditulis ulang menggunakan kombinator (dan gula jika perlu) atau disimpan operasi serupa dan ditunggu beberapa kali.

@dyteachman

Bentuk awalan akan memaksa pengembang untuk mengekstrak kode ke dalam metode daripada merangkai, seperti:

Apakah itu hal yang baik atau buruk? Untuk kasus di mana ada N metode, masing-masing dapat menghasilkan tindak lanjut M, pengembang seharusnya menyediakan N * M metode? Saya pribadi, menyukai solusi yang dapat disusun, meski sedikit lebih lama.

menjadi seperti ini:

let body: MyResponse = await client.get_json("http://api")?;
let body: MyResponse = client.get("http://api").await?.into_json().await?;

@Tokopedia

Saya tidak berpikir di C # Anda akan mendapatkan kode rantai / fungsional sebanyak yang Anda dapatkan di Rust. Atau apakah saya salah? Itu membuat pengalaman C # devs / users menarik, tetapi tidak selalu berlaku untuk Rust. Saya berharap kami bisa kontras dengan Ocaml atau Haskell.

Saya pikir itulah akar penyebab ketidaksepakatan ini adalah bahwa beberapa dari kita menikmati gaya imperatif, dan beberapa yang fungsional. Dan itu dia. Rust mendukung keduanya, dan kedua sisi perdebatan ingin asinkron cocok dengan cara mereka biasanya menulis kode.

@pc

Saya tidak berpikir di C # Anda akan mendapatkan kode rantai / fungsional sebanyak yang Anda dapatkan di Rust. Atau apakah saya salah? Itu membuat pengalaman C # devs / users menarik, tetapi tidak selalu berlaku untuk Rust. Saya berharap kami bisa kontras dengan Ocaml atau Haskell.

LINQ dan gaya fungsional cukup populer di C #.

@pc ,
jika Anda tentang keterbacaan - Saya menganggap itu sebagai kode eksplisit - sebagai lebih baik, dan pengalaman saya mengatakan bahwa jika nama metode / fungsi menggambarkan diri dengan baik, tidak perlu melakukan tindak lanjut.

jika Anda tentang overhead - kompiler Rust agak pintar untuk menyebariskannya (bagaimanapun kami selalu memiliki #[inline] ).

@pc

let body: MyResponse = client.get("http://api").await?.into_json().await?;

Perasaan saya adalah bahwa ini pada dasarnya mengulangi masalah API masa depan: membuat rantai lebih mudah, tetapi jenis rantai itu menjadi jauh lebih sulit untuk ditembus dan di-debug.

Bukankah argumen yang sama berlaku untuk? operator sekalipun? Ini memungkinkan untuk lebih banyak rantai dan dengan demikian membuat debugging lebih sulit. Tapi kenapa kita memilih? mencoba lebih! kemudian? Dan mengapa Rust lebih memilih API dengan pola pembangun? Jadi Rust sudah membuat banyak pilihan yang mendukung rantai di API. Dan ya, itu mungkin mempersulit proses debug, tetapi bukankah seharusnya hal itu terjadi pada tingkat lint - mungkin lint baru untuk clippy untuk rantai yang terlalu besar? Saya masih harus melihat motivasi yang cukup tentang bagaimana menunggu berbeda di sini.

Apakah itu hal yang baik atau buruk? Untuk kasus di mana ada N metode, masing-masing dapat menghasilkan tindak lanjut M, pengembang seharusnya menyediakan N * M metode? Saya pribadi, menyukai solusi yang dapat disusun, meski sedikit lebih lama.

N dan M tidak selalu besar, dan juga tidak semuanya mungkin menarik / berguna untuk diekstraksi.

@distroartonline ,

  1. Saya tidak setuju, keakraban berlebihan di sini. Saat bermigrasi dari bahasa lain, orang pertama akan mencari kemampuan untuk melakukan pemrograman bergaya async-await tetapi tidak untuk sintaks yang persis sama. Jika itu akan diimplementasikan secara berbeda tetapi itu akan memberikan pengalaman pemrograman yang lebih baik, maka itu akan menjadi keuntungan lain.

  2. Ketidakmampuan untuk men-debug rantai panjang adalah batasan debugger bukan gaya kode. Tentang keterbacaan, menurut saya itu tergantung, dan penerapan gaya imperatif tidak terlalu membatasi di sini. Jika async pemanggilan fungsi masih merupakan pemanggilan fungsi maka mengejutkan untuk tidak mendukung perangkaian. Dan bagaimanapun, <-- adalah operator awalan dengan kemampuan untuk menggunakannya dalam rantai fungsi sebagai opsi tambahan, seperti yang Anda katakan

@skade Setuju.

Semua contoh dengan rantai panjang menunggu adalah bau kode. Mereka dapat diselesaikan jauh lebih elegan dengan kombinator atau API yang diperbarui, daripada membuat mesin negara spaghetti generator ini di bawah tenda. Sintaks singkat yang ekstrem adalah resep untuk proses debug yang lebih sulit daripada masa depan saat ini.

Saya masih penggemar dari kedua awalan kata kunci await dan makro await!(...) / .await!() , dalam kombinasi dengan async dan #[async] fungsi pembangkit , seperti yang dijelaskan dalam komentar saya Di Sini .

Jika semuanya berjalan dengan benar, makro tunggal mungkin dapat dibuat untuk await! untuk menangani prefiks dan sufiks, dan kata kunci await hanya akan menjadi kata kunci di dalam fungsi async .

@bayu_joo
Bagi saya, kerugian besar menggunakan kombinator pada future adalah mereka akan menyembunyikan panggilan fungsi async dan tidak mungkin membedakannya dari fungsi biasa. Saya ingin melihat semua poin yang dapat ditangguhkan karena kemungkinan besar mereka akan digunakan di dalam rantai gaya pembangun.

@ I60R debugging rantai panggilan panjang sama sekali bukan masalah debugger, karena masalah dengan menulis rantai ini mendapatkan inferensi jenis yang benar. Mengingat bahwa banyak dari metode tersebut mengambil parameter generik dan membagikan parameter generik, yang berpotensi terikat pada penutupan, merupakan masalah serius.

Saya tidak yakin apakah membuat semua titik suspensi terlihat adalah tujuan dari fitur tersebut. Ini ada pada pelaksana untuk memutuskan. Dan itu sangat mungkin dengan semua versi sintaks await diusulkan.

@novacrazy poin bagus, sekarang Anda menyebutkannya, sebagai mantan javascripter, saya TIDAK PERNAH menggunakan rantai menunggu, saya selalu menggunakan lalu blok

Saya kira itu akan terlihat seperti itu

let result = (await doSomethingAsync()
          .then(|result| {
                     match result {
                          Ok(v) => doSomethingAsyncWithFirstResponse(v)
                          Err(e) => Future.Resolve(Err(e))
                      }
            }).then(|result| {
                  Ok(result.unwrap())
            })).unwrap();

yang saya bahkan tidak tahu apakah itu mungkin, saya perlu mencari masa depan di Rust

@richardanaya ini sangat mungkin (sintaks berbeda).

Like the Box impl:

impl<T> Box<T> {
    #[inline]
    pub fn new(x: T) -> Box<T> {
        box x
    }
    ...
}

Kita dapat memasukkan kata kunci baru await dan ciri Await dengan impl seperti ini:

impl<T> Await for T {
    #[inline]
    pub fn await(self) -> T {
        await self
    }
    ...
}

Dan gunakan await sebagai metode dan juga sebagai kata kunci:

let result = await foo();
first().await()?.second().await()?;

@richardanaya Setuju. Saya termasuk di antara beberapa pengembang pertama yang mengadopsi async / menunggu barang webdev saya bertahun-tahun yang lalu, dan kekuatan sebenarnya berasal dari menggabungkan async / menunggu dengan Janji / Masa Depan yang ada. Juga, bahkan contoh Anda mungkin dapat disederhanakan sebagai:

let result = await doSomethingAsync()
                  .and_then(doSomethingAsyncWithFirstResponse);

let value = result.unwrap();

Jika Anda memiliki berjangka berjangka atau hasil berjangka atau hasil berjangka, kombinator .flatten() dapat lebih menyederhanakannya secara drastis. Ini akan menjadi bentuk yang buruk untuk membuka dan menunggu setiap orang secara manual.

@XX Itu mubazir dan tidak valid. await hanya ada di async fungsi, dan hanya bisa bekerja pada jenis yang mengimplementasikan Future / IntoFuture , jadi tidak perlu yang baru sifat.

@novrazy , @richardanaya

mereka dapat diselesaikan jauh lebih elegan dengan kombinator atau API yang diperbarui, daripada membuat mesin negara spaghetti generator ini di bawah tenda. Sintaks singkat yang ekstrem adalah resep untuk proses debug yang lebih sulit daripada masa depan saat ini.

@novacrazy poin bagus, sekarang Anda menyebutkannya, sebagai mantan javascripter, saya TIDAK PERNAH menggunakan rantai menunggu, saya selalu menggunakan lalu blok

Kombinator memiliki sifat dan kekuatan yang sangat berbeda di Rusts async / await, misalnya terkait peminjaman di seluruh titik hasil. Anda tidak dapat dengan aman menulis kombinator yang memiliki kemampuan yang sama dengan blok asinkron. Mari kita tinggalkan diskusi kombinator dari utas ini, karena itu tidak membantu.

@ I60

Bagi saya, kerugian besar menggunakan kombinator di masa mendatang adalah bahwa mereka akan menyembunyikan pemanggilan fungsi asinkron dan tidak mungkin membedakannya dari fungsi biasa. Saya ingin melihat semua poin yang dapat ditangguhkan karena kemungkinan besar mereka akan digunakan di dalam rantai gaya pembangun.

Anda tidak dapat menyembunyikan poin penangguhan. Satu-satunya hal yang memungkinkan kombinator adalah menciptakan masa depan lainnya. Di beberapa titik waktu itu harus ditunggu.

Perlu diingat bahwa C # tidak memiliki masalah di mana sebagian besar kode yang menggunakan awalan await akan menggabungkannya dengan operator (yang sudah ada) postfix ? . Secara khusus, pertanyaan tentang prioritas, dan kecanggungan umum karena memiliki dua "dekorator" yang serupa muncul di sisi ekspresi yang berlawanan.

@ Matthias247 Jika Anda memang perlu meminjam data, jangan ragu untuk menggunakan beberapa pernyataan await . Namun, sering kali Anda hanya perlu memindahkan data, dan kombinator benar-benar valid untuk itu, dan berpotensi menyusun kode yang lebih efisien. Terkadang dioptimalkan sepenuhnya.

Sekali lagi, kekuatan sebenarnya adalah menggabungkan hal kode yang dapat dipelihara, berkinerja, dan dapat dibaca . 80% dari waktu saya ragu saya bahkan akan menyentuh async / await tidak peduli apa sintaksnya, hanya untuk menyediakan API yang paling stabil dan berkinerja menggunakan kontrak berjangka biasa.

Dalam hal ini, kata kunci awalan await dan / atau makro campuran await!(...) / .await!() adalah opsi yang paling mudah dibaca, dikenal, dan mudah di-debug.

@dyteachman

Saya melihat posting Anda dan sangat setuju, pada kenyataannya, sekarang saya merenungkan "keakraban", menurut saya ada dua jenis keakraban:

  1. Membuat sintaks await terlihat seperti bahasa serupa yang menggunakan banyak await. Javascript cukup besar di sini, dan sangat relevan dengan kemampuan WASM kami sebagai komunitas.

  2. Jenis keakraban kedua adalah membuat kode terlihat familier bagi developer yang hanya pernah bekerja dengan kode sinkron. Saya rasa aspek terbesar dari menunggu adalah membuat kode asinkron TERLIHAT sinkron. Ini sebenarnya satu hal yang benar-benar salah javascript adalah bahwa itu dinormalisasi lama kemudian rantai yang terlihat sangat asing untuk devs sinkron.

Satu hal yang saya tawarkan dari masa async javascript saya, apa yang jauh lebih berguna bagi saya sebagai pengembang dalam retrospeksi adalah kemampuan untuk mengelompokkan janji / masa depan bersama. Promise.all (p1 (), p2 ()) sehingga saya dapat dengan mudah memparalelkan pekerjaan. Rangkaian then () selalu hanya gema dari janji Javascript di masa lalu, tetapi cukup kuno dan tidak perlu sekarang karena saya memikirkannya.

Saya mungkin menawarkan ide menunggu ini. "Cobalah untuk membuat perbedaan antara kode asinkron dan kode sinkronisasi seminimal mungkin"

@novacrazy Fungsi async mengembalikan tipe impl Future , bukan? Apa yang mencegah kita menambahkan metode await ke Future trait? Seperti ini:

pub fn await(self) -> Self::Output {
    await self
}
...

@XX Seperti yang saya pahami, async fungsi di Rust diubah menjadi mesin negara menggunakan generator. Artikel ini adalah penjelasan yang bagus. Jadi await membutuhkan fungsi async untuk bekerja, sehingga kompilator dapat mengubah keduanya dengan benar. await tidak dapat bekerja tanpa bagian async .

Future memang memiliki metode wait , yang mirip dengan yang Anda sarankan, tetapi memblokir utas saat ini.

@bayu_joo

Tetapi bagaimana gaya kode dapat memengaruhi inferensi tipe? Saya tidak dapat melihat perbedaan dalam kode yang sama yang ditulis dengan gaya dirantai dan imperatif. Jenisnya harus persis sama. Jika debugger tidak dapat menemukannya, itu pasti masalah di debugger, bukan dalam kode.

@skade , @ Matthias247 , @XX

Dengan kombinator Anda akan memiliki tepat satu titik ditangguhkan yang ditandai di awal rantai fungsi. Semua lainnya akan tersirat di dalam. Itu persis masalah yang sama dengan mengambil mut implisit, yang secara pribadi bagi saya adalah salah satu titik kebingungan terbesar di Rust sama sekali. Beberapa API akan mengembalikan masa depan sementara yang lain akan mengembalikan hasil masa depan - saya tidak menginginkannya. Poin penangguhan harus eksplisit jika memungkinkan dan sintaks yang dapat disusun dengan benar akan mendorongnya

@ I60R let binding adalah titik persimpangan untuk mengetik inferensi dan membantu dalam pelaporan kesalahan.

@gila_gila

Jadi await membutuhkan fungsi async untuk bekerja

Bisakah ini diekspresikan dalam tipe pengembalian? Misalnya, jenis pengembaliannya adalah impl Future + Async , bukan impl Future .

@skade Saya selalu berpikir bahwa . dalam sintaks pemanggilan metode memiliki tujuan yang persis sama

@pc

Saya tidak berpikir di C # Anda akan mendapatkan kode rantai / fungsional sebanyak yang Anda dapatkan di Rust. Atau apakah saya salah? Itu membuat pengalaman C # devs / users menarik, tetapi tidak selalu berlaku untuk Rust. Saya berharap kami bisa kontras dengan Ocaml atau Haskell.

Anda mendapatkan sebanyak Rust. Lihat kode LINQ mana saja dan Anda akan melihatnya.

Kode asinkron tipikal terlihat seperti ini:

async Task<List<IGroping<int, PageMetadata>>> GetPageMetadata(string url, DbSet<Page> pages)
{
    using(var client = new HttpClient())
    using(var r = await client.GetAsync(new Uri(url)))
    {
        var content = await r.Content.ReadAsStringAsync();
                return await pages
                   .Where(x => x.Content == content)
                   .Select(x => x.Metadata)
                   .GroupBy(x => x.Id)
                   .ToListAsync();
    }
}

Atau, lebih umum

let a = await!(service_a);
let b = await!(some_method_on(a, some, other, params));
let c = await!(combine(somehow, a, b));

Anda tidak melakukan panggilan berantai, Anda menetapkannya ke variabel dan kemudian menggunakannya. Ini terutama benar jika Anda berurusan dengan pemeriksa peminjam.


Saya setuju bahwa Anda dapat merantai masa depan dalam satu situasi. Ketika Anda ingin menangani kesalahan yang bisa terjadi selama panggilan. misal let a = await!(service_a)? . Ini adalah satu-satunya situasi dimana alternatif postfix lebih baik. Saya bisa melihat keuntungannya di sini, tapi saya rasa itu tidak melebihi semua kekurangannya.

Alasan lain: bagaimana dengan impl Add for MyFuture { ... } dan let a = await a + b; ?

Saya ingin mengingatkan tentang Generalized Type Ascription RFC dalam konteks kata kunci menunggu postfix. Itu akan memungkinkan kode berikut:

let x = (0..10)
    .map(some_computation)
    .collect() : Result<Vec<_>, _>
    .unwrap()
    .map(other_computation) : Vec<usize>
    .into() : Rc<[_]>;

Sangat mirip dengan postfix menunggu kata kunci:

let foo = alpha() await?
    .beta await
    .some_other_stuff() await?
    .even_more_stuff() await
    .stuff_and_stuff();

Dengan kerugian yang sama ketika diformat dengan buruk:

foo.iter().map(|x| x.bar()).collect(): Vec<_>.as_ref()
client.get("https://my_api").send() await.unwrap().json() await.unwrap()

Saya pikir jika kita menelan pil Tipe Ascription RFC, kita harus menelan fut await untuk konsistensi.

BTW, tahukah Anda bahwa ini adalah sintaks yang valid:

fn main() {
    println
    !("Hello, World!");
}

Namun saya tidak melihat kejadian ini dalam kode sebenarnya.

Tolong izinkan saya untuk ngelantur sedikit. Saya pikir kita harus memberikan makro postfix, seperti yang disarankan oleh @BenoitZugmeyer , pemikiran lain. Itu bisa dilakukan sebagai expr!macro atau sebagai expr@macro , atau bahkan mungkin expr.macro!() . Saya pikir opsi pertama lebih disukai. Saya tidak yakin apakah itu ide yang bagus, tetapi jika kita ingin mengekstrak konsep umum dan bukan solusi ad-hoc sementara masih menunggu postfix , setidaknya kita harus memikirkan makro postfix sebagai solusi potensial.

Ingatlah bahwa meskipun kita membuat await makro postfix, itu akan tetap menjadi makro yang ajaib (seperti compile_error! ). Namun, @jplatte dan lainnya telah menetapkan bahwa itu bukan masalah.

Bagaimana saya akan melakukannya jika kita pergi ke rute itu, pertama-tama saya akan menetapkan dengan tepat bagaimana makro postfix akan bekerja, kemudian hanya mengizinkan await sebagai makro postfix ajaib dan kemudian mengizinkan makro postfix yang ditentukan sendiri.

Lexing

Mengenai lexing / parsing, ini mungkin menjadi masalah. Jika kita melihat expr!macro , kompilator mungkin berpikir bahwa ada makro bernama expr! dan kemudian ada beberapa huruf yang tidak valid macro setelah itu. Namun, expr!macro seharusnya dapat di-lex dengan melihat salah satunya, dan sesuatu menjadi makro postfix ketika ada expr diikuti oleh ! , langsung diikuti oleh identifier . Saya bukan pengembang bahasa dan saya tidak yakin apakah ini membuat lexing menjadi terlalu rumit. Saya hanya akan berasumsi bahwa makro postfix bisa berbentuk expr!macro .

Apakah makro postfix berguna?

Di atas kepala saya, saya telah menemukan kasus penggunaan lain ini untuk makro postfix. Saya tidak berpikir semuanya dapat diimplementasikan oleh makro khusus, jadi saya tidak yakin apakah daftar ini sangat membantu.

  • stream!await_all : menunggu streaming
  • option!or_continue : jika option adalah None, lanjutkan pengulangan
  • monad!bind : untuk melakukan =<< tanpa mengikatnya ke nama

stream!await_all

Ini memungkinkan kita untuk tidak hanya menunggu masa depan, tetapi juga arus.

event_stream("ws://some.stock.exchange/usd2eur")
    .and_then(|exchange_response| {
        let exchange_rate = exchange_response.json()?;
        stream::once(UpdateTickerAction::new(exchange_rate.value))
    })

akan sama dengan (dalam blok async -stream-esque):

let exchange_rate = event_stream("ws://some.stock.exchange/usd2eur")
    !await_all
    .json()?;

UpdateTickerAction::new(exchange_rate.value)

option!or_continue

Ini memungkinkan kita untuk membuka sebuah opsi, dan jika itu None , lanjutkan pengulangan.

loop {
    let event = match engine.event() {
        Some(event) => event,
        None => continue,
    }
    let button = match event.button() {
        Some(button) => button,
        None => continue,
    }
    handle_button_pressed(button);
}

akan setara dengan:

loop {
    handle_button_pressed(
        engine.event()!or_continue
            .button()!or_continue
    );
}

monad!bind

Yang ini akan memungkinkan kita untuk mendapatkan monad dengan cara yang cukup kasar (yaitu ekspresi terpusat). Rust belum memiliki sesuatu seperti monad dan saya tidak menjualnya untuk ditambahkan ke Rust. Namun demikian, jika mereka akan ditambahkan, sintaks ini mungkin berguna.

Saya mengambil yang berikut dari sini .

nameDo :: IO ()
nameDo = do putStr "What is your first name? "
            first <- getLine
            putStr "And your last name? "
            last <- getLine
            let full = first ++ " " ++ last
            putStrLn ("Pleased to meet you, " ++ full ++ "!")

akan setara dengan:

do {
    putStr("What is your first name? ")!bind;
    let first = getLine()!bind;
    putStr("And your last name? ")!bind;
    let last = getLine()!bind;
    let full = first + " " + &last
    putStrLn("Pleased to meet you, " + &full + "!")!bind;
}

Atau, lebih sesuai, dengan lebih sedikit let s:

do {
    putStr("What is your first name? ")!bind;
    let first = getLine()!bind;
    putStr("And your last name? ")!bind;
    putStrLn(
        "Pleased to meet you, " + &first + " " + &getLine()!bind + "!"
    )!bind;
}

Evaluasi

Saya sangat terpecah dalam hal ini. Bagian matematikawan di otak saya ingin menemukan solusi umum untuk await , dan makro postfix mungkin merupakan cara untuk mencapainya. Bagian pragmatis dari otak saya berpikir: Mengapa repot-repot membuat bahasa yang sistem makronya sudah tidak ada yang mengerti bahkan lebih rumit lagi.

Namun, dari ? dan await , kami memiliki dua contoh operator postfix yang sangat berguna. Bagaimana jika kita menemukan yang lain yang ingin kita tambahkan di masa depan, mungkin serupa dengan yang saya sebutkan? Jika kita telah menggeneralisasikannya, kita dapat menambahkannya ke Rust secara alami. Jika belum, kita harus membuat sintaks lain setiap kali, yang kemungkinan akan membengkak bahasa lebih dari yang makro postfix akan miliki.

apa yang kalian pikirkan?

@Yonaprima

Saya memiliki ide yang sangat mirip, dan yakin bahwa makro postfix di Rust adalah masalah waktu.
Saya memikirkannya setiap kali berurusan dengan ndarray mengiris:

let view = array.slice(s![.., ..]);

tapi jauh lebih baik

let view = array.slice![.., ..];
// or like you suggested
let view = array!slice[.., ..];
// or like in PHP
let view = array->slice![.., ..];

dan banyak kombinator mungkin hilang, seperti yang memiliki _with atau _else perbaikan pos:

opt!unwrap_or(Error::new("Error!"))?; //equal to .unwrap_or_else(||Error::new("Error!"));

@EyeOfPython @andreytkachenko Makro postfix saat ini bukan fitur di Rust dan IMHO akan membutuhkan fase Implementasi RFC + FCP + lengkap.

Ini bukan diskusi RFC, tetapi diskusi tentang RFC yang diterima yang perlu dilaksanakan.

Oleh karena itu, menurut saya tidak praktis untuk membahasnya di sini atau mengusulkannya untuk sintaks asinkron. Ini selanjutnya akan menunda fitur secara besar-besaran.

Untuk mengekang diskusi yang sudah masif ini, menurut saya _tidak_ berguna untuk membahasnya di sini, karena dapat dilihat di luar masalah yang dibahas.

Hanya beberapa pemikiran: Meskipun utas ini khusus untuk await , saya pikir kita akan memiliki diskusi yang sama untuk ekspresi yield di jalan, yang juga dapat dirantai. Karena saya lebih suka melihat sintaks yang dapat digeneralisasikan, bagaimanapun tampilannya.

Alasan saya untuk tidak menggunakan makro di sini:

  1. Makro disediakan untuk hal-hal khusus domain dan menggunakannya dengan cara apa pun yang mengubah aliran kontrol program atau mengemulasi fitur bahasa inti adalah hal yang berlebihan
  2. Makro Postfix akan segera disalahgunakan untuk mengimplementasikan operator kustom atau fitur bahasa esotheric lainnya yang mengarah ke kode yang buruk
  3. Mereka akan mencegah pengembangan fitur bahasa yang tepat:

    • stream.await_all adalah kasus penggunaan sempurna untuk kombinator

    • option.or_continue dan mengganti _else combinators adalah kasus penggunaan yang sempurna untuk operator penggabungan nol

    • monad.bind adalah kasus penggunaan yang sempurna untuk rantai if-let

    • ndarray slicing adalah kasus penggunaan yang sempurna untuk const obat generik

  4. Mereka akan mengajukan pertanyaan sudah menerapkan operator ?

@koleksitiketbis

yang juga bisa dirantai

Mengapa Anda mengira mereka bisa dirantai? nol kejadian dalam kode nyata, seperti println contoh di atas.

@Pzixel Kemungkinan pada akhirnya Generator::resume akan mengambil nilai, dan dengan demikian yield expr akan memiliki tipe non- () .

@vlaff Saya tidak melihat dengan jelas argumen untuk await harus konsisten dengan Type Ascription. Mereka adalah hal yang sangat berbeda.

Juga, Type Ascription adalah fitur lain yang berulang kali dicoba, tidak ada jaminan bahwa _ini_ akan berhasil. Meskipun saya tidak ingin menentang ini, TA adalah RFC masa depan dan ini adalah diskusi tentang fitur yang diterima dengan sintaks yang sudah diusulkan.

Membaca banyak komentar, untuk menambahkan ringkasan lebih lanjut tentang mengapa await! (...) muncul sebagai jalur yang sangat ideal:

  1. itu terlihat paling familiar dengan kode yang ada karena makro sudah familiar
  2. ada pekerjaan yang sudah ada yang menggunakan await! (...) https://github.com/alexcrichton/futures-await dan dapat membantu mengurangi penulisan ulang kode
  3. karena makro postfix tidak ada di tabel, mungkin tidak pernah menjadi bagian dari bahasa, dan "await!" dalam konteks non standar sepertinya tidak ada kemungkinan sesuai RFC
  4. ini memberi komunitas lebih banyak waktu untuk mempertimbangkan arah jangka panjang setelah penggunaan stabil yang tersebar luas sambil memberikan sesuatu yang tidak sepenuhnya keluar dari norma (mis. coba! ())
  5. bisa digunakan sebagai pola serupa untuk hasil mendatang! () sampai kita menemukan jalur resmi yang bisa memuaskan menunggu dan hasil
  6. rangkaian mungkin tidak seberharga yang diharapkan dan kejelasan mungkin bahkan ditingkatkan dengan memiliki banyak menunggu di beberapa baris
  7. tidak ada perubahan yang harus terjadi untuk penyorot sintaks IDE
  8. orang-orang dari bahasa lain mungkin tidak akan terlempar oleh makro menunggu! (...) setelah melihat penggunaan makro lainnya (lebih sedikit kelebihan kognitif)
  9. itu mungkin jalur upaya paling minimal dari semua jalur untuk bergerak maju ke stabilisasi

menunggu! makro sudah ada di sini, pertanyaannya adalah tentang rantai.

Semakin saya memikirkannya, semakin banyak postfix yang bagus untuk saya. Sebagai contoh:

let a = foo await;
let b = bar await?;
let c = baz? await;
let d = booz? await?;
let e = kik? + kek? await? + kuk? await?;
// a + b is `impl Add for MyFuture {}` which alises to `a.select(b)`

Memungkinkan bersarang di tingkat mana pun, dan quire dapat dibaca. Bekerja mulus dengan ? dan kemungkinan operator masa depan. Ini terlihat agak asing bagi pendatang baru, tetapi keuntungan tata bahasa mungkin melebihi itu.

Alasan utamanya adalah await harus menjadi kata kunci terpisah, bukan pemanggilan fungsi. Ini terlalu penting sehingga harus memiliki sorotan dan tempat tersendiri di teks.

@Pixel , @Heroator , @skade

Lihat misalnya Python yield dan yield from ekspresi: di sana fungsi luar dapat memberikan nilai yang akan menjadi hasil dari yield saat generator dilanjutkan. Inilah yang dimaksud @valff , dan tidak ada hubungannya dengan tipe ascriptions. Karenanya, yield juga akan memiliki tipe selain ! atau () .

Dari titik coroutine, yield dan await menangguhkannya dan (pada akhirnya) dapat mengembalikan nilai. Karena mereka hanyalah 2 sisi dari mata uang yang sama.

Dan untuk membuang kemungkinan sintaks lainnya, _square-brackets-with-keyword_, sebagian untuk menyorotnya (menggunakan yield untuk penyorotan sintaks):

let body: MyResponse = client.get("http://api").send()[yield]?.into_json()[yield]?

"kata kunci postfix" paling masuk akal bagi saya. postfix dengan karakter selain spasi yang memisahkan ekspresi masa depan dan await juga masuk akal bagi saya tetapi tidak '.' karena itu untuk metode dan await bukanlah metode. Namun, jika Anda ingin await sebagai kata kunci awalan, saya suka saran @XX dari Sifat atau metode yang hanya memanggil await self bagi mereka yang ingin menyatukan banyak hal (walaupun kita mungkin tidak bisa memberi nama metode await ; saya pikir hanya wait saja). Secara pribadi, saya mungkin akan membuat await-per-line dan tidak terlalu banyak rantai karena saya menemukan rantai panjang kurang dapat dibaca sehingga awalan atau postfix akan berfungsi untuk saya.

[sunting] Saya lupa bahwa wait sudah ada dan menghalangi masa depan, jadi gosoklah pikiran itu.

@roland Saya secara khusus mengacu pada ini, yang membahas jenis ascriptions: https://github.com/rust-lang/rust/issues/57640#issuecomment -456023146

@rolandsteiner jadi Anda menulis

let body: MyResponse = client.get("http://api").send() await?.into_json() await?;

Ketika saya menulisnya seperti:

let response = client.get("http://api").send() await?;
let body: MyResponse = response.into_json() await?;

@skade Oh, maksud Anda komentar yang berbeda dari yang saya kira, maaf. : stuck_out_tongue:

@Pzixel Kemungkinan pada akhirnya Generator::resume akan mengambil nilai, dan dengan demikian yield expr akan memiliki tipe non- () .

Karenanya, yield juga akan memiliki tipe selain ! atau () .

@valff , @rolandsteiner Saya merasa tidak mungkin bahwa yield akan mengembalikan nilai melanjutkan, yang sulit untuk dimasukkan ke dalam bahasa yang diketik secara statis tanpa membuat sintaks generator dan / atau sifat mengganggu untuk bekerja dengannya. Prototipe asli dengan argumen resume menggunakan kata kunci gen arg untuk merujuk pada argumen ini, sesuatu seperti itu kemungkinan besar akan bekerja dengan baik IMO. Dari sudut pandang itu yield masih akan mengembalikan () jadi seharusnya tidak mempengaruhi pembahasan await banyak.

Saya pikir await harus menjadi operator awalan, karena async adalah (dan saya mengharapkan yield menjadi awalan). Atau menjadikannya sebagai metode biasa, digunakan dalam posisi postfix, lalu.

Namun, saya tidak menjalankan semua bikeshedding di sini: mengapa bahkan mempertimbangkan kata kunci postfix yang tidak dimiliki Rust lainnya? Itu akan membuat bahasanya jadi aneh.

Juga, merangkai masa depan, saya mengerti mengapa itu bisa menarik. Tapi rantai menunggu? Apa artinya itu? Masa depan yang mengembalikan masa depan baru? Apakah ini benar-benar idiom yang sangat umum sampai kita ingin Rust memiliki idiom itu sebagai warga kelas satu? Jika kita benar-benar peduli tentang itu, saya pikir kita harus:

  1. Pergi untuk metode (yaitu foo.await() ). Itu tidak memperkenalkan kata kunci postfix yang aneh dan kita semua tahu artinya. Kita juga bisa menghubungkannya dengan itu.
  2. Jika kita benar-benar menginginkan kata kunci / loloperator, kita bisa menyelesaikan pertanyaan itu nanti.

Juga, demi memberikan pendapat pribadi saya, saya benci await dalam posisi kata kunci postfix.

Awalan await digunakan dalam bahasa lain karena suatu alasan - cocok dengan bahasa aslinya. Anda tidak mengatakan "Saya akan menunggu kedatangan Anda". Meskipun telah menulis JavaScript, saya mungkin sedikit bias.

Saya juga akan mengatakan bahwa saya menganggap await -chaining terlalu tinggi. Nilai async/await selama kombinator masa depan adalah memungkinkan seseorang untuk menulis kode asinkron secara berurutan, memanfaatkan konstruksi bahasa standar seperti if , match , dan sebagainya. Bagaimanapun, Anda pasti ingin memutuskan penantian Anda.

Jangan membuat terlalu banyak sintaks ajaib untuk async/await , itu hanya sebagian kecil dari bahasa. Sintaks metode yang diusulkan (yaitu foo.await() ) adalah IMO terlalu ortogonal terhadap pemanggilan metode normal dan dianggap terlalu ajaib. Mesin proc-macro sudah siap, mengapa tidak menyembunyikannya di balik itu?

Bagaimana jika menggunakan await kata kunci sebagai lawan dari async dalam definisi fungsi? Sebagai contoh:

await fn foo(future: impl Future<Output = i32>) -> i32 {
    future
}

await fn ini hanya dapat dipanggil dalam async konteks:

async {
    let n = foo(bar());
}

Dan menginginkan let n = (await foo(bar())); .
Kemudian di samping kata kunci await , ciri Future dapat mengimplementasikan metode await untuk menggunakan logika await pada posisi postfix, contoh:

async {
    let n = bar().awaited();
}

Juga, dapatkah seseorang menjelaskan kepada saya hubungan dengan generator? Saya terkejut async / await diimplementasikan sebelum generator (bahkan distabilkan).

Generator @phaazon telah ditambahkan ke Rust sebagai detail implementasi internal untuk async / await . Tidak ada rencana saat ini untuk menstabilkannya, dan mereka masih memerlukan RFC non-eksperimental sebelum bisa. (Secara pribadi saya ingin mereka distabilkan, tetapi tampaknya mereka akan setidaknya satu atau dua tahun lagi, mungkin tidak di-RFC sampai setelah async / await stabil dan sudah ada beberapa pengalaman dengannya).

@XX Saya pikir Anda melanggar semantik async jika Anda menggunakan await dalam definisi fungsi. Mari tetap berpegang pada standar, bukan?

@phaazon Dapatkah Anda menjelaskan secara lebih rinci bagaimana ini merusak semantik async ?

Kebanyakan bahasa menggunakan async untuk memperkenalkan sesuatu yang akan menjadi asinkron dan await menunggu untuk itu. Jadi memiliki await daripada async agak aneh, bagi saya.

@phaazon Tidak, bukan sebagai gantinya, tapi sebagai tambahan. Contoh lengkap:

async fn bar() -> i32 {
    5 // will be "converted" to impl Future<Output = i32>
}

await fn foo(future: impl Future<Output = i32>) -> i32 {
    future // will be "converted" to i32 in async context
}

async {
    let a = await bar(); // correct, a == 5
    let b = foo(bar()); // correct, b == 5
}

let c = foo(bar()); // error, can't call desugaring statement `await foo(bar())`

jika valid, maka dimungkinkan untuk mengimplementasikan await-method untuk digunakan dalam sebuah rantai:

async {
    let n = first().awaited()?.second().awaited()?;
    // let n = (await (await first())?.second())?;
}

Menurut saya, tidak masalah jika postfix menunggu "terasa" terlalu ajaib dan tidak familiar dengan bahasanya. Jika kita mengadopsi sintaks metode .await() atau .await!() maka itu hanya masalah menjelaskan bahwa Future s memiliki metode .await() atau .await!() yang juga dapat Anda gunakan untuk menunggu mereka dalam kasus yang masuk akal. Tidak sulit untuk memahami jika Anda menghabiskan 5 menit untuk memikirkannya, bahkan jika Anda belum pernah melihatnya sebelumnya.

Selain itu, jika kita menggunakan kata kunci awalan, apa yang menghentikan kita dari menulis peti dengan yang berikut (pseudocode tidak lengkap karena saya tidak memiliki infrastruktur untuk menangani detailnya sekarang):

trait AwaitChainable {
    fn await(self) -> impl Future;
}

impl<T: Future> AwaitChainable for T {
    fn await(self) -> impl Future {
        await self
    }
}

Dengan begitu kita bisa mendapatkan postfix menunggu jika kita mau. Maksud saya, meskipun itu tidak mungkin dan kita harus mengimplementasikan postfix menunggu secara ajaib oleh kompiler, kita masih dapat menggunakan sesuatu seperti contoh di atas untuk menjelaskan bagaimana cara kerjanya. Tidak akan sulit untuk mempelajarinya.

@ivandardi saya memikirkan hal yang sama. Tapi definisi ini

fn await(self) -> impl Future {
    await self
}

tidak mengatakan bahwa fungsi hanya dapat dipanggil dalam async -context.

@XX Ya, seperti yang saya katakan, abaikan pseudocode yang rusak: PI saat ini tidak memiliki infrastruktur untuk mencari bagaimana sintaks dan sifat yang benar harus ditulis di sana :( Bayangkan saja saya menulis hal yang membuatnya hanya dapat dipanggil di asinkron konteks.

@XX , @ivandardi Sesuai definisi, await hanya bekerja dalam konteks async . Oleh karena itu, ini ilegal:

fn await(self) -> impl Future {
    await self
}

Itu pasti

async fn await(self) -> impl Future {
    await self
}

Yang hanya dapat dipanggil dalam konteks async seperti ini:

await future.await()

Jenis mana yang mengalahkan seluruh tujuan.

Satu-satunya cara untuk membuat ini bekerja adalah dengan mengubah async sepenuhnya, yang tidak ada di meja.

@ivandardi Evolusi versi saya ini:

fn await(self) -> T { // How to indicate using in async-context only?
    await self
}

`` karat
await fn await (self) -> T {// Karena async sudah diambil
menunggu diri sendiri
}

```rust
await fn await(self) -> T {
    self // remove excess await
}

@CryZe @XX Mengapa Anda mencemooh saya? Aku benar.

@XX Apa yang Anda coba lakukan adalah mengubah arti async dan menunggu sepenuhnya (mis. Buat konteks await yang entah bagaimana berbeda dari konteks async ). Saya tidak berpikir itu akan mendapat banyak dukungan dari pengembang bahasa

@EyeOfPython Definisi ini tidak bekerja seperti yang diharapkan:

async fn await(self) -> impl Future {
    await self
}

Lebih mungkin:

#[call_only_in_async_context_with_derived_await_prefix]
fn await(self) -> impl Future {
    self
}

Saya downvoting karena Anda mengabaikan bahwa ini adalah sintaks pseudo hipotetis. Pada dasarnya poin mereka adalah bahwa Rust dapat menambahkan sifat khusus seperti Drop dan Copy yang dimengerti oleh bahasa, yang menambahkan kemampuan untuk memanggil .await () pada tipe yang kemudian berinteraksi dengan tipe seperti kata kunci await. Selain itu, pemungutan suara juga dianggap tidak relevan untuk RFC, karena intinya adalah untuk menemukan solusi yang obyektif, bukan berdasarkan perasaan subjektif seperti yang divisualisasikan oleh suara positif / suara negatif.

Saya tidak mengerti apa yang harus dilakukan kode ini. Tetapi tidak ada hubungannya dengan cara async menunggu saat ini bekerja. Jika Anda peduli tentang hal itu, harap jauhkan dari utas ini dan tulis RFC khusus untuk itu.

@CryZe Tipe itu tersedia. Ini disebut Masa Depan. Dan telah disepakati sejak lama bahwa async / await adalah mekanisme yang HANYA ditargetkan untuk mengubah kode asinkron. Ini tidak dimaksudkan sebagai notasi jilid umum.

@ivandari postfix Anda menunggu tidak menunggu tetapi menciptakan masa depan baru. Ini pada dasarnya adalah fungsi identitas. Anda tidak dapat menunggu secara implisit, karena ini bukan fungsi biasa.

@ Matthias247 Apa yang mereka coba sarankan adalah cara agar postfix menunggu memiliki sintaks dari pemanggilan metode. Dengan begitu Rust tidak perlu memasukkan simbol baru yang sewenang-wenang seperti #, @ atau ... seperti yang kita lakukan dengan? operator, dan masih terlihat cukup alami:

let result = some_operation().await()?.some_method().await()?;

Jadi idenya adalah entah bagaimana menunggu menjadi jenis metode khusus pada sifat Future yang dilihat oleh kompilator dengan cara yang sama seperti kata kunci await normal (yang tidak Anda perlukan sama sekali dalam kasus ini) dan ubah async kode ke generator dari sana. Kemudian Anda memiliki aliran kontrol logis yang tepat dari kiri ke kanan alih-alih kiri kanan kiri dengan await some_future()? dan tidak perlu memasukkan simbol baru yang aneh.

(Jadi tl; dr: ini terlihat seperti pemanggilan metode tetapi sebenarnya hanya menunggu postfix)

Sesuatu yang belum pernah saya lihat disebutkan secara eksplisit dalam pertimbangan sintaks postfix adalah prevalensi dari kombinasi kesalahan dan opsi. Ini adalah tempat di mana karat paling berbeda dari semua bahasa yang menunggu - alih-alih penelusuran balik otomatis, kami memiliki .map_err()? .

Dalam hal-hal tertentu seperti:

let result = await reqwest::get(..).send();
let response = result.map_err(|e| add_context(e))?;
let parser = await response.json();
let parsed = parser.map_err(|e| add_context(e))?;

kurang terbaca daripada (saya tidak peduli sintaks postfix mana yang dipertimbangkan):

let parsed = reqwest::get(..)
    .send() await
    .map_err(add_context)?
    .json() await
    .map_err(add_context)?;

_meskipun_ pemformatan default ini memiliki lebih banyak baris daripada pendekatan multi-variabel. Ekspresi yang tidak memiliki banyak menunggu sepertinya harus mengambil lebih sedikit ruang vertikal dengan postfix-await.

Saya tidak menulis kode async sekarang, jadi saya berbicara karena ketidaktahuan, maaf menumpuk - jika ini bukan masalah dalam praktiknya, maka itu luar biasa. Saya hanya tidak ingin penanganan kesalahan yang tepat dibuat kurang ergonomis hanya agar lebih mirip dengan bahasa lain yang melakukan penanganan kesalahan secara berbeda.

Ya, itulah yang saya maksud. Ini akan menjadi yang terbaik dari kedua dunia, memungkinkan orang untuk memilih apakah akan menggunakan prefiks atau postfix menunggu tergantung pada situasinya.

@XX Jika itu tidak dapat diekspresikan dalam kode pengguna, maka kita harus menggunakan kompiler untuk mengimplementasikan fungsionalitas itu. Namun dalam hal pemahaman cara kerja postfix, penjelasan yang saya posting sebelumnya tetap berfungsi, walaupun hanya pemahaman yang dangkal.

@CryZe, @XX,

Sekilas terlihat mulus, tapi sama sekali tidak sesuai dengan filosofi Rust. Bahkan sort metode pada iterator tidak mengembalikan self dan memutus rantai panggilan metode untuk membuat alokasi eksplisit. Tapi Anda mengharapkan sesuatu yang tidak kurang berdampak untuk diimplementasikan dengan cara implisit yang tidak bisa dibedakan dari pemanggilan fungsi biasa. Tidak ada kemungkinan IMO.

Ya, itulah yang saya maksud. Ini akan menjadi yang terbaik dari kedua dunia, memungkinkan orang untuk memilih apakah akan menggunakan prefiks atau postfix menunggu tergantung pada situasinya.

Mengapa itu harus menjadi tujuan? Rust tidak mendukung ini untuk semua aliran kontrol lainnya (misalnya break , continue , if , dll. Tidak ada alasan khusus, selain dari "ini mungkin terlihat lebih bagus dalam pendapat tertentu.

Secara umum saya ingin mengingatkan semua orang bahwa await cukup spesial dan bukan pemanggilan metode normal:

  • Debugger mungkin bertingkah aneh saat melangkahi menunggu, karena tumpukan mungkin terlepas dan dibangun kembali. Sejauh yang saya ingat, butuh banyak waktu untuk mendapatkan async debugging bekerja di C # dan Javascript. Dan mereka memiliki tim berbayar yang bekerja pada debugger!
  • Objek lokal yang tidak berhasil melewati titik menunggu dapat disimpan di tumpukan OS yang sebenarnya. Yang tidak harus dipindahkan ke masa depan yang dihasilkan, yang pada akhirnya akan membuat perbedaan dalam memori heap yang diperlukan (di situlah Masa Depan akan hidup).
  • Meminjam di seluruh titik menunggu adalah alasan mengapa masa depan yang dihasilkan harus !Unpin , dan menyebabkan banyak ketidaknyamanan dengan beberapa kombinator dan mekanisme yang ada. Ini mungkin berpotensi menghasilkan Unpin futures dari async metode yang tidak meminjam menunggu di masa depan. Namun jika await s tidak terlihat, itu tidak akan pernah terjadi.
  • Mungkin ada masalah pemeriksa peminjam tak terduga lainnya, yang tidak tercakup oleh status async / await saat ini.

@Pzixel @lnicola

Contoh yang Anda berikan untuk C # benar-benar penting, dan tidak seperti rantai panjang gaya fungsional yang sering kita lihat di Rust. Sintaks LINQ itu hanyalah DSL, dan tidak seperti foo().bar().x().wih_boo(x).camboom().space_flight(); Saya sudah mencari-cari di Google, dan contoh kode C # terlihat 100% penting, sama seperti kebanyakan bahasa pemrograman populer. Itulah mengapa kita tidak bisa begitu saja mengambil apa yang dilakukan bahasa X, karena memang tidak sama.

Notasi awalan IMO sangat cocok dengan gaya pengkodean imperatif. Tapi Rust mendukung kedua gaya tersebut.

@tokopedia

Saya tidak setuju dengan beberapa komentar Anda (dan lainnya) tentang masalah dengan gaya fungsional. Untuk membuatnya tetap singkat - mari kita sepakati saja bahwa ada orang dengan preferensi kuat terhadap satu atau yang lain.

@ Matthias247 Tidak, ada alasan khusus selain betapa bagus tampilannya. Itu karena Rust mendorong perangkaian. Dan Anda mungkin berkata "oh, mekanisme aliran kontrol lain tidak memiliki sintaks postfix, jadi mengapa harus menunggu menjadi khusus?". Nah, itu penilaian yang salah. Kami DO memiliki aliran kontrol postfix. Mereka hanya internal ke metode yang kami sebut. Option::unwrap_or seperti melakukan pernyataan pencocokan postfix. Iterator::filter seperti melakukan pernyataan if postfix. Hanya karena aliran kendali bukan bagian dari sintaks rantai itu sendiri, itu tidak berarti kita belum memiliki aliran kendali postfix. Dalam sudut pandang itu, menambahkan postfix await sebenarnya akan konsisten dengan apa yang kita miliki. Dalam kasus ini, kita bahkan dapat memiliki sesuatu yang lebih mirip dengan cara kerja Iterator dan daripada hanya menunggu postfix mentah, kita dapat memiliki beberapa kombinator menunggu, seperti Future::await_or . Bagaimanapun, menunggu postfix bukan hanya masalah penampilan - ini masalah fungsionalitas dan kualitas hidup. Jika tidak, kami masih akan menggunakan makro try!() , bukan?

@pc

Contoh yang Anda berikan untuk C # benar-benar penting, dan tidak seperti rantai panjang gaya fungsional yang sering kita lihat di Rust. Sintaks LINQ itu hanyalah DSL, dan tidak seperti foo (). Bar (). X (). Wih_boo (x) .camboom (). Space_flight (); Saya sudah mencari-cari di Google sedikit, dan contoh kode C # terlihat 100% penting, sama seperti kebanyakan bahasa pemrograman populer. Itulah mengapa kita tidak bisa begitu saja mengambil apa yang dilakukan bahasa X, karena memang tidak sama.

Itu tidak benar (Anda dapat memeriksa kerangka apa pun, misalnya polly ), tetapi saya tidak akan membantahnya. Saya melihat banyak kode yang dirantai dalam kedua bahasa. Namun, sebenarnya ada hal yang membuat semuanya berbeda. Dan itu disebut ciri Try . C # tidak memiliki kemiripan dengan itu sehingga tidak seharusnya melakukan apa pun yang melebihi await poin. Jika Anda mendapatkan pengecualian, itu akan secara otomatis dibungkus dan dimunculkan untuk pemanggil.

Ini tidak terjadi untuk Rust, di mana Anda harus menggunakan operator ? secara manual.

Jika Anda harus memiliki lebih dari await point Anda harus membuat operator "gabungan" await? , memperluas aturan bahasa, dll. ATAU Anda dapat menunggu postfix dan semuanya menjadi lebih alami (kecuali untuk sintaks sedikit alien, tapi siapa yang peduli?).

Jadi saat ini saya melihat postfix menunggu sebagai solusi yang lebih layak. Satu-satunya saran yang saya miliki adalah kata kunci khusus yang dipisahkan spasi, bukan await() atau await!() .

Saya rasa saya mendapatkan alasan yang bagus untuk membenarkan metode .await () normal. Jika Anda memikirkannya, itu tidak ada bedanya dengan cara pemblokiran lainnya. Anda memanggil metode .await (), eksekusi thread (mungkin hijau) akan diparkir dan pada titik tertentu runtime yang dieksekusi kemudian melanjutkan eksekusi thread di beberapa titik. Jadi untuk semua maksud dan tujuan, apakah Anda memiliki saluran Mutex atau std seperti:

let result = my_channel().recv()?.iter().map(...).collect();

atau masa depan seperti ini:

let result = my_future().await()?.iter().map(...).collect();

tidak ada bedanya. Keduanya memblokir eksekusi di recv () / await () masing-masing dan keduanya terhubung dengan cara yang sama. Satu-satunya perbedaan adalah bahwa await () mungkin berjalan pada eksekutor berbeda yang bukan merupakan penguliran berat OS (tetapi mungkin saja dalam kasus eksekutor berulir tunggal). Jadi mengecilkan hati sejumlah besar rangkaian memengaruhi keduanya dengan cara yang persis sama dan mungkin akan mengarah ke serat gumpalan yang sebenarnya.

Namun maksud saya di sini adalah bahwa dari orang yang menulis sudut pandang kode ini, tidak banyak perbedaan dalam .recv () atau .await (), keduanya adalah metode yang memblokir eksekusi dan kembali setelah hasilnya tersedia . Jadi, untuk semua maksud dan tujuan, ini bisa menjadi metode normal dan tidak memerlukan kata kunci yang lengkap. Kami juga tidak memiliki kata kunci recv atau lock untuk Mutex dan std.

Namun Rustc jelas ingin mengubah seluruh kode menjadi generator. Tetapi apakah itu benar-benar diperlukan secara semantik dari sudut pandang bahasa? Saya cukup yakin seseorang dapat menulis kompiler alternatif untuk rustc yang mengimplementasikan menunggu melalui pemblokiran utas OS yang sebenarnya (memasuki async fn perlu menelurkan utas dan kemudian menunggu akan memblokir utas itu). Sementara itu akan menjadi implementasi yang sangat naif dan lambat, itu akan berperilaku semantik dengan cara yang persis sama. Jadi fakta bahwa Rustc sebenarnya menunggu menjadi generator tidak diperlukan secara semantik. Jadi saya benar-benar berpendapat bahwa rustc mengubah panggilan ke metode .await () menjadi generator dapat dilihat sebagai detail implementasi yang mengoptimalkan dari rustc. Dengan cara itu Anda bisa membenarkan .await () menjadi semacam metode lengkap dan bukan kata kunci lengkap, tetapi juga masih memiliki Rustc mengubah semuanya menjadi generator.

@CryZe tidak seperti .recv() kita dapat dengan aman menginterupsi await dari tempat lain dalam program dan kemudian kode di sebelah await tidak akan dijalankan. Itu perbedaan besar dan itulah alasan paling berharga mengapa kita tidak boleh melakukan await secara implisit.

@ I60R Ini tidak kurang eksplisit dari menunggu sebagai kata kunci. Anda masih dapat menyorotnya dengan cara yang sama di IDE jika diinginkan. Itu hanya memindahkan kata kunci ke posisi postfix, di mana saya berpendapat itu sebenarnya kurang mudah untuk dilewatkan (karena persis di posisi di mana eksekusi berhenti).

Ide gila: menunggu implisit . Saya telah pindah ke utas terpisah, karena yang ini sudah terlalu sibuk dengan diskusi postfix vs prefix.

@Tokopedia

Saya rasa saya mendapatkan alasan yang bagus untuk membenarkan metode .await () normal. Jika Anda memikirkannya, itu tidak ada bedanya dengan cara pemblokiran lainnya.

Harap baca https://github.com/rust-lang/rust/issues/57640#issuecomment -456147515 lagi

@ Matthias247 Ini adalah poin yang sangat bagus, sepertinya saya melewatkannya.

Semua pembicaraan tentang membuat menunggu menjadi suatu fungsi, anggota atau implisit secara fundamental tidak valid. Ini bukanlah tindakan, ini transformasi.

Kompiler, pada tingkat tinggi, baik itu melalui kata kunci atau makro, mengubah ekspresi menunggu menjadi ekspresi hasil generator khusus, di tempat, menghitung pinjaman dan hal-hal lain di sepanjang jalan.

Ini harus eksplisit.

Itu harus baik sebagai pakaian mungkin sebagai kata kunci, atau jelas menghasilkan kode dengan makro.

Metode sihir, sifat, anggota, dll., Terlalu mudah untuk salah dibaca dan disalahpahami, terutama bagi pengguna baru.

Prefix keyword await atau awalan makro menunggu tampaknya cara yang paling dapat diterima untuk melakukan ini, yang masuk akal karena banyak bahasa lain melakukannya dengan cara itu. Kita tidak harus menjadi spesial untuk membuatnya bagus.

@gila_gila

Metode sihir, sifat, anggota, dll., Terlalu mudah untuk salah dibaca dan disalahpahami, terutama bagi pengguna baru.

Bisakah Anda mengembangkannya? Lebih khusus lagi pada varian .await!() , jika memungkinkan.

Memiliki foo.await!() tidak sulit untuk dibaca, menurut saya, TERUTAMA dengan penyorotan sintaks yang tepat, yang tidak boleh diabaikan.

Salah paham artinya? Apa yang dilakukan pada dasarnya adalah sebagai berikut (abaikan tipe mystakes):

trait Future {
    fn await!(self) -> Self::Item {
        await self
    }
}

AKA await this_foo dan this_foo.await!() sama persis. Apa yang mudah disalahpahami tentang itu?

Dan tentang topik pengguna baru: pengguna baru untuk apa? Pemrograman secara umum, atau pengguna baru Rust sebagai bahasa tetapi dengan latar belakang bahasa pemrograman? Karena jika yang pertama, maka saya ragu mereka akan mencoba-coba pemrograman async dengan benar. Dan jika yang terakhir, maka lebih mudah untuk menjelaskan semantik dari postfix menunggu (seperti yang dijelaskan di atas) tanpa kebingungan.

Atau, jika hanya awalan menunggu yang ditambahkan, tidak ada yang saya tahu akan menghentikan pembuatan program yang mengambil input kode bentuk Rust

foo.bar().baz().quux().await!().melo().await!()

dan mengubahnya menjadi

await (await foo.bar().baz().quux()).melo()

@ivardi

.await!() bagus untuk beberapa kasus, dan mungkin akan bekerja bersama await!(...) seperti:

macro_rules! await {
    // prefix
    ($fut:expr) => {...}

    // postfix
    ($self:Self) => { await!($self) }
}

Namun, makro metode postfix tidak ada sekarang, dan mungkin tidak pernah ada.

Jika kita berpikir itu adalah kemungkinan di masa depan, kita harus menggunakan makro await!(...) untuk saat ini dan cukup tambahkan postfix di masa mendatang saat itu diterapkan.

Memiliki kedua makro akan ideal, tetapi jika tidak ada niat untuk menerapkan makro postfix di masa mendatang, kata kunci awalan await mungkin adalah pilihan terbaik.

@novacrazy Saya setuju dengan itu dan itu adalah proposal asli saya. Kita harus menambahkan await!() untuk saat ini dan mencari bentuk postfix saat kita pergi. Juga diskusikan secara potensial kemungkinan makro postfix, dan jika kita dapat menambahkan .await!() ke dalam bahasa ad-hoc sebelum memiliki dukungan makro postfix penuh dalam bahasa tersebut. Mirip dengan apa yang terjadi dengan ? dan sifat Try : itu ditambahkan pertama kali sebagai kasus khusus dan setelah itu diperluas menjadi kasus yang lebih umum. Satu-satunya hal yang perlu kita waspadai saat memutuskan adalah bagaimana sintaks makro umum postfix akan terlihat, yang mungkin perlu dibahas secara terpisah.

Prefix keyword await atau awalan makro menunggu tampaknya cara yang paling dapat diterima untuk melakukan ini, yang masuk akal karena banyak bahasa lain melakukannya dengan cara itu.

Ini jelas _workable_, tapi saya hanya ingat dua argumen untuk itu, dan saya tidak menganggapnya persuasif:

  • Begitulah cara bahasa lain melakukannya

    • Itu bagus, tapi kami sudah melakukan hal-hal yang berbeda seperti tidak-berjalan-kecuali- poll ed alih-alih menjalankan-ke-yang-pertama- await , karena karat pada dasarnya berbeda bahasa

  • Orang-orang suka melihat await di awal baris

    • Tapi itu tidak selalu ada, jadi jika itu satu-satunya tempat yang terlihat, seseorang akan mendapat masalah

    • Sangat mudah untuk melihat await s di foo(aFuture.await, bFuture.await) seolah-olah itu adalah awalan

    • Dalam keadaan karat, seseorang sudah memindai _end_ baris untuk ? jika Anda akan melihat aliran kontrol

Apakah saya melewatkan sesuatu?

Jika debatnya adalah "meh, mereka semua hampir sama", saya sangat setuju dengan "baik, jika kita tidak terlalu peduli kita mungkin juga melakukan apa yang orang lain lakukan". Tapi saya rasa kita tidak berada di sana.

@scottmcm Ya untuk, "meh, semuanya hampir sama." Setidaknya dari sudut pandang pengguna.

Jadi kita harus menemukan keseimbangan yang layak antara keterbacaan, keakraban, pemeliharaan, dan kinerja. Oleh karena itu, pernyataan saya di komentar terakhir saya benar, dengan makro await!() jika kita bermaksud menambahkan makro metode postfix ( .await!() ) di masa mendatang, atau hanya kata kunci awalan yang membosankan await sebaliknya.

Saya bilang membosankan, karena membosankan itu bagus. Kami ingin menjauhkan pikiran kami dari sintaks itu sendiri saat menulis kode dengan ini.

Jika f.await() bukanlah ide yang bagus, maka saya lebih memilih sintaks prefiks.

  1. Sebagai pengguna, saya berharap bahasa yang saya gunakan hanya memiliki sedikit aturan sintaks, dan dengan menggunakan aturan ini, saya dapat menyimpulkan secara andal apa yang mungkin dilakukannya. BUKAN pengecualian di sana-sini. Di Rust, async di awal, await di awal tidak akan menjadi pengecualian. Namun, f await , bentuk kata kunci posting akan menjadi. f.await tampak seperti akses bidang, pengecualian . f.await!() memiliki makro postfix yang tidak pernah muncul dalam bahasa tersebut, dan tidak tahu kasus apa lagi yang bagus untuk itu, sebuah pengecualian . Kami tidak memiliki jawaban bagaimana sintaks ini akan menjadi aturan, bukan pengecualian satu kali .

  2. Saat pengecualian dibuat, saya harap ini masuk akal secara intuitif. Ambil ? sebagai contoh, yang dapat dilihat sebagai pengecualian karena tidak sering digunakan dalam bahasa lain. f()?.map() dibaca hampir seperti compute f () dan apakah hasil ini bagus? ? sini menjelaskan dirinya sendiri. Tapi untuk f await saya tanya kenapa ini postfix ?, untuk f.await saya tanyakan adalah await sebuah field, f.await!() Saya bertanya kenapa makro muncul di posisi itu? Mereka tidak memberikan pengertian yang meyakinkan / intuitif bagi saya, setidaknya pada pandangan pertama.

  3. Memperluas poin pertama, Rust paling ingin menjadi bahasa sistem. Pemain utama di sini, C / C ++ / Go / Java semuanya agak penting. Saya juga percaya kebanyakan orang memulai karir mereka dengan bahasa imperatif, C / Python / Java bukan Haskell, dll. Saya kira untuk membujuk pengembang sistem dan pengembang generasi berikutnya untuk mengadopsi Rust, Rust pertama-tama harus melakukannya dengan baik dalam gaya wajib dan kemudian berfungsi, bukan menjadi sangat fungsional tetapi tidak memiliki perasaan imperatif yang familiar.

  4. Menurut saya tidak ada salahnya membagi rantai dan menuliskannya menjadi beberapa baris. Ini tidak akan bertele-tele. Itu hanya eksplisit .

Ketika melihat bagaimana diskusi terus-menerus berpindah dari satu arah ke arah lain (awalan vs postfix) saya mengembangkan pendapat yang kuat bahwa ada yang salah dengan kata kunci await dan kita harus mundur sepenuhnya. Sebagai gantinya saya mengusulkan sintaks berikut yang menurut saya akan menjadi kompromi yang sangat baik di antara semua pendapat yang diterbitkan di utas saat ini:

// syntax below is exactly the same as with prefix `await`
let response = go client.get("https://my_api").send();
let body: MyResponse = go response.into_json();

Pada langkah pertama kami akan menerapkannya seperti operator awalan biasa tanpa kesalahan penanganan superstruktur tertentu:

// code below don't compiles because `?` takes precedence over `go`
let response = go client.get("https://my_api").send()?;
let body: MyResponse = go response.into_json()?;

Pada langkah kedua kami akan mengimplementasikan sintaks operator prefiks yang ditangguhkan yang akan memungkinkan penanganan kesalahan yang tepat juga.

// now `go` takes precedence over `?` if present
let response = client.get("https://my_api").go send()?;
let body: MyResponse = response.go into_json()?;

Itu saja.


Sekarang mari kita lihat beberapa contoh tambahan yang memberikan lebih banyak konteks:


// A
if db.go is_trusted_identity(recipient.clone(), message.key.clone())? {
    info!("recipient: {}", recipient);
}

// B
match db.go load(message.key)? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = client.get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .go send()?
    .error_for_status()?;

// D
let mut res: InboxResponse = client.get(inbox_url)
    .headers(inbox_headers)
    .go send()?
    .error_for_status()?
    .go json()?;

// E
let mut res: Response = client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .go send()?
    .error_for_status()?
    .go json()?;

// F
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.go request(url, Method::GET, None, true)?
        .res.go json::<UserResponse>()?
        .user
        .into();

    Ok(user)
}

// G
async fn log_service(&self) -> T {
   let service = self.myService.foo();
   go self.logger.log("beginning service call");
   let output = go service.exec();
   go self.logger.log("foo executed with result {}.", output));
   output
}

// H
async fn try_log(message: String) -> Result<usize, Error> {
    let logger = go acquire_lock();
    let length = logger.go log_into(message)?;
    go logger.timestamp();
    Ok(length)
}

// I
async fn await_chain() -> Result<usize, Error> {
    go (go partial_computation()).unwrap_or_else(or_recover);
}

/// J
let res = client.get("https://my_api").go send()?.go json()?;

Ada tersembunyi di bawah versi spoiler dengan penyorotan sintaks diaktifkan


// A
if db.as is_trusted_identity(recipient.clone(), message.key.clone())? {
    info!("recipient: {}", recipient);
}

// B
match db.as load(message.key)? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = client.get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .as send()?
    .error_for_status()?;

// D
let mut res: InboxResponse = client.get(inbox_url)
    .headers(inbox_headers)
    .as send()?
    .error_for_status()?
    .as json()?;

// E
let mut res: Response = client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .as send()?
    .error_for_status()?
    .as json()?;

// F
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.as request(url, Method::GET, None, true)?
        .res.as json::<UserResponse>()?
        .user
        .into();

    Ok(user)
}

// G
async fn log_service(&self) -> T {
   let service = self.myService.foo();
   as self.logger.log("beginning service call");
   let output = as service.exec();
   as self.logger.log("foo executed with result {}.", output));
   output
}

// H
async fn try_log(message: String) -> Result<usize, Error> {
    let logger = as acquire_lock();
    let length = logger.as log_into(message)?;
    as logger.timestamp();
    Ok(length)
}

// I
async fn await_chain() -> Result<usize, Error> {
    as (as partial_computation()).unwrap_or_else(or_recover);
}

/// J
let res = client.get("https://my_api").as send()?.as json()?;


IMO sintaks ini lebih baik daripada alternatif dalam setiap aspek:

✓ Konsistensi: terlihat sangat organik di dalam kode Rust dan tidak memerlukan gaya kode melanggar
✓ Komposabilitas: terintegrasi dengan baik dengan fitur Rust lainnya seperti rantai dan penanganan kesalahan
✓ Kesederhanaan: singkat, deskriptif, mudah dimengerti, dan mudah digunakan
✓ Dapat digunakan kembali: sintaks operator prefiks yang ditangguhkan akan berguna dalam konteks lain juga
✓ Dokumentasi: menyiratkan bahwa kiriman sisi panggilan mengalir ke suatu tempat dan menunggu sampai kembali
✓ Keakraban: memberikan pola yang sudah dikenal tetapi melakukannya dengan lebih sedikit pengorbanan
✓ Keterbacaan: dibaca sebagai bahasa Inggris biasa dan tidak mengubah arti kata
✓ Visibilitas: posisinya membuatnya sangat sulit untuk disamarkan di suatu tempat dalam kode
✓ Reachability: bisa dengan mudah dicari di Google
✓ Teruji dengan baik: golang populer saat ini dengan sintaks yang mirip
✓ Kejutan: sulit untuk disalahpahami serta menyalahgunakan sintaksis itu
✓ Gratifikasi: setelah sedikit belajar, setiap pengguna akan puas dengan hasilnya


Edit: seperti yang ditunjukkan @ivandardi pada komentar di bawah, beberapa hal harus diperjelas:

1. Ya, sintaks ini agak sulit untuk dipahami, tetapi pada saat yang sama tidak mungkin menemukan sintaksis di sini yang tidak akan memiliki masalah keterbacaan. go sintaks bagi saya tampaknya kurang jahat di sini, karena pada posisi awalan persis sama dengan prefiks await , dan pada posisi ditangguhkan IMO lebih mudah dibaca daripada postfix await misalnya:

match db.go load(message.key) await {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

dimana bukan await terlihat ambigu dan memiliki asosiasi yang berbeda dari semua kata kunci yang ada, tetapi juga bisa menjadi pemblokir jika kita memutuskan untuk menerapkan await blok di masa mendatang.

2. Ini akan membutuhkan pembuatan RFC, implementasi dan stabilisasi sintaks "operator prefiks yang ditangguhkan". Sintaks ini tidak spesifik untuk kode asinkron, namun menggunakannya di asinkron akan menjadi motivasi utama untuk itu.

3. Dalam "operator awalan yang ditangguhkan", kata kunci sintaks penting karena:

  • Kata kunci yang panjang akan menambah jarak antara variabel dan metode yang buruk untuk keterbacaan
  • kata kunci panjang adalah pilihan aneh untuk operator awalan
  • kata kunci yang panjang tidak perlu bertele-tele saat kita ingin kode asinkron terlihat sebagai sinkronisasi

Bagaimanapun, sangat mungkin untuk mulai menggunakan await daripada go dan kemudian mengganti namanya pada edisi 2022. Untuk saat itu kemungkinan besar akan ditemukan kata kunci atau operator yang berbeda. Dan kami bahkan dapat memutuskan bahwa tidak ada penggantian nama sama sekali. Saya telah menemukan await dapat dibaca.

Ada yang tersembunyi di bawah versi spoiler dengan await digunakan sebagai pengganti go


// A
if db.await is_trusted_identity(recipient.clone(), message.key.clone())? {
    info!("recipient: {}", recipient);
}

// B
match db.await load(message.key)? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = client.get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .await send()?
    .error_for_status()?;

// D
let mut res: InboxResponse = client.get(inbox_url)
    .headers(inbox_headers)
    .await send()?
    .error_for_status()?
    .await json()?;

// E
let mut res: Response = client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .await send()?
    .error_for_status()?
    .await json()?;

// F
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.await request(url, Method::GET, None, true)?
        .res.await json::<UserResponse>()?
        .user
        .into();

    Ok(user)
}

// G
async fn log_service(&self) -> T {
   let service = self.myService.foo();
   await self.logger.log("beginning service call");
   let output = await service.exec();
   await self.logger.log("foo executed with result {}.", output));
   output
}

// H
async fn try_log(message: String) -> Result<usize, Error> {
    let logger = await acquire_lock();
    let length = logger.await log_into(message)?;
    await logger.timestamp();
    Ok(length)
}

// I
async fn await_chain() -> Result<usize, Error> {
    await (as partial_computation()).unwrap_or_else(or_recover);
}

/// J
let res = client.get("https://my_api").await send()?.await json()?;


Jika Anda di sini untuk memberi suara negatif, pastikan:

  • bahwa Anda memahaminya dengan benar, karena saya mungkin bukan pembicara terbaik untuk menjelaskan hal-hal baru
  • bahwa pendapat Anda tidak bias, karena ada banyak prasangka dari mana sintaks itu berasal
  • bahwa Anda akan mencantumkan alasan yang valid di bawah ini, karena suara negatif diam sangat beracun
  • bahwa alasan Anda bukanlah tentang perasaan langsung, karena saya telah mencoba merancang fitur jangka panjang di sini

@ I60

Saya memiliki beberapa keluhan dengan itu.

match db.go load(message.key)? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

Itu agak sulit untuk diuraikan. Sekilas Anda akan mengira kami akan mencocokkan db.go , tetapi kemudian ada sesuatu yang lebih dari itu dan Anda berkata "oooh, ok, memuat adalah metode db". IMO sintaks itu terutama tidak akan berfungsi karena metode harus selalu dekat dengan objek miliknya, tanpa spasi dan interupsi kata kunci di antara mereka.

Kedua, proposal Anda memerlukan definisi "sintaks operator prefiks yang ditangguhkan" dan mungkin menggeneralisasikannya dalam bahasa terlebih dahulu. Jika tidak, itu akan menjadi sesuatu yang hanya digunakan untuk kode asinkron, dan kita kembali ke diskusi tentang "mengapa bukan makro postfix?" terlalu.

Ketiga, menurut saya kata kunci sama sekali tidak penting. Namun, kami sebaiknya memilih await karena kata kunci telah dipesan untuk edisi 2018, sedangkan kata kunci go belum dan harus menjalani pencarian lebih dalam crates.io sebelum diusulkan.

Ketika melihat bagaimana diskusi terus bergerak dari satu arah ke arah lain (awalan vs postfix) saya mengembangkan pendapat yang kuat bahwa ada sesuatu yang salah dengan kata kunci await dan kita harus mundur sepenuhnya.

Kami dapat memiliki (semi-) implisit menunggu dan semuanya akan baik-baik saja, tetapi ini akan menjadi penjualan yang sulit di komunitas Rust, meskipun fakta bahwa inti dari fungsi asinkron adalah untuk menyembunyikan detail implementasi dan membuatnya terlihat adil seperti memblokir io one.

@ dpc Maksud saya, cara terbaik yang dapat saya pikirkan yang berfungsi sebagai kompromi antara menunggu semi-implisit dan keinginan untuk memiliki bagian kode yang secara eksplisit ditunggu adalah sesuatu yang mirip dengan unsafe .

let mut res: Response = await { client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .send()?
    .error_for_status()?
    .json()?
};

Dimana semua yang ada di dalam blok await akan otomatis ditunggu. Itu menyelesaikan kebutuhan postfix menunggu untuk dirantai, karena Anda bisa menunggu keseluruhan rantai sebagai gantinya. Tetapi pada saat yang sama, saya tidak yakin apakah bentuk itu diinginkan. Mungkin orang lain dapat menemukan alasan yang sangat bagus untuk menentang ini.

@dpc Tujuan dari fungsi async bukanlah untuk menyembunyikan detailnya, ini untuk membuatnya lebih mudah didekati dan digunakan. Kita tidak boleh menyembunyikan operasi yang berpotensi mahal, dan harus memanfaatkan masa depan eksplisit jika memungkinkan.

Tidak ada yang bingung antara fungsi asinkron dengan fungsi sinkron, dalam bahasa apa pun. Ini murni untuk mengatur urutan operasi dalam bentuk yang lebih mudah dibaca dan dipelihara, daripada menulis tangan mesin negara.

Juga, implisit menunggu seperti yang ditunjukkan dalam contoh @ivandardi akan membuat tidak mungkin menggunakan Future combinators, yang seperti yang telah saya tunjukkan masih sangat kuat. Kita tidak bisa begitu saja mengambil Future dan menunggu, itu akan sangat tidak efisien.

Eksplisit, familiar, dapat dibaca, dipelihara, dan performant: kata kunci awalan await atau makro await!() , dengan kemungkinan makro postfix .await!() di masa mendatang.

@novacrazy Meskipun sintaks yang ideal telah dipilih, saya masih ingin menggunakan kontrak berjangka dan kombinator khusus dalam beberapa situasi. Gagasan bahwa itu bisa lembut tidak berlaku lagi membuat saya mempertanyakan seluruh arah Rust.

Tentu saja Anda dapat terus menggunakan kombinator, tidak ada yang ditinggalkan.

Ini situasi yang sama seperti ? : kode baru biasanya akan menggunakan ? (karena lebih baik daripada kombinator), tetapi kombinator Opsi / Hasil masih dapat digunakan (dan yang lebih funkier seperti or_else masih digunakan secara teratur).

Secara khusus, async / await dapat sepenuhnya menggantikan map dan and_then , tetapi kombinator Future yang lebih funk masih dapat (dan akan) digunakan.

Contoh yang Anda berikan masih terlihat buruk dibandingkan dengan kombinator,

Saya rasa tidak, saya rasa mereka jauh lebih jelas, karena mereka menggunakan fitur Rust standar.

Kejelasan jarang tentang jumlah karakter, tetapi ada hubungannya dengan konsep mental.

Dengan postfix await ini terlihat lebih baik:

some_op().await?
    .another_op().await?
    .final_op().await

Anda dapat membandingkannya dengan aslinya:

some_op()
    .and_then(|v| v.another_op())
    .and_then(|v2| v2.final_op())

dan dengan overhead generator mungkin akan sedikit lebih lambat dan menghasilkan lebih banyak kode mesin.

Mengapa Anda mengklaim bahwa akan ada overhead tambahan dari async / await? Baik generator dan async / await dirancang khusus untuk menjadi tanpa biaya dan sangat dioptimalkan.

Secara khusus, async / await dikompilasi menjadi mesin status yang sangat dioptimalkan, sama seperti kombinator Future.

daripada membuat mesin negara spaghetti generator ini di bawah kap mesin.

Para kombinator Future juga menciptakan "mesin spaghetti state" di bawah kap mesin, yang memang dirancang untuk itu.

Mereka secara fundamental tidak berbeda dari async / await.

[Penggabung masa depan] memiliki potensi untuk menyusun kode yang lebih efisien.

Tolong berhenti menyebarkan informasi yang salah. Secara khusus, async / await memiliki kemungkinan untuk lebih cepat daripada kombinator Future.

Jika ternyata pengguna tidak senang dengan kata kunci awalan, itu bisa diubah pada edisi 2019/2020.

Seperti yang dikatakan orang lain, edisi bukanlah hal yang sembarangan yang kita buat kapan saja kita mau, mereka dirancang khusus untuk hanya terjadi setiap beberapa tahun (paling awal).

Dan seperti yang ditunjukkan oleh @Centril , membuat sintaks yang buruk hanya untuk menggantinya nanti adalah cara yang sangat tidak efisien dalam melakukan sesuatu.

Laju diskusi saat ini cukup tinggi; jadi dalam upaya memperlambat segalanya dan memungkinkan orang lain mengetahuinya, saya telah mengunci masalah untuk sementara . Ini akan dibuka dalam satu hari. Pada saat itu, mohon untuk bergerak sekonstruktif mungkin.

Membuka kunci masalah sehari kemudian ... Harap pertimbangkan untuk tidak membuat poin yang sudah dibuat dan menyimpan komentar pada topik (misalnya ini bukan tempat untuk mempertimbangkan menunggu tersirat atau hal-hal lain di luar ruang lingkup ..).

Karena ini adalah utas yang panjang, mari kita soroti beberapa komentar paling menarik yang terkubur di dalamnya.

@mehcode memberi kami kode dunia nyata yang luas dengan await di posisi awalan dan posfiks: https://github.com/rust-lang/rust/issues/57640#issuecomment -455846086

@Centril berargumen secara persuasif bahwa menstabilkan await!(expr) berarti menambahkan utang teknis secara sadar: https://github.com/rust-lang/rust/issues/57640#issuecomment -455806584

@valff mengingatkan kita bahwa sintaks kata kunci expr await postfix akan sangat cocok dengan Generalized Type Ascription : https://github.com/rust-lang/rust/issues/57640#issuecomment -456023146

@quodlibetor menyoroti bahwa efek sintaksis dari kombinator Error dan Option adalah unik untuk Rust dan mendukung sintaks postfix: https://github.com/rust-lang/rust/issues/57640#issuecomment -456143523

Pada akhirnya, tim lang perlu menelepon ke sini. Untuk mengidentifikasi kesamaan yang mungkin mengarah pada konsensus, mungkin akan membantu untuk meringkas posisi yang dinyatakan dari anggota tim lang pada isu utama sintaks postfix:

  • @Centril menyatakan dukungannya untuk sintaks postfix dan tidak ingin menstabilkan await!(expr) .

  • @cramertj menyatakan dukungannya untuk sintaks postfix dan khususnya untuk sintaks kata kunci postfix expr await .

  • @joshtriplett menyatakan dukungan untuk sintaks postfix dan menyarankan agar kami juga menyediakan versi prefiks.

  • @scottmcm menyatakan dukungan untuk sintaks postfix.

  • @withoutboats tidak ingin menstabilkan sintaks makro. Sementara khawatir tentang menghabiskan "anggaran asing" kami, tanpa perahu menganggap expr await sintaks kata kunci postfix memiliki "tembakan nyata".

  • @aturon , @eddyb , @nikomatsakis , dan @pnkfelix belum mengutarakan posisi apa pun terkait sintaks dalam edisi # 57640 atau # 50547.

Jadi, hal-hal yang perlu kita pikirkan adalah jawaban ya / tidak:

  • Haruskah kita memiliki sintaks prefiks?
  • Haruskah kita memiliki sintaks postfix?
  • Haruskah kita memiliki sintaks prefiks sekarang dan mencari tahu sintaks postfix nanti?

Jika kita menggunakan sintaks prefiks, ada dua pesaing utama yang menurut saya paling diterima orang: Berguna dan Jelas, seperti yang terlihat di komentar ini . Argumen melawan await!() cukup kuat, jadi saya mempertimbangkan itu mengesampingkan. Jadi kita perlu mencari tahu apakah kita menginginkan sintaks yang Berguna atau Jelas. Menurut pendapat saya, kita harus memilih sintaks mana yang menggunakan lebih sedikit tanda kurung secara umum.

Dan untuk sintaks postfix, itu lebih rumit. Ada juga argumen kuat untuk kata kunci postfix await dengan spasi. Tapi itu sangat tergantung pada bagaimana kode diformat. Jika kode berformat buruk, kata kunci postfix await dengan spasi dapat terlihat sangat buruk. Jadi pertama-tama, kita perlu berasumsi bahwa semua kode Rust akan diformat dengan benar dengan rustfmt, dan Rustfmt selalu memisahkan penantian yang dirantai menjadi baris yang berbeda, bahkan jika mereka muat dalam satu baris. Jika kita bisa melakukannya, maka sintaks itu sangat baik, karena ini memecahkan masalah keterbacaan dan kebingungan dengan spasi di tengah rantai garis tunggal.

Dan akhirnya, jika kita menggunakan prefiks dan postfix, kita perlu mencari tahu dan menuliskan semantik dari kedua sintaks untuk melihat bagaimana mereka berinteraksi satu sama lain dan bagaimana satu akan dikonversi ke yang lain. Seharusnya mungkin untuk melakukannya, karena memilih antara postfix atau prefix await seharusnya tidak mengubah cara kode dijalankan.

Untuk memperjelas proses pemikiran saya, dan bagaimana saya mendekati dan mengevaluasi proposal sintaks permukaan wrt. await ,
berikut adalah tujuan yang saya miliki (tanpa urutan kepentingan):

  1. await harus tetap menjadi kata kunci untuk memungkinkan desain bahasa di masa mendatang.
  2. Sintaksnya harus terasa kelas satu.
  3. Menunggu harus dapat dirantai untuk membuat dengan baik dengan ? dan metode secara umum karena mereka lazim di Rust. Seseorang tidak boleh dipaksa membuat ikatan let yang mungkin bukan subdivisi yang berarti.
  4. Harus mudah untuk grep untuk menunggu poin.
  5. Seharusnya mungkin untuk melihat menunggu poin secara sekilas.
  6. Prioritas sintaks harus intuitif.
  7. Sintaksnya harus tersusun dengan baik dengan IDE dan memori otot.
  8. Sintaksnya harus mudah dipelajari.
  9. Sintaksnya harus ergonomis untuk ditulis.

Setelah menentukan (dan mungkin lupa ...) beberapa tujuan saya, berikut adalah ringkasan dan evaluasi saya terhadap beberapa proposal terkait dengan itu:

  1. Mempertahankan await sebagai kata kunci menyulitkan penggunaan sintaks berbasis makro apakah itu await!(expr) atau expr.await!() . Untuk menggunakan sintaks makro, await sebagai makro menjadi hardcode dan tidak terintegrasi dengan resolusi nama (yaitu use core::await as foo; menjadi tidak mungkin), atau await dilepaskan sebagai kata kunci seluruhnya.

  2. Saya percaya bahwa makro memiliki perasaan yang jelas bukan kelas satu tentang mereka karena mereka dimaksudkan untuk menciptakan sintaksis di ruang pengguna. Meskipun ini bukan poin teknis, menggunakan sintaks makro dalam konstruksi sentral bahasa tersebut memberikan kesan yang tidak terpoles. Bisa dibilang, sintaks .await dan .await() juga bukan sintaks kelas satu; tetapi tidak sekelas yang dirasakan makro.

  3. Keinginan untuk memfasilitasi perangkaian membuat sintaks prefiks bekerja dengan buruk sedangkan sintaks postfix secara alami dibuat dengan rantai metode dan ? pada khususnya.

  4. Grepping paling mudah dilakukan ketika kata kunci await digunakan baik itu postfix, prefix, makro, dll. Ketika sintaks berbasis sigil digunakan, misalnya # , @ , ~ , menggenggam menjadi lebih sulit. Perbedaannya tidak besar, tapi .await sedikit lebih mudah untuk grep daripada await dan await karena salah satu dari mereka mungkin dimasukkan sebagai kata-kata dalam komentar sedangkan itu lebih tidak mungkin untuk .await .

  5. Sigil cenderung lebih sulit dilihat sekilas sedangkan await lebih mudah dilihat dan terutama sintaks yang disorot. Telah disarankan bahwa .await atau sintaks postfix lainnya lebih sulit untuk dilihat sekilas atau bahwa postfix await menawarkan keterbacaan yang buruk. Namun, dalam pandangan saya, @scottmcm dengan tepat mencatat bahwa masa depan hasil adalah hal biasa dan bahwa .await? membantu menarik perhatian ekstra pada dirinya sendiri. Selain itu, Scott mencatat bahwa awalan await mengarah ke kalimat jalur taman dan awalan yang menunggu di tengah ekspresi tidak lebih bisa dibaca daripada postfix. Dengan munculnya operator ? , programmer Rust sudah perlu memindai akhir baris untuk mencari aliran kontrol .

  6. Urutan dari .await dan .await() terkenal karena sepenuhnya dapat diprediksi; mereka bekerja sebagai akses lapangan dan rekan pemanggilan metode. Makro postfix mungkin memiliki prioritas yang sama dengan pemanggilan metode. Sementara itu, prefiks await memiliki prioritas yang konsisten, dapat diprediksi, dan tidak berguna dalam kaitannya dengan ? (yaitu await (expr?) ), atau prioritas tidak konsisten dan berguna (yaitu (await expr)? ). Sigil dapat diberikan prioritas yang diinginkan (misalnya, mengambil intuisinya dari ? ).

  7. Pada tahun 2012, @nikomatsakis mencatat bahwa Simon Peyton Jones pernah (hlm. 56) merasa iri dengan "kekuatan titik" dan bagaimana hal itu memberikan keajaiban IDE sehingga Anda dapat mempersempit fungsi atau bidang yang Anda maksud. Karena kekuatan titik ada di banyak bahasa populer (misalnya Java, C #, C ++, ..), hal ini menyebabkan "meraih titik" tertanam dalam memori otot. Untuk mengilustrasikan seberapa kuat kebiasaan ini, berikut tangkapan layar, karena @scottmcm , dari Visual Studio dengan Re # er:
    Re#er intellisense

    C # tidak menunggu postfix. Meskipun demikian, ini sangat berguna karena ditampilkan dalam daftar pelengkapan otomatis tanpa sintaks yang valid. Namun, mungkin tidak terpikir oleh banyak orang, termasuk saya sendiri, untuk mencoba .aw ketika .await bukan sintaks permukaan. Pertimbangkan manfaat pengalaman IDE jika memang demikian. Sintaks await expr dan expr await tidak menawarkan manfaat itu.

  8. Sigils kemungkinan besar tidak terlalu familiar. Awalan await memiliki keuntungan dari keakraban dalam C #, JS, dll. Namun, ini tidak memisahkan await dan ? ke dalam operasi yang berbeda sedangkan Rust melakukannya. Ini juga bukan peregangan untuk beralih dari await expr menjadi expr.await - perubahannya tidak seradikal menggunakan sigil. Selain itu, karena dot-power yang disebutkan di atas, kemungkinan .await akan dipelajari dengan mengetik expr. dan melihat await sebagai opsi pertama dalam munculan pelengkapan otomatis.

  9. Sigil mudah ditulis dan menawarkan ergonomi yang baik. Namun, meskipun .await lebih panjang untuk diketik, ia juga dilengkapi dengan kekuatan titik yang dapat membuat alur penulisan menjadi lebih baik. Tidak harus membobol pernyataan let juga memfasilitasi ergonomi yang lebih baik. Awalan await , atau lebih buruk lagi await!(..) , tidak memiliki kemampuan dot-power dan chaining. Saat membandingkan .await , .await() , dan .await!() , penawaran pertama menjadi yang paling singkat.

Karena tidak ada sintaks yang terbaik untuk mencapai semua tujuan secara bersamaan, trade-off harus dilakukan. Bagi saya, sintaks dengan manfaat paling banyak dan kekurangan paling sedikit adalah .await . Khususnya, ini dapat dirantai, mempertahankan await sebagai kata kunci, greppable, memiliki kekuatan titik dan karena itu dapat dipelajari dan ergonomis, memiliki prioritas yang jelas, dan akhirnya dapat dibaca (terutama dengan pemformatan dan penyorotan yang baik).

@Centril Hanya untuk menjernihkan beberapa pertanyaan. Pertama, saya ingin memastikan bahwa Anda hanya ingin postfix menunggu, bukan? Kedua, bagaimana mendekati dualitas .await sebagai akses lapangan? Apakah boleh memiliki .await seperti itu, atau haruskah menggunakan .await() dengan tanda kurung sehingga menyiratkan bahwa semacam operasi sedang terjadi di sana? Dan ketiga, dengan sintaks .await , apakah yang menunggu menjadi kata kunci atau hanya pengenal "akses bidang"?

Pertama, saya ingin memastikan bahwa Anda hanya ingin postfix menunggu, bukan?

Ya. Setidaknya sekarang.

Kedua, bagaimana mendekati dualitas .await sebagai akses lapangan?

Ini sedikit kekurangan; ada keuntungan besar dalam kekuatan titik. Perbedaannya adalah sesuatu yang saya yakin pengguna akan belajar dengan cepat, terutama karena kata kunci tersebut disorot dan konstruksinya akan sering digunakan. Selain itu, seperti yang dicatat Scott, .await? akan menjadi yang paling umum yang selanjutnya akan meringankan situasi. Seharusnya juga mudah untuk menambahkan entri kata kunci baru untuk await di rustdoc seperti yang telah kami lakukan untuk mengatakan fn .

Apakah boleh memiliki .await seperti itu, atau haruskah menggunakan .await() dengan tanda kurung sehingga menyiratkan bahwa semacam operasi sedang terjadi di sana?

Saya lebih suka .await tanpa ekor () ; memiliki () pada akhirnya tampaknya seperti garam yang tidak perlu di sebagian besar kasus dan akan, saya duga, membuat pengguna lebih cenderung mencari metode tersebut.

Dan ketiga, dengan sintaks .await , apakah yang menunggu menjadi kata kunci atau hanya pengenal "akses bidang"?

await akan tetap menjadi kata kunci. Agaknya Anda akan mengubah libsyntax sedemikian rupa agar tidak terjadi kesalahan saat menemukan await setelah . dan kemudian merepresentasikannya secara berbeda baik dalam AST atau saat menurunkan ke HIR ... tetapi itu sebagian besar merupakan detail implementasi .

Terima kasih telah menyelesaikan semua itu! 👍

Pada titik ini, masalah utamanya adalah apakah akan mengejar sintaks postfix. Kalau itu arahnya, maka pasti kita bisa mempersempit ke arah mana. Saat membaca ruangan, sebagian besar pendukung sintaks postfix akan menerima sintaks postfix yang masuk akal di atas sintaks prefiks.

Masuk ke utas ini, sintaks postfix sepertinya tidak diunggulkan. Namun, hal ini telah menarik dukungan yang jelas dari empat anggota tim dan keterbukaan dari yang kelima (empat lainnya diam sejauh ini). Plus, itu telah menarik banyak dukungan dari komunitas yang lebih luas untuk berkomentar di sini.

Selain itu, para pendukung sintaks postfix tampaknya memiliki keyakinan yang jelas bahwa ini adalah jalan terbaik untuk Rust. Tampaknya diperlukan beberapa masalah yang mendalam atau sanggahan yang meyakinkan terhadap argumen-argumen yang telah diajukan sejauh ini untuk menarik dukungan ini.

Mengingat hal ini, sepertinya kami perlu mendengar dari @withoutboats , yang pasti telah mengikuti utas ini dengan cermat, dan dari empat anggota tim bahasa lainnya. Pandangan mereka kemungkinan akan mendorong utas ini. Jika mereka memiliki kekhawatiran, membahasnya akan menjadi prioritas. Jika mereka yakin kasus sintaks postfix, maka kita dapat melanjutkan untuk menemukan konsensus yang mana.

Ups, baru perhatikan @Centril sudah berkomentar, jadi saya akan menyembunyikan posting saya untuk kebersihan.

@ivandardi Diberikan tujuan (1) dari posting, pemahaman saya adalah bahwa menunggu akan tetap menjadi kata kunci, jadi tidak akan pernah menjadi akses lapangan, dengan cara yang sama bahwa loop {} tidak pernah merupakan ekspresi literal struct. Dan saya berharap itu akan disorot untuk membuatnya lebih jelas (seperti https://github.com/rust-lang/rust-enhanced/issues/333 menunggu untuk dilakukan di Sublime).

Satu kekhawatiran saya adalah bahwa membuat sintaks menunggu terlihat seperti akses yang berbeda berpotensi menyebabkan kebingungan ketika dalam basis kode edisi 2015 tanpa disadari. (2015 adalah default ketika tidak ditentukan, membuka proyek orang lain, dll.) Selama edisi 2015 memiliki kesalahan yang jelas ketika tidak ada bidang await (dan peringatan jika ada), saya pikir bahwa (bersama dengan argumen indah Centril) menghilangkan kekhawatiran pribadi saya tentang bidang kata kunci (atau metode).

Oh, dan satu hal yang juga harus kita putuskan saat kita melakukannya adalah bagaimana rustfmt akan memformatnya.

let val = await future;
let val = await returns_future();
let res = client.get("https://my_api").await send()?.await json()?;
  1. Menggunakan await - cek
  2. Kelas satu - periksa
  3. Rantai - periksa
  4. Grepping - periksa
  5. Non sigil - cek
  6. Diutamakan - ¹ periksa
  7. Dot power - ²check
  8. Mudah dipelajari - ³ periksa
  9. Ergonomis - periksa

¹Precedence: spasi setelah await membuatnya tidak jelas dalam posisi ditangguhkan. Namun itu persis sama dengan kode berikut: client.get("https://my_api").await_send()?.await_json()? . Untuk semua penutur bahasa Inggris, ini bahkan lebih alami dibandingkan dengan semua proposal lainnya

²Dot power: akan membutuhkan dukungan tambahan untuk IDE untuk memindahkan await ke kiri pemanggilan metode setelah . , ? atau ; diketik berikutnya. Sepertinya tidak terlalu sulit untuk diterapkan

³Mudah dipelajari: pada posisi awalan sudah familiar bagi programmer. Dalam posisi deffered akan terlihat jelas setelah sintaks itu distabilkan


Tetapi poin terkuat tentang sintaks ini adalah itu akan sangat konsisten:

  • Tidak bisa disamakan dengan akses properti
  • Ini memiliki prioritas yang jelas dengan ?
  • Itu akan selalu memiliki format yang sama
  • Itu cocok dengan bahasa manusia
  • Itu tidak terpengaruh dengan kejadian horizontal variabel dalam rantai panggilan metode *

* Penjelasan tersembunyi di bawah spoiler


Dengan varian postfix, sulit untuk memprediksi fungsi mana yang async dan di mana kemunculan berikutnya dari await pada sumbu horizontal:

let res = client.get("https://my_api")
    .very_long_method_name(param, param, param).await?
    .short().await?;

Dengan varian yang ditangguhkan akan hampir selalu sama:

let res = client.get("https://my_api")
    .await very_long_method_name(param, param, param)?
    .await short()?;

Bukankah akan ada. antara menunggu dan pemanggilan metode selanjutnya? Suka
get().await?.json() .

Pada Rabu, 23 Jan 2019, 05:06 I60R < [email protected] menulis:

biarkan val = menunggu masa depan;
let res = client.get ("https: // my_api") .await send () ?. await json () ?;

  1. Penggunaan menunggu - periksa
  2. Kelas satu - periksa
  3. Rantai - periksa
  4. Grepping - periksa
  5. Non sigil - cek
  6. Diutamakan - ¹ periksa
  7. Dot power - ²check
  8. Mudah dipelajari - ³ periksa
  9. Ergonomis - periksa

¹Precedence: spasi membuatnya tidak jelas dalam posisi ditangguhkan. Bagaimanapun itu
persis sama seperti pada kode berikut: client.get ("https: // my_api
") .await_send () ?. await_json () ?. Untuk semua penutur bahasa Inggris, ini bahkan lebih
alami dibandingkan dengan semua proposal lainnya

²Dot power: dibutuhkan dukungan tambahan untuk memindahkan IDE
di sebelah kiri pemanggilan metode setelahnya. atau ? diketik berikutnya. Sepertinya tidak terlalu
sulit diterapkan

³Mudah dipelajari: dalam posisi awalan sudah familiar bagi programmer,
dan dalam posisi deffered akan terlihat jelas setelah sintaks itu

stabil

Tetapi poin terkuat tentang sintaks ini adalah itu akan sangat
konsisten:

  • Tidak bisa disamakan dengan akses properti
  • Ini jelas diutamakan dengan?
  • Itu akan selalu memiliki format yang sama
  • Itu cocok dengan bahasa manusia
  • Itu tidak terpengaruh dengan kejadian horizontal variabel dalam pemanggilan metode
    rantai*

* Penjelasan tersembunyi di bawah spoiler

Dengan varian postfix, sulit untuk memprediksi fungsi mana yang async dan
di mana kejadian menunggu berikutnya pada sumbu horizontal:

biarkan res = client.get ("https: // my_api")

.very_long_method_name(param, param, param).await?

.short().await?;

Dengan varian yang ditangguhkan akan hampir selalu sama:

biarkan res = client.get ("https: // my_api")

.await very_long_method_name(param, param, param)?

.await short()?;

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/57640#issuecomment-456693759 ,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/AIA8Ogyjc8LW6teZnXyzOCo31-0GPohTks5vGAoDgaJpZM4aBlba
.

kita juga harus memutuskan sementara kita berada di sana adalah bagaimana rustfmt akhirnya akan memformatnya

Itu adalah domain https://github.com/rust-dev-tools/fmt-rfcs , jadi menurut saya ini di luar topik untuk masalah ini. Saya yakin akan ada sesuatu yang konsisten dengan ketetapan dan prioritas apa pun yang dipilih.

@ I60

let res = client.get("https://my_api").await send()?.await json()?;

Bagaimana itu akan mencari fungsi fungsi gratis? Jika saya membacanya dengan benar, sebenarnya itu adalah awalan await .

Saya pikir ini menunjukkan bahwa kita seharusnya tidak terlalu cepat mendiskreditkan pengalaman tim bahasa C #. Microsoft banyak memikirkan studi kegunaan, dan kami membuat keputusan di sini berdasarkan satu baris kode dan premis bahwa Rust cukup istimewa untuk memiliki sintaks yang berbeda dari semua bahasa lainnya. Saya tidak setuju dengan premis itu - saya sebutkan di atas LINQ dan metode ekstensi yang meresap di C #.

Namun, mungkin tidak terpikir oleh banyak orang, termasuk saya sendiri, untuk mencoba .aw ketika .await bukan sintaks permukaan.

~ Saya tidak berpikir itu akan terjadi pada siapa pun yang akrab dengan bahasa lain. ~ Selain itu, apakah Anda mengharapkan pop-up penyelesaian untuk menyertakan metode await dan Future ?

[...] dan kami membuat keputusan di sini berdasarkan satu baris kode dan premis bahwa Rust cukup spesial untuk memiliki sintaks yang berbeda dari semua bahasa lainnya. Saya tidak setuju dengan premis itu [...]

@lnicola Saya hanya ingin menunjukkan jika Anda dan orang lain melewatkannya (mudah terlewatkan dalam banjir komentar): https://github.com/rust-lang/rust/issues/57640#issuecomment -455846086

Beberapa contoh nyata dari kode produksi aktual.

Diberikan komentar @Centril di atas , tampaknya pilihan postfix yang paling kuat adalah expr await sintaks kata kunci postfix dan sintaks field postfix expr.await (dan mungkin sintaks metode postfix belum keluar).

Dibandingkan dengan kata kunci postfix, @Centril berpendapat bahwa

Sebaliknya, @cramertj telah berpendapat mendukung postfix kata kunci sintaks atas dasar bahwa sintaks seperti membuat jelas bahwa itu bukan metode panggilan atau akses lapangan. @withoutboats berpendapat bahwa kata kunci postfix adalah opsi postfix yang paling familiar.

Mengenai "kekuatan titik", kita harus mempertimbangkan bahwa IDE masih dapat menawarkan await sebagai penyelesaian setelah titik dan kemudian cukup menghapus titik tersebut saat penyelesaian dipilih. IDE yang cukup cerdas (misalnya dengan RLS) bahkan dapat memperhatikan Masa Depan dan menawarkan await setelah spasi.

Field postfix dan opsi metode tampaknya menawarkan area permukaan yang lebih besar untuk keberatan, karena ambiguitasnya, dibandingkan dengan kata kunci postfix. Karena beberapa dukungan untuk field / metode postfix atas kata kunci postfix tampaknya berasal dari sedikit ketidaknyamanan dengan adanya spasi dalam rangkaian metode, kita harus meninjau dan terlibat dengan pengamatan @valff bahwa kata kunci postfix ( foo.bar() await ) tidak akan terlihat lebih mengejutkan daripada tipe ascription umum ( foo.bar() : Result<Vec<_>, _> ). Untuk keduanya, perangkaian berlanjut setelah selingan yang dipisahkan spasi ini.

@ivardiardi , @nicola ,

Saya telah memperbarui komentar terbaru saya dengan contoh yang hilang untuk panggilan fungsi gratis. Jika Anda ingin lebih banyak contoh, Anda dapat merujuk ke spoiler terakhir di komentar saya sebelumnya

Untuk kepentingan debat future.await? vs. future await? ...

Sesuatu yang tidak terlalu banyak dibahas adalah pengelompokan visual atau penggabungan rantai metode.

pertimbangkan ( match alih-alih await untuk penyorotan sintaks)

post(url).multipart(form).send().match?.error_for_status()?.json().match?

jika dibandingkan dengan

post(url).multipart(form).send() match?.error_for_status()?.json() match?

Ketika saya secara visual memindai rantai pertama untuk await saya dengan jelas dan cepat mengidentifikasi send().await? dan melihat bahwa kami sedang menunggu hasil dari send() .

Namun, ketika saya memindai rantai kedua secara visual untuk await , saya pertama kali melihat await?.error_for_status() dan harus pergi, tidak, mencadangkan, dan kemudian menghubungkan send() await bersama-sama.


Ya, beberapa dari argumen yang sama berlaku untuk Generalized Type Ascription, tetapi itu belum menjadi fitur yang diterima. Sementara saya suka jenis ascription dalam arti, saya merasa bahwa dalam konteks ekspresi _general_ (tidak jelas, saya tahu) itu harus dibungkus dalam tanda kurung. Itu semua di luar topik untuk diskusi ini. Juga perlu diingat bahwa jumlah await dalam basis kode memiliki perubahan yang tinggi secara signifikan lebih tinggi daripada jumlah jenis ascription.

Uraian Jenis Umum juga mengikat jenis ke ungkapan yang dianggap berasal dari penggunaan operator berbeda : . Sama seperti operator biner lainnya, membacanya memperjelas (secara visual) bahwa kedua ekspresi tersebut adalah satu pohon. Sedangkan future await tidak memiliki operator dan dapat dengan mudah disalahartikan sebagai future_await luar kebiasaan jarang melihat dua nama yang tidak dipisahkan oleh operator kecuali jika yang pertama adalah kata kunci (pengecualian berlaku, bukan untuk mengatakan bahwa saya lebih suka sintaks awalan, saya tidak).

Bandingkan ini dengan bahasa Inggris jika Anda mau, di mana tanda hubung ( - ) digunakan untuk mengelompokkan kata-kata secara visual yang sebaliknya akan dengan mudah ditafsirkan sebagai terpisah.

Saya pikir ini menunjukkan bahwa kita seharusnya tidak terlalu cepat mendiskreditkan pengalaman tim bahasa C #.

Saya tidak berpikir "begitu cepat" dan "mendiskreditkan" itu adil di sini. Saya pikir hal yang sama terjadi di sini yang terjadi di async / menunggu RFC itu sendiri: mempertimbangkan dengan cermat mengapa hal itu dilakukan dengan satu cara, dan mencari tahu apakah keseimbangan keluar dengan cara yang sama untuk kami. Tim C # bahkan disebutkan secara eksplisit dalam salah satu:

Saya pikir gagasan await adalah Anda sebenarnya menginginkan hasilnya bukan di masa depan
jadi mungkin await sintaks harus lebih seperti sintaks 'ref'

let future = task()
tapi
let await result = task()

jadi untuk merangkai Anda harus melakukan keduanya

task().chained_method(|future| { /* do something with future */ })

tapi

task().chained_method(|await result| { /* I've got the result */ })
- foo.await             // NOT a real field
- foo.await()           // NOT a real method
- foo.await!()          // NOT a real macro

Mereka semua bekerja dengan baik dengan rangkaian, dan semua memiliki kekurangan yang bukan bidang / metode / makro nyata.
Tapi karena await , sebagai kata kunci, sudah menjadi hal yang spesial, kita tidak perlu membuatnya lebih spesial.
Kami hanya harus memilih yang paling sederhana, foo.await . Baik () dan !() berlebihan di sini.

@liigo

- foo await        // IS neither field/method/macro, 
                   // and clearly seen as awaited thing. May be easily chained. 
                   // Allow you to easily spot all async spots.

@tokopedia

Ketika saya secara visual memindai rantai pertama untuk menunggu, saya dengan jelas dan cepat mengidentifikasi send (). Await? dan lihat bahwa kita menunggu hasil send ().

Saya lebih suka versi spasi, karena jauh lebih mudah untuk melihat di mana masa depan dibangun, dan di mana ia ditunggu. Jauh lebih mudah untuk melihat apa yang terjadi, IMHO

image

Dengan pemisahan titik, ini terlihat sangat mirip dengan panggilan metode. Penyorotan kode tidak akan banyak membantu, dan tidak selalu tersedia.

Akhirnya, saya percaya chaining bukanlah kasus penggunaan utama (kecuali untuk ? , tapi foo await? sejelas mungkin), dan dengan satu await Itu menjadi

post(url).multipart(form).send().match?

vs.

post(url).multipart(form).send() match?

Dimana yang terakhir terlihat imo jauh lebih ramping.

Jadi, jika kita telah mempersempit menjadi future await dan future.await , dapatkah kita menjadikan "titik" ajaib sebagai opsional? Agar orang bisa memilih yang mana yang mereka inginkan, dan yang terpenting, hentikan pertengkaran dan maju terus!

Tidaklah berbahaya untuk memiliki sintaks opsional, Rust memiliki banyak contoh seperti itu. yang paling terkenal adalah sperator terakhir ( ; atau , atau apa pun, setelah item terakhir, seperti di (A,B,) ) sebagian besar opsional. Saya tidak melihat alasan kuat mengapa kami tidak dapat membuat titik tersebut opsional.

Saya pikir ini akan menjadi waktu yang tepat untuk melakukan ini di Nightly, dan biarkan ekosistem memutuskan mana yang terbaik untuk mereka. Kami dapat memiliki lint untuk menerapkan gaya yang disukai, membuatnya dapat disesuaikan.

Kemudian sebelum masuk ke Stable, kami meninjau penggunaan komunitas dan memutuskan apakah kami harus memilih satu atau meninggalkan keduanya.

Saya benar-benar tidak setuju dengan gagasan bahwa makro tidak akan terasa cukup kelas satu untuk fitur ini (saya tidak akan menganggapnya sebagai fitur inti di tempat pertama) dan ingin menyoroti kembali komentar saya await!() (tidak peduli apakah prefiks atau postfix) bukan "makro nyata", tapi saya rasa pada akhirnya terlalu banyak orang yang menginginkan fitur ini distabilkan secepatnya, daripada memblokir ini pada makro postfix dan awalan menunggu sebenarnya bukanlah cocok untuk Rust.

Bagaimanapun, mengunci utas ini selama sehari hanya sangat membantu dan saya akan berhenti berlangganan sekarang. Jangan ragu untuk menyebut saya jika Anda membalas langsung salah satu poin saya.

Argumen saya menentang sintaks dengan . adalah bahwa await bukanlah sebuah field dan oleh karena itu seharusnya tidak terlihat seperti sebuah field. Bahkan dengan penyorotan sintaks dan font tebal itu akan terlihat seperti bidang yang entah bagaimana . diikuti oleh kata sangat terkait dengan akses bidang.

Tidak ada alasan untuk menggunakan sintaks dengan . untuk integrasi IDE yang lebih baik, karena kita dapat memberdayakan sintaks yang berbeda dengan penyelesaian . dengan menggunakan fitur seperti

Kita perlu menganggap await sebagai kata kunci aliran kontrol. Jika kita sangat memutuskan untuk menggunakan postfix await , maka kita harus mempertimbangkan varian tanpa . dan saya mengusulkan untuk menggunakan format berikut bersama untuk lebih menekankan poin pemblokiran dengan kemungkinan gangguan:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.request(url, Method::GET, None, true)
        await?.res.json::<UserResponse>()
        await?.user
        .into();
    Ok(user)
}

Lebih banyak contoh

// A
if db.is_trusted_identity(recipient.clone(), message.key.clone()) 
    await? {
    info!("recipient: {}", recipient);
}

// B
match db.load(message.key)
    await? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = client.get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .send()
    await?.error_for_status()?;

// D
let mut res: InboxResponse = client.get(inbox_url)
    .headers(inbox_headers)
    .send()
    await?.error_for_status()?
    .json()
    await?;

// E
let mut res: Response = client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .send()
    await?.error_for_status()?
    .json()
    await?;

Dengan penyorotan sintaks

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.request(url, Method::GET, None, true)
        yield?.res.json::<UserResponse>()
        yield?.user
        .into();
    Ok(user)
}

// A
if db.is_trusted_identity(recipient.clone(), message.key.clone()) 
    yield? {
    info!("recipient: {}", recipient);
}

// B
match db.load(message.key)
    yield? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = client.get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .send()
    yield?.error_for_status()?;

// D
let mut res: InboxResponse = client.get(inbox_url)
    .headers(inbox_headers)
    .send()
    yield?.error_for_status()?
    .json()
    yield?;

// E
let mut res: Response = client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .send()
    yield?.error_for_status()?
    .json()
    yield?;


Ini juga bisa menjadi jawaban atas poin @ivandardi tentang pemformatan

Saya menemukan semua contoh untuk postfix ini menunggu untuk lebih menentangnya daripada itu. Rantai panjang menunggu terasa salah, dan rantai harus disimpan untuk kombinator masa depan.

Bandingkan (dengan match sebagai await hal untuk penyorotan sintaks):

post(url)?.multipart(form).send()?.match?.error_for_status()?.json().match?

untuk

let value = await post(url)?.multipart(form).send()?.error_for_status()
                 .and_then(|resp| resp.json()) // and then parse JSON

// now handle errors

Perhatikan kurangnya menunggu di tengah? Karena perangkaian biasanya dapat diselesaikan dengan desain API yang lebih baik. Jika send() mengembalikan masa depan kustom dengan metode tambahan, katakanlah, untuk mengonversi pesan status non-200 menjadi kesalahan berat, maka tidak perlu menunggu ekstra. Saat ini, setidaknya dalam reqwest , tampaknya berjalan di tempat dan menghasilkan Result daripada masa depan baru, dari mana masalah ini berasal.

Rantai menunggu adalah bau kode yang kuat menurut saya, dan .await atau .await() akan membingungkan begitu banyak pengguna baru bahkan tidak lucu. .await terutama terasa seperti sesuatu yang keluar dari Python atau PHP, di mana akses anggota variabel secara ajaib dapat memiliki perilaku khusus.

await harus di muka yang sudah jelas, seolah-olah berkata, "sebelum melanjutkan, kita tunggu ini" bukan "dan kemudian, kita tunggu ini". Yang terakhir secara harfiah adalah kombinator and_then .

Cara lain untuk memikirkannya adalah dengan mempertimbangkan ekspresi dan futures async / await menjadi seperti iterator. Keduanya malas, dapat memiliki banyak status, dan dapat diakhiri dengan sintaks ( for-in untuk iterator, await untuk kontrak berjangka). Kami tidak akan mengusulkan ekstensi sintaks untuk iterator untuk menyatukannya sebagai ekspresi reguler seperti kami di sini, bukan? Mereka menggunakan kombinator untuk merangkai, dan operasi / masa depan asinkron harus melakukan hal yang sama, hanya berhenti sekali. Ini berhasil dengan baik.

Terakhir, dan yang paling penting, saya ingin dapat menelusuri beberapa indentasi pertama dari suatu fungsi secara vertikal dan melihat dengan jelas di mana operasi asinkron terjadi. Visi saya tidak terlalu bagus, dan menunggu di akhir jalur atau terjebak di tengah akan membuat segalanya jauh lebih sulit sehingga saya mungkin hanya tinggal dengan masa depan mentah sepenuhnya.

Semuanya harap baca posting blog ini sebelum berkomentar.

Async / await bukanlah tentang kenyamanan. Ini bukan tentang menghindari kombinator.

Ini tentang mengaktifkan hal - hal tidak dapat dilakukan dengan kombinator.

Jadi setiap saran tentang "mari gunakan kombinator" berada di luar topik dan harus didiskusikan di tempat lain.

Perhatikan kurangnya menunggu di tengah? Karena perangkaian biasanya dapat diselesaikan dengan desain API yang lebih baik.

Anda tidak akan mendapatkan rantai cantik dalam contoh asinkron yang nyata. Contoh nyata:

req.into_body().concat2().and_then(move |chunk| {
    from_slice::<Update>(chunk.as_ref())
        .into_future()
        .map_err(|_| {
            ...
        })
        .and_then(|update| {
            ...
        })
        .and_then(move |(user, file_id, chat_id, message_id)| {
            do_thing(&file_id)
                .and_then(move |file| {
                    if some_cond {
                        Either::A(do_yet_another-thing.and_then(move |bytes| {
                            ...

                            if another_cond {
                                ...
                                Either::A(
                                    do_other_thing()
                                        .then(move |res| {
                                            ...
                                        }),
                                )
                            } else {
                                Either::B(future::ok(()))
                            }
                        }))
                    } else {
                        Either::B(future::ok(()))
                    }
                })
                .map_err(|e| {
                    ...
                })
        })
        // ...and here we unify both paths
        .map(|_| {
            Response::new(Body::empty())
        })
        .or_else(Ok)
})

Kombinator tidak membantu di sini. Nesting bisa dihilangkan, tapi itu tidak mudah (saya coba dua kali dan gagal). Anda memiliki banyak Either:A(Either:A(Either:A(...))) di akhir.

OTOH itu mudah diselesaikan dengan async / await .

@Pauan telah menulisnya sebelum saya menyelesaikan posting saya. Jadilah itu. Jangan menyebutkan kombinator, async/await seharusnya tidak terlihat serupa. Tidak apa-apa jika ya, ada hal yang lebih penting untuk dipertimbangkan.


Merendahkan komentar saya tidak akan membuat kombinator lebih nyaman.

Membaca komentar jenis ascription , saya berpikir bagaimana itu akan terlihat dikombinasikan dengan await . Mengingat reservasi yang dimiliki beberapa orang dengan .await atau .await() sebagai bidang anggota yang membingungkan / akses metode, saya bertanya-tanya apakah sintaks "ascription" umum dapat menjadi opsi, (yaitu, "beri saya await return ").

Menggunakan yield untuk await untuk penyorotan sintaks

let result = connection.fetch("url"):yield?.collect_async():yield?;

Dikombinasikan dengan jenis ascription, mis:

let result = connection.fetch("url") : yield?
    .collect_async() : yield Vec<u64>?;

Keuntungan: masih terlihat bagus (IMHO), sintaks berbeda dari akses bidang / metode.
Kekurangan: potensi konflik dengan tipe ascription (?), Beberapa asumsi secara teori:

foo():yield:yield

Sunting: Terpikir oleh saya bahwa menggunakan 2 ascriptions akan lebih logis dalam contoh gabungan:

let result = connection.fetch("url") : yield?
    .collect_async() : yield : Vec<u64>?;

@Pauan akan menjadi tidak pantas untuk menerapkan posting blog itu secara

Masalah intinya selalu adalah bahwa masa depan yang bisa dipijahkan haruslah 'static , tetapi jika Anda menangkap variabel dengan referensi di kombinator tersebut, Anda berakhir dengan masa depan yang bukan 'static . Tetapi menunggu masa depan non- 'static tidak membuat asinkron jika Anda tidak berada di 'static . Jadi Anda bisa melakukan hal-hal seperti ini:

`` karat
// slice: & [T]

biarkan x = menunggu masa depan.and_then (| i | if slice.contains (i) {async_fn (slice)});

Jadi, saya berencana untuk tidak berkomentar sama sekali, tetapi saya ingin menambahkan beberapa catatan dari seseorang yang tidak banyak menulis kode asinkron, tetapi masih harus membacanya:

  • Awalan await jauh lebih mudah diperhatikan. Ini terjadi ketika Anda mengharapkannya (di dalam async fn) atau di luar (dalam ekspresi beberapa badan makro).
  • Postfix await menjadi kata kunci yang mungkin menonjol secara berantai dengan sedikit atau tanpa kata kunci lainnya. Tetapi begitu Anda memiliki penutupan atau serupa dengan struktur kontrol lainnya, awaits akan menjadi jauh lebih tidak terlihat dan lebih mudah untuk diabaikan.
  • Saya menemukan .await sebagai bidang semu sangat aneh. Tidak seperti bidang lain dalam hal apa pun yang penting bagi saya.
  • Saya tidak yakin apakah mengandalkan deteksi IDE dan penyorotan sintaks adalah ide yang bagus. Ada nilai dalam kemudahan pembacaan ketika tidak ada penyorotan sintaks yang tersedia. Ditambah dengan diskusi baru-baru ini tentang memperumit tata bahasa dan masalah yang akan ditimbulkannya untuk perkakas, mungkin bukan ide yang baik untuk mengandalkan IDE / editor untuk menyelesaikannya dengan benar.

Saya tidak berbagi sentimen bahwa await!() sebagai makro "tidak terasa kelas satu". Macro benar-benar warga negara kelas satu di Rust. Mereka juga tidak ada di sana hanya untuk "menciptakan sintaksis dalam kode pengguna". Banyak makro "bawaan" tidak ditentukan pengguna dan tidak ada di sana hanya untuk alasan sintaksis. format!() ada untuk memberi kompilator kekuatan string format pemeriksaan tipe statis. include_bytes!() juga tidak ditentukan pengguna dan tidak ada di sana hanya untuk alasan sintaksis, ini adalah makro karena perlu ekstensi sintaks karena, sekali lagi, ia melakukan hal-hal khusus di dalam kompiler dan tidak bisa secara wajar ditulis dengan cara lain tanpa menambahkannya sebagai fitur bahasa inti.

Dan inilah maksud saya: makro adalah cara sempurna untuk memperkenalkan dan menyembunyikan sihir kompiler dengan cara yang seragam dan tidak mengejutkan. Await adalah contoh yang baik untuk ini: ia memerlukan penanganan khusus, tetapi masukannya hanyalah sebuah ekspresi, dan karenanya, sangat cocok untuk realisasi menggunakan makro.

Jadi, saya sama sekali tidak melihat perlunya kata kunci khusus. Yang mengatakan, jika itu akan menjadi kata kunci, maka seharusnya hanya itu, kata kunci telanjang - Saya tidak begitu mengerti motivasi menyamar sebagai bidang. Itu sepertinya salah, ini bukan akses lapangan, itu bahkan tidak seperti akses lapangan (tidak seperti, katakanlah, metode pengambil / penyetel dengan gula bisa jadi), jadi memperlakukannya sebagai satu tidak konsisten dengan cara kerja bidang hari ini dalam bahasa.

@ H2CO3 await! makro baik untuk setiap cara yang diinginkan kecuali untuk dua properti:

  1. Kawat gigi ekstra benar-benar menjengkelkan saat Anda menulis ribuan lagi menunggu. Karat menghapus kawat gigi dari blok if/while/... karena (saya yakin) alasan yang sama. Atm mungkin terlihat tidak penting, tetapi itu benar-benar masalahnya
  2. Asimetri dengan async menjadi kata kunci. Async harus melakukan sesuatu yang lebih besar dari sekadar mengizinkan satu makro di badan metode. Jika tidak, bisa jadi atribut #[async] di atas fungsi. Saya mengerti bahwa atribut tidak akan mengizinkan Anda untuk menggunakannya di blok dll, tetapi memberikan beberapa harapan untuk menemukan await kata kunci . Harapan ini mungkin disebabkan oleh pengalaman dalam bahasa lain, siapa tahu, tapi saya sangat yakin itu ada. Ini mungkin tidak dibahas, tetapi itu harus dipertimbangkan.

Jika tidak, bisa jadi atribut #[async] di atas fungsi.

Untuk waktu yang lama, dan saya menyesal melihatnya pergi. Kami memperkenalkan kata kunci lain sedangkan atribut #[async] bekerja dengan sangat baik, dan sekarang dengan makro prosedural seperti atribut yang stabil, bahkan akan konsisten dengan bahasa lainnya baik secara sintaksis maupun semantik, bahkan jika itu masih diketahui (dan ditangani secara khusus) oleh kompilator.

@ H2CO3 apa tujuan atau keuntungan memiliki await sebagai makro dari perspektif pengguna ? Apakah ada makro bawaan yang mengubah aliran kontrol program di bawah tenda?

@ I60R Keuntungannya adalah keseragaman dengan bahasa lainnya, dan dengan demikian tidak perlu khawatir tentang kata kunci lain, dengan semua beban yang akan dibawa dengan sendirinya - misalnya, prioritas dan pengelompokan terlihat jelas dalam pemanggilan makro.

Saya tidak mengerti mengapa bagian kedua relevan - tidak dapatkah ada makro bawaan yang melakukan sesuatu yang baru? Sebenarnya saya pikir hampir setiap makro built-in melakukan sesuatu yang "aneh" yang tidak dapat (secara wajar / efisien / dll.) Dicapai tanpa dukungan compiler. await!() tidak akan menjadi penyusup baru dalam hal ini.

Saya sebelumnya menyarankan menunggu implisit , dan baru-baru ini menyarankan menunggu non-chainable .

Komunitas tampaknya sangat mendukung penantian eksplisit (saya ingin itu berubah, tapi…). Namun, granularitas kesederhanaan inilah yang menciptakan boilerplate (yaitu membutuhkannya berkali-kali dalam ekspresi yang sama tampaknya membuat boilerplate).

[ Peringatan : Saran berikut akan menjadi kontroversial, tapi tolong berikan kesempatan yang tulus]

Saya ingin tahu apakah sebagian besar komunitas akan berkompromi dan mengizinkan "menunggu sebagian tersirat"? Mungkin membaca menunggu sekali per rantai ekspresi cukup eksplisit?

await response = client.get("https://my_api").send()?.json()?;

Ini adalah sesuatu yang mirip dengan de-structuring nilai, yang saya sebut ekspresi de-structuring.

// Value de-structuring
let (a, b, c) = ...;

// De-sugars to:
let _abc = ...;
let a = _abc.0;
let b = _abc.1;
let c = _abc.2;
// Expression de-structuring
await response = client.get("https://my_api").send()?.json()?;

// De-sugards to:
// Using: prefix await with explicit precedence
// Since send() and json() return impl Future but ? does not expect a Future, de-structure the expression between those sub-expressions.
let response = await (client.get("https://my_api").send());
let response = await (response?.json());
let response = response?;



md5-eeacf588eb86592ac280cf8c372ef434



```rust
// If needed, given that the compiler knows these expressions creating a, b are independent,
// each of the expressions would be de-structured independently.
await (a, b) = (
    client.get("https://my_api_a").send()?.json()?,
    client.get("https://my_api_b").send()?.json()?,
);
// De-sugars to:
// Not using await syntax like the other examples since await can't currently let us do concurrent polling.
let a: impl Future = client.get("https://my_api_a").send();
let b: impl Future = client.get("https://my_api_b").send();
let (a, b) = (a.poll(), b.poll());
// return if either a or b is NotReady;

let a: impl Future = a?.json();
let b: impl Future = b?.json();
let (a, b) = (a.poll(), b.poll());
// return if either a or b is NotReady;
let (a, b) = (a?, b?);

Saya juga akan senang dengan implementasi lain dari gagasan inti, "membaca menunggu sekali per rantai ekspresi kemungkinan cukup eksplisit". Karena ini mengurangi boilerplate tetapi juga memungkinkan perangkaian dengan sintaks berbasis prefiks yang paling akrab bagi semua orang.

@yazaddaruvala Ada peringatan di atas jadi hati-hati.

Saya ingin tahu apakah sebagian besar komunitas akan berkompromi dan mengizinkan "menunggu sebagian tersirat"? Mungkin membaca menunggu sekali per rantai ekspresi cukup eksplisit?

  1. Saya tidak berpikir sebagian besar komunitas menginginkan sebagian-implisit-apapun. Pendekatan asli Rust lebih seperti "semuanya eksplisit". Bahkan gips u8 -> u32 dilakukan secara eksplisit.
  2. Ini tidak bekerja dengan baik untuk skenario yang lebih rumit. Apa yang harus dilakukan kompilator dengan Vec<Future> ?
await response = client.get("https://my_api").send()?.json()?.parse_as::<Vec<String>>()?.map(|x| client.get(x))?;

Haruskah await dilakukan untuk setiap panggilan bertingkat? Oke, kita mungkin bisa membuat menunggu bekerja untuk satu baris kode (jadi bertumpuk mapFunc tidak akan ditunggu), tapi terlalu mudah untuk menghentikan perilaku ini. Jika await berfungsi untuk satu baris kode maka pemfaktoran ulang seperti "nilai ekstrak" akan merusaknya.

@yaelahcan

Apakah saya mengerti Anda benar dalam apa yang Anda maksudkan adalah awalan menunggu yang didahulukan lebih dari ? dengan kemungkinan tambahan menggunakan menunggu di tempat kata kunci biarkan untuk mengubah konteks untuk menaburkan semua impl Future <_ i = "7"> kembali dengan menunggu?

Artinya ketiga pernyataan di bawah ini setara:

// foo.bar() -> Result<impl Future<_>, _>>
let a = await foo.bar()?;
let a = await (foo.bar()?);
await a = foo.bar()?;

Dan kedua pernyataan di bawah ini setara:

// foo.bar() -> impl Future<Result<_, _>>
await a = foo.bar()?;
let a = await (foo.bar())?;

Saya tidak yakin sintaksnya ideal, tetapi mungkin memiliki kata kunci yang berubah menjadi "menunggu konteks implisit" membuat masalah kata kunci awalan tidak terlalu menjadi masalah. Dan pada saat yang sama memungkinkan untuk menggunakan kontrol yang lebih halus di mana menunggu digunakan saat diperlukan.

Menurut pendapat saya, untuk awalan satu baris yang pendek await memiliki manfaat kemiripan dengan banyak bahasa pemrograman lain, dan banyak bahasa manusia . Jika pada akhirnya diputuskan bahwa menunggu seharusnya hanya sufiks, saya berharap saya akan terkadang menggunakan makro Await!() (atau bentuk serupa apa pun yang tidak berbenturan dengan kata kunci), ketika saya merasa itu bersandar pada keakraban yang tertanam dalam dengan struktur kalimat bahasa Inggris akan membantu mengurangi beban mental dalam membaca / menulis kode. Ada nilai pasti dalam bentuk sufiks untuk ekspresi yang dirantai lebih panjang, tetapi pandangan saya yang secara eksplisit tidak didasarkan pada fakta-objektif adalah bahwa, dengan kompleksitas awalan manusia menunggu disubsidi oleh bahasa lisan, manfaat kesederhanaan hanya memiliki satu bentuk tidak. tidak secara jelas lebih penting daripada kejelasan kode potensial karena memiliki keduanya. Dengan asumsi Anda mempercayai programmer untuk memilih mana yang paling sesuai untuk potongan kode saat ini, setidaknya.

@Tokopedia

Saya tidak berpikir sebagian besar komunitas menginginkan sebagian-implisit-apapun. Pendekatan asli Rust lebih seperti "semuanya eksplisit".

Saya tidak melihatnya dengan cara yang sama. Saya melihatnya selalu merupakan kompromi eksplisit dan implisit. Sebagai contoh:

  • Jenis variabel diperlukan di ruang lingkup fungsi

    • Jenis variabel dapat tersirat dalam lingkup lokal.

    • Konteks clousure bersifat implisit

    • Mengingat kita sudah bisa bernalar tentang variabel lokal.

  • Seumur hidup diperlukan untuk membantu pemeriksa pinjaman.

    • Tapi dalam lingkup lokal mereka bisa disimpulkan.

    • Pada level fungsi, masa pakai tidak perlu eksplisit, jika semuanya memiliki nilai yang sama.

  • Saya yakin masih ada lagi.

Menggunakan await hanya sekali per ekspresi / pernyataan, dengan semua manfaat rangkaian, adalah kompromi yang sangat wajar antara boilerplate eksplisit dan pengurangan.

Ini tidak bekerja dengan baik untuk skenario yang lebih rumit. Apa yang harus dilakukan kompilator dengan Vec?

await response = client.get("https://my_api").send()?.json()?.parse_as::<Vec<String>>()?.map(|x| client.get(x))?;

Saya berpendapat bahwa kompiler harus error. Ada perbedaan tipe besar yang tidak bisa disimpulkan. Sementara itu, contoh dengan Vec<impl Future> tidak diselesaikan dengan sintaks mana pun di utas ini.

// Given that json() returns a Vec<imple Future>,
// do I use .await on the `Vec`? That seems odd.
// Given that the client.get() returns an `impl Future`,
// do I .await inside the .map? That wont work, given Iterator methods are not `async`
client.get("https://my_api").send().await?.json()[UNCLEAR]?.parse_as::<Vec<String>>()?.map(|x| client.get(x)[UNCLEAR])?;

Saya berpendapat bahwa Vec<impl Future> adalah contoh yang buruk, atau kita harus memegang setiap sintaks yang diusulkan dengan standar yang sama. Sementara itu, sintaks "parsial-implisit menunggu" yang saya usulkan bekerja lebih baik daripada proposal saat ini untuk skenario yang lebih rumit seperti await (a, b) = (client.get("a")?, client.get("b")?); menggunakan postfix atau awalan normal menunggu let (a, b) = (client.get("a") await?, client.get("b") await?); menghasilkan operasi jaringan sekuensial, di mana parsial versi implisit dapat dijalankan secara bersamaan oleh kompiler yang diinginkan dengan tepat (seperti yang saya tunjukkan di posting asli saya).

Kembali pada tahun 2011, ketika fitur async di C # masih dalam pengujian, saya bertanya tentang await menjadi operator awalan dan mendapat respon dari C # Bahasa PM. Karena ada diskusi tentang apakah Rust harus menggunakan operator awalan, saya pikir mungkin bermanfaat untuk memposting tanggapan itu di sini. Dari Mengapa 'await' menjadi operator awalan daripada operator postfix? :

Seperti yang dapat Anda bayangkan, sintaks ekspresi await adalah poin diskusi yang besar sebelum kita memutuskan apa yang sekarang ada :-). Namun, kami tidak terlalu mempertimbangkan operator postfix. Ada sesuatu tentang postfix (cf. HP caluclators dan bahasa pemrograman Keempat) yang pada prinsipnya membuat segalanya lebih sederhana dan kurang dapat didekati atau dibaca dalam praktiknya. Mungkin itu hanya cara notasi matematika mencuci otak kita sebagai anak-anak ...

Kami benar-benar menemukan bahwa operator awalan (dengan rasa yang benar-benar penting - "lakukan ini!") Sejauh ini yang paling intuitif. Sebagian besar operator unary kami sudah menjadi awalan, dan menunggu tampaknya cocok dengan itu. Ya itu tenggelam dalam ekspresi kompleks, tetapi begitu juga urutan evaluasi secara umum, karena fakta bahwa misalnya aplikasi fungsi juga bukan postfix. Anda terlebih dahulu mengevaluasi argumen (yang ada di sebelah kanan) dan kemudian memanggil fungsi (yang ada di sebelah kiri). Ya, ada perbedaan dalam sintaks aplikasi fungsi di C # dilengkapi dengan tanda kurung yang sudah ada di dalamnya, sedangkan untuk await Anda biasanya dapat menghapusnya.

Apa yang saya lihat secara luas (dan saya sendiri mengadopsinya) di sekitar await adalah gaya yang menggunakan banyak variabel sementara. Saya cenderung lebih suka

var bResult = await A().BAsync();
var dResult = await bResult.C().DAsync();
dResult.E()

atau semacam itu. Secara umum saya biasanya menghindari memiliki lebih dari satu menunggu di semua kecuali ekspresi paling sederhana (yaitu mereka semua argumen untuk fungsi atau operator yang sama; itu mungkin baik-baik saja) dan saya akan menghindari tanda kurung ekspresi menunggu, lebih memilih menggunakan penduduk setempat tambahan untuk kedua pekerjaan .

Masuk akal?

Mads Torgersen, C # Bahasa PM


Pengalaman pribadi saya sebagai pengembang C # adalah bahwa sintaksis mendorong saya untuk menggunakan gaya beberapa pernyataan, dan ini membuat kode asinkron jauh lebih canggung untuk dibaca dan ditulis daripada padanan sinkron.

@yaelahcan

Saya tidak melihatnya dengan cara yang sama. Saya melihatnya selalu merupakan kompromi eksplisit dan implisit

Itu masih eksplisit. Saya dapat menautkan artikel yang bagus tentang topik btw .

Saya berpendapat bahwa kompiler harus error. Ada perbedaan tipe besar yang tidak bisa disimpulkan. Sedangkan contoh dengan Vectidak diselesaikan oleh salah satu sintaks di utas ini.

Mengapa bisa terjadi kesalahan? Saya senang memiliki Vec<impl future> , sehingga saya dapat bergabung bersama. Anda mengusulkan batasan ekstra tanpa alasan.

Saya berpendapat bahwa Vecadalah contoh yang buruk, atau kita harus menahan setiap sintaks yang diusulkan dengan standar yang sama.

Kita harus memeriksa setiap kasus. Kami tidak bisa mengabaikan kasus edge karena "meh, Anda tahu, tidak ada yang menulis ini". Desain bahasa terutama tentang kasus tepi.

@chescock Saya setuju dengan Anda, tetapi dikatakan sebelumnya, Rust memiliki perbedaan yang sangat besar dari C # di sini: di C # Anda tidak pernah menulis (await FooAsync()).Bar() . Tapi di Rust kamu lakukan. Dan saya berbicara tentang ? . Di C # Anda memiliki pengecualian implisit yang disebarkan melalui panggilan asinkron. Tapi di karat Anda harus eksplisit dan menulis ? pada hasil fungsi setelah ditunggu.

Tentu saja, Anda dapat meminta pengguna untuk menulis

let foo = await bar();
let bar = await foo?.baz();
let bar = bar?;

tapi itu jauh lebih aneh dari

let foo = await? bar();
let bar = await? foo.baz();

Ini terlihat jauh lebih baik, tetapi perlu memperkenalkan kombinasi baru await dan ? doint (await expr)? . Selain itu, Jika kita memiliki beberapa operator lain, kita dapat menulis await?&@# dan membuat semua kombinasi bekerja secara bersamaan ... Kelihatannya agak rumit.

Tapi kemudian kita bisa menempatkan await sebagai postfix dan secara alami akan sesuai dengan bahasa saat ini:

let foo = bar() await?;
let bar = foo.baz() await?;

sekarang await? adalah dua token yang terpisah, tetapi mereka bekerja sama sekali. Anda dapat memiliki await?&@# dan Anda tidak perlu repot-repot meretasnya ke dalam kompiler itu sendiri.

Tentu saja, ini terlihat sedikit lebih aneh daripada cara awalan, tetapi dikatakan, ini semua tentang perbedaan Rust / C #. Kita dapat menggunakan pengalaman C # untuk memastikan kita membutuhkan sintaksis menunggu eksplisit yang kemungkinan memiliki kata kunci terpisah, tetapi kita tidak boleh mengikuti cara yang sama dan mengabaikan perbedaan Rust / C #.

Saya adalah pendukung untuk prefiks await sendiri untuk waktu yang lama dan saya bahkan mengundang pengembang bahasa C # ke dalam topik (jadi kami memiliki informasi yang lebih baru dari tahun 2011 😄), tapi sekarang saya pikir postfix await kata kunci adalah pendekatan terbaik.

Jangan lupa ada nightly, jadi kita bisa mengerjakannya lagi nanti jika menemukan cara yang lebih baik.

Terpikir oleh saya bahwa kita mungkin bisa mendapatkan yang terbaik dari kedua dunia dengan kombinasi:

  • awalan async kata kunci
  • fitur makro postfix umum

Yang bersama-sama akan memungkinkan makro .await!() postfix diimplementasikan tanpa sihir kompilator.

Saat ini, makro .await!() akan membutuhkan sihir kompilator. Namun, jika varian awalan dari await kata kunci distabilkan, maka ini tidak akan berlaku lagi: makro .await!() dapat diimplementasikan dengan mudah sebagai makro yang mengawali argumennya (ekspresi) dengan kata kunci await dan membungkus semuanya dalam tanda kurung.

Keuntungan dari pendekatan ini:

  • Awalan await kata kunci tersedia, dan dapat diakses oleh pengguna yang akrab dengan konstruksi itu dari bahasa lain.
  • Awalan kata kunci async dapat digunakan di tempat yang diinginkan untuk membuat sifat asinkron dari ekspresi "menonjol".
  • Akan ada varian postfix .await!() , yang dapat digunakan dalam situasi chaining, atau situasi lain di mana sintaks prefiks canggung.
  • Tidak diperlukan sihir kompilator (yang berpotensi tidak terduga) untuk varian postfix (seperti yang akan menjadi masalah untuk bidang postfix, metode, atau opsi makro).
  • Makro Postfix juga akan tersedia untuk situasi lain (seperti .or_else!(continue) ), yang secara kebetulan membutuhkan sintaks makro postfix untuk alasan yang sama (sebaliknya mereka memerlukan ekspresi sebelumnya untuk dibungkus dengan cara yang aneh dan tidak terbaca).
  • Awalan await kata kunci dapat distabilkan dengan relatif cepat (memungkinkan ekosistem berkembang) tanpa kita harus menunggu implementasi makro postfix. Tapi jangka menengah-panjang kita masih akan membiarkan kemungkinan sintaks postfix menunggu.

@nicoburns, @ H2CO3,

Kita seharusnya tidak mengimplementasikan operator aliran kontrol seperti await!() dan .or_else!(continue) sebagai makro karena dari perspektif pengguna tidak ada alasan yang berarti untuk melakukan itu. Bagaimanapun, dalam kedua bentuk pengguna akan memiliki fitur yang persis sama dan harus mempelajari keduanya, namun jika fitur ini akan diimplementasikan sebagai makro, selain itu pengguna akan khawatir tentang mengapa mereka diterapkan dengan cara ini. Tidak mungkin untuk tidak menyadarinya, karena hanya akan ada perbedaan yang aneh dan artifisial antara operator kelas satu reguler dan operator reguler yang diimplementasikan sebagai makro (karena makro itu sendiri adalah kelas satu, tetapi hal-hal yang diterapkan oleh mereka tidak).

Jawabannya persis sama seperti untuk tambahan . sebelum await : kami tidak membutuhkannya. Makro tidak dapat meniru aliran kontrol kelas satu: mereka memiliki bentuk yang berbeda, memiliki kasus penggunaan yang berbeda, memiliki penyorotan yang berbeda, mereka bekerja dengan cara yang berbeda, dan mereka dipersepsikan sangat berbeda.

Untuk fitur .await!() dan .or_else!(continue) ada solusi yang tepat dan ergonomis dengan sintaks yang indah: await kata kunci dan none -pengoperasian operator. Kita harus lebih suka mengimplementasikannya daripada sesuatu yang umum, jarang digunakan, dan tampak jelek seperti makro postfix.

Tidak ada alasan untuk menggunakan makro, karena tidak ada yang rumit seperti format!() , tidak ada sesuatu yang jarang digunakan seperti include_bytes!() , tidak ada DSL khusus, tidak ada penghapusan duplikasi dalam kode pengguna, ada tidak memerlukan sintaks seperti vararg, tidak perlu sesuatu yang tampak berbeda, dan kami tidak dapat menggunakan cara ini hanya karena memungkinkan.

Kita tidak boleh mengimplementasikan operator aliran kontrol sebagai makro karena dari sudut pandang pengguna tidak ada alasan yang berarti untuk melakukan itu.

try!() diimplementasikan sebagai makro. Ada alasan yang bagus untuk itu.

Saya telah menjelaskan apa alasan untuk membuat makro await - tidak perlu elemen bahasa baru jika fitur yang ada dapat mencapai tujuan juga.

tidak ada yang rumit seperti format!()

Saya mohon berbeda: format!() bukan tentang kerumitan, ini tentang pemeriksaan waktu kompilasi. Jika bukan untuk pemeriksaan waktu kompilasi, itu bisa menjadi fungsi.

Ngomong-ngomong, saya tidak menyarankan itu harus makro postfix. (Saya pikir seharusnya tidak.)

Untuk kedua fitur tersebut terdapat solusi yang tepat dan ergonomis dengan sintaks yang indah

Kita tidak boleh memberikan setiap panggilan fungsi, ekspresi kontrol aliran, atau fitur kenyamanan kecil sintaksnya yang sangat khusus. Itu hanya menghasilkan bahasa yang membengkak, penuh sintaks tanpa alasan, dan itu kebalikan dari "indah".

(Saya telah mengatakan apa yang saya bisa; saya tidak akan menjawab aspek pertanyaan ini lagi.)

@tokopedia

Terpikir oleh saya bahwa kita mungkin bisa mendapatkan yang terbaik dari kedua dunia dengan kombinasi:

  • awalan await kata kunci
  • fitur makro postfix umum

Ini memerlukan membuat await kata kunci kontekstual sebagai lawan dari kata kunci sebenarnya saat ini. Saya tidak berpikir kita harus melakukan itu.

Yang bersama-sama akan memungkinkan makro .await!() postfix diimplementasikan tanpa sihir kompilator.

Ini adalah implementasi solusi yang lebih kompleks daripada foo await atau foo.await (terutama yang terakhir). Masih ada "keajaiban", sebanyak itu; Anda baru saja melakukan manuver akuntansi .

Keuntungan dari pendekatan ini:

  • Awalan await kata kunci tersedia, dan dapat diakses oleh pengguna yang terbiasa dengan konstruksi itu dari bahasa lain.

Jika kami akan menambahkan .await!() nanti, kami hanya memberi pengguna lebih banyak untuk dipelajari ( await foo dan foo.await!() ) dan sekarang pengguna akan bertanya "yang mana yang harus saya gunakan kapan..". Ini tampaknya menggunakan lebih banyak anggaran kompleksitas daripada foo await atau foo.await sebagai solusi tunggal.

  • Makro Postfix juga akan tersedia untuk situasi lain (seperti .or_else!(continue) ), yang secara kebetulan memerlukan sintaksis makro postfix untuk alasan yang sama (sebaliknya mereka memerlukan ekspresi sebelumnya untuk dibungkus dengan cara yang aneh dan tidak terbaca).

Saya percaya makro postfix memiliki nilai dan harus ditambahkan ke bahasa. Namun itu tidak berarti bahwa mereka harus digunakan untuk await ing; Seperti yang Anda sebutkan, ada .or_else!(continue) dan banyak tempat lain di mana makro postfix akan berguna.

  • Awalan await kata kunci dapat distabilkan dengan relatif cepat (memungkinkan ekosistem berkembang) tanpa kita harus menunggu implementasi makro postfix. Tapi jangka menengah-panjang kita masih akan membiarkan kemungkinan sintaks postfix menunggu.

Saya tidak melihat nilai dalam "meninggalkan kemungkinan yang terbuka"; nilai postfix untuk komposisi, pengalaman IDE, dll. dikenal saat ini. Saya tidak tertarik untuk "menstabilkan await foo hari ini dan berharap kita dapat mencapai konsensus tentang foo.await!() besok".


@ H2O3

try!() diimplementasikan sebagai makro. Ada alasan yang bagus untuk itu.

try!(..) sudah tidak digunakan lagi, artinya kami menganggapnya tidak cocok untuk bahasa tersebut, terutama karena ini bukan postfix. Menggunakannya sebagai argumen - selain untuk apa yang seharusnya tidak kita lakukan - tampak aneh. Selain itu, try!(..) didefinisikan tanpa dukungan apapun dari kompilator .

Ngomong-ngomong, saya tidak menyarankan itu harus makro postfix. (Saya pikir seharusnya tidak.)

await bukanlah "setiap orang .." - khususnya, ini bukan fitur kenyamanan kecil ; melainkan, ini adalah fitur bahasa utama yang sedang ditambahkan.

@bayu_joo

Ditambah dengan diskusi baru-baru ini tentang memperumit tata bahasa dan masalah yang akan ditimbulkannya untuk perkakas, mungkin bukan ide yang baik untuk mengandalkan IDE / editor untuk menyelesaikannya dengan benar.

Sintaks foo.await dimaksudkan untuk mengurangi masalah perkakas karena editor mana pun yang memiliki kemiripan dukungan yang baik untuk Rust sudah memahami formulir .ident . Yang perlu dilakukan editor hanyalah menambahkan await ke daftar kata kunci. Selain itu, IDE yang baik sudah memiliki penyelesaian kode berdasarkan . - jadi tampaknya lebih mudah untuk memperluas RLS (atau yang setara ...) untuk memberikan await sebagai saran pertama saat pengguna mengetik . setelah my_future .

Adapun untuk memperumit tata bahasa, .await sebenarnya paling tidak mungkin untuk memperumit tata bahasa mengingat bahwa mendukung .await dalam sintaks pada dasarnya mengurai .$ident dan kemudian tidak membuat kesalahan pada ident == keywords::Await.name() .

Sintaks foo.await dimaksudkan untuk mengurangi masalah perkakas karena editor apa pun yang memiliki kemiripan dukungan yang baik untuk Rust sudah memahami bentuk .ident. Apa yang perlu dilakukan editor hanyalah menambahkan menunggu ke daftar kata kunci. Selain itu, IDE yang bagus sudah memiliki penyelesaian kode berdasarkan. - jadi tampaknya lebih mudah untuk memperluas RLS (atau yang setara ...) untuk memberikan await sebagai saran pertama saat pengguna mengetik. setelah my_future.

Saya hanya menemukan ini bertentangan dengan diskusi tata bahasa. Dan masalahnya bukan hanya sekarang, tetapi dalam 5, 10, 20 tahun ke depan, setelah beberapa edisi. Tetapi seperti yang saya harap Anda perhatikan, saya juga menyebutkan bahwa meskipun kata kunci disorot, poin menunggu dapat luput dari perhatian jika ada kata kunci lain yang terlibat.

Untuk memperumit tata bahasa, .await sebenarnya paling tidak mungkin mempersulit tata bahasa mengingat bahwa mendukung .await dalam sintaks pada dasarnya adalah parsing. $ Ident dan kemudian tidak membuat kesalahan pada ident == keywords :: Await.name ().

Saya pikir kehormatan itu milik await!(future) , karena sudah didukung penuh oleh tata bahasa.

@Centril try! akhirnya menjadi mubazir karena operator ? dapat melakukan lebih banyak hal. Itu bukan "tidak cocok untuk bahasa". Anda mungkin tidak menyukainya , yang saya terima. Tapi bagi saya itu sebenarnya salah satu hal terbaik yang ditemukan Rust, dan merupakan salah satu nilai jual. Dan saya tahu ini diimplementasikan tanpa dukungan compiler - tapi saya gagal untuk melihat bagaimana hal itu relevan saat membahas apakah itu melakukan aliran kontrol atau tidak. Itu benar, bagaimanapun.

menunggu bukanlah "setiap orang .."

Tetapi yang lain yang disebutkan di sini (mis. or_else ) adalah, dan poin saya masih berlaku untuk fitur-fitur utama. Menambahkan sintaks hanya untuk menambahkan sintaks bukanlah keuntungan, jadi setiap kali ada sesuatu yang sudah berfungsi dalam kasus yang lebih umum, itu harus lebih disukai daripada menciptakan notasi baru. (Saya tahu argumen lain melawan makro adalah bahwa "mereka bukan postfix". Saya hanya tidak berpikir bahwa manfaat dari await menjadi operator postfix sendiri cukup tinggi untuk membenarkan biaya. Kami telah bertahan dari pemanggilan fungsi bertingkat. Kami akan sama baiknya setelah menulis beberapa makro bertingkat.)

Pada Rabu, 23 Jan 2019 pukul 09:59:36 +0000, Mazdak Farrokhzad menulis:

  • Makro Postfix juga akan tersedia untuk situasi lain (seperti .or_else!(continue) ), yang secara kebetulan membutuhkan sintaksis makro postfix untuk alasan yang sama (sebaliknya mereka memerlukan ekspresi sebelumnya untuk dibungkus dengan cara yang aneh dan tidak terbaca).

Saya percaya makro postfix memiliki nilai dan harus ditambahkan ke bahasa. Namun itu tidak berarti bahwa mereka harus digunakan untuk await ing; Seperti yang Anda sebutkan, ada .or_else!(continue) dan banyak tempat lain di mana makro postfix akan berguna.

Alasan utama untuk menggunakan .await!() adalah karena terlihat seperti buatan makro
jelas bahwa itu dapat mempengaruhi aliran kontrol.

.await tampak seperti akses bidang, .await() tampak seperti fungsi
panggilan, dan akses bidang maupun panggilan fungsi tidak dapat mempengaruhi kontrol
mengalir. .await!() terlihat seperti makro, dan makro dapat memengaruhi kontrol
mengalir.

@joshtriplett Saya tidak memengaruhi aliran kontrol dalam arti standar. Ini adalah fakta dasar untuk membenarkan bahwa meminjam checker harus bekerja di futures sebagaimana didefinisikan (dan pin adalah penalaran bagaimana). Dari tampilan eksekusi fungsi lokal, mengeksekusi await seperti kebanyakan panggilan fungsi lainnya. Anda melanjutkan di mana Anda tinggalkan, dan memiliki nilai kembali di tumpukan.

Saya hanya menemukan ini bertentangan dengan diskusi tata bahasa. Dan masalahnya bukan hanya sekarang, tetapi dalam 5, 10, 20 tahun ke depan, setelah beberapa edisi.

Saya tidak tahu apa yang Anda maksud dengan ini.

Saya pikir kehormatan itu milik await!(future) , karena sudah didukung penuh oleh tata bahasa.

Saya mengerti, tetapi pada titik ini, karena banyak kelemahan lain yang dimilikinya sintaks ini, saya pikir itu secara efektif dikesampingkan.

@Centril try! akhirnya menjadi mubazir karena operator ? dapat melakukan lebih banyak hal. Itu bukan "tidak cocok untuk bahasa". Anda mungkin tidak _menyukainya_, yang saya terima.

Ini secara eksplisit tidak digunakan lagi dan lebih jauh lagi merupakan kesalahan besar untuk menulis try!(expr) pada Rust 2018. Kami memutuskan secara kolektif untuk membuatnya jadi dan karenanya dianggap tidak layak.

Alasan utama untuk menggunakan .await!() adalah karena terlihat seperti makro yang memperjelas bahwa hal itu dapat memengaruhi aliran kontrol.

.await tampak seperti akses bidang, .await() tampak seperti pemanggilan fungsi, dan baik akses bidang maupun pemanggilan fungsi tidak dapat memengaruhi aliran kontrol.

Saya percaya perbedaannya adalah sesuatu yang akan dipelajari pengguna dengan relatif mudah, terutama karena .await biasanya akan diikuti oleh ? (yang merupakan aliran kontrol fungsi-lokal). Mengenai pemanggilan fungsi, dan seperti yang telah disebutkan di atas, saya pikir cukup adil untuk mengatakan bahwa metode iterator adalah bentuk aliran kontrol. Selain itu, hanya karena makro dapat memengaruhi aliran kontrol tidak berarti akan melakukannya. Banyak makro tidak (misalnya dbg! , format! , ...). Pemahaman bahwa .await!() atau .await akan mempengaruhi aliran kontrol (yang dalam arti yang jauh lebih lemah daripada ? , sesuai catatan @HeroicKatora ) mengalir dari kata await itu sendiri.

@Sentril

Ini memerlukan menunggu kata kunci kontekstual sebagai lawan dari kata kunci sebenarnya saat ini. Saya tidak berpikir kita harus melakukan itu.

Eek. Itu sedikit menyakitkan. Mungkin makro dapat memiliki nama yang berbeda seperti .wait!() atau .awaited!() (yang terakhir cukup bagus karena menjelaskan bahwa ini berlaku untuk ekspresi sebelumnya).

"Yang bersama-sama akan memungkinkan makro .await! () Postfix diimplementasikan tanpa sihir kompilator."
Ini adalah implementasi solusi yang lebih kompleks daripada foo await atau foo.await (terutama yang terakhir). Masih ada "keajaiban", sebanyak itu; Anda baru saja melakukan manuver akuntansi.

Selain itu, coba! (..) didefinisikan tanpa dukungan apa pun dari kompiler.

Dan jika kita memiliki awalan await kata kunci dan makro postfix, maka .await!() (mungkin dengan nama yang berbeda) juga dapat diimplementasikan tanpa dukungan compiler, bukan? Tentu saja kata kunci await itu sendiri masih memerlukan sejumlah besar sihir penyusun, tetapi itu hanya akan diterapkan pada hasil makro, dan tidak berbeda dengan hubungan try!() ke return kata kunci.

Jika kita akan menambahkan .await! () Nanti, kita hanya memberi pengguna lebih banyak untuk dipelajari (keduanya menunggu foo dan foo.await! ()) Dan sekarang pengguna akan bertanya "yang mana yang harus saya gunakan saat ..". Ini tampaknya menggunakan lebih banyak anggaran kompleksitas daripada foo await atau foo.await sebagai solusi tunggal.

Menurut saya kompleksitas yang ditambahkan ini kepada pengguna adalah minimal (kompleksitas implementasi mungkin merupakan masalah lain, tetapi jika kita tetap menginginkan makro postfix ...). Kedua bentuk membaca secara intuitif, sehingga ketika membaca, jelas apa yang terjadi. Adapun yang harus dipilih saat menulis kode, dokumen dapat dengan mudah mengatakan sesuatu seperti:

"Ada dua cara untuk menunggu Masa Depan di Rust: await foo.bar(); dan foo.bar().await!() . Yang akan digunakan adalah preferensi gaya, dan tidak ada bedanya dengan aliran eksekusi"

Saya tidak melihat nilai dalam "meninggalkan kemungkinan yang terbuka"; nilai postfix untuk komposisi, pengalaman IDE, dll. dikenal saat ini.

Itu benar. Saya kira dalam pikiran saya ini lebih merupakan masalah memastikan bahwa implementasi kami tidak menutup kemungkinan memiliki bahasa yang lebih bersatu di masa depan.

Saya tidak tahu apa yang Anda maksud dengan ini.

Tidak terlalu penting. Tetapi secara umum saya juga lebih suka jika sintaksnya jelas tanpa penyorotan yang baik. Jadi hal-hal menonjol di git diff dan dan konteks sejenis lainnya.

Secara teori ya, dan untuk program sepele ya, tetapi dalam kenyataannya programmer
perlu tahu di mana titik penangguhan mereka.

Untuk contoh sederhana, Anda dapat menahan RefCell melintasi titik penangguhan, dan
perilaku program Anda akan berbeda dibandingkan jika RefCell dulu
dirilis sebelum titik penangguhan. Dalam program besar akan ada
kehalusan yang tak terhitung jumlahnya seperti ini, di mana fakta bahwa fungsi saat ini
adalah menangguhkan adalah informasi penting.

Pada Rabu, 23 Jan 2019 pukul 14:21 HeroicKatora [email protected]
menulis:

@joshtriplett https://github.com/joshtriplett Saya tidak memengaruhi kontrol
mengalir dalam arti standar. Ini adalah fakta dasar untuk membenarkan bahwa
pemeriksa pinjaman harus bekerja di masa depan seperti yang didefinisikan. Dari tampilan
eksekusi fungsi lokal, mengeksekusi await seperti pemanggilan fungsi lainnya.

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

Pada Rabu, 23 Jan 2019 pada 14:26:07 -0800, Mazdak Farrokhzad menulis:

Alasan utama untuk menggunakan .await!() adalah karena terlihat seperti makro yang memperjelas bahwa hal itu dapat memengaruhi aliran kontrol.

.await tampak seperti akses bidang, .await() tampak seperti pemanggilan fungsi, dan baik akses bidang maupun pemanggilan fungsi tidak dapat memengaruhi aliran kontrol.

Saya yakin perbedaannya adalah sesuatu yang akan dipelajari pengguna dengan relatif mudah

Mengapa mereka harus melakukannya?

Saya yakin perbedaannya adalah sesuatu yang akan dipelajari pengguna dengan relatif mudah, terutama karena .await biasanya akan diikuti oleh? (yang merupakan aliran kontrol fungsi-lokal).

Seringkali ya, tetapi tidak selalu, dan sintaksnya pasti berfungsi untuk kasus di mana ini tidak terjadi.

Mengenai pemanggilan fungsi, dan seperti yang telah disebutkan di atas, saya pikir cukup adil untuk mengatakan bahwa metode iterator adalah bentuk aliran kontrol.

Tidak seperti return atau ? (atau bahkan break ). Akan sangat mengejutkan jika pemanggilan fungsi dapat mengembalikan dua tingkat dari fungsi yang memanggilnya (salah satu alasan Rust tidak memiliki pengecualian justru karena ini mengejutkan), atau keluar dari loop dalam pemanggilan fungsi.

Selain itu, hanya karena makro dapat memengaruhi aliran kontrol tidak berarti akan melakukannya. Banyak makro tidak (mis. Dbg !, format !, ...). Pemahaman bahwa .await! () Atau .await akan mempengaruhi aliran kontrol (tho dalam arti yang jauh lebih lemah dari?, Sesuai catatan

Saya tidak suka ini sama sekali. Mempengaruhi aliran kontrol adalah efek samping yang sangat penting . Ini harus memiliki bentuk sintaksis yang berbeda dengan konstruksi yang biasanya tidak dapat melakukan ini. Konstruksi yang dapat melakukan ini di Rust adalah: kata kunci ( return , break , continue ), operator ( ? ), dan makro (dengan mengevaluasi satu dari bentuk lain). Mengapa memperkeruh air dengan menambahkan pengecualian?

@ Ejmahler Saya tidak melihat bagaimana memegang RefCell berbeda untuk perbandingan dengan pemanggilan fungsi normal. Bisa juga karena fungsi bagian dalam ingin mendapatkan RefCell yang sama. Tapi tampaknya tidak ada yang memiliki masalah besar dengan tidak segera memahami grafik panggilan potensial penuh. Demikian pula, masalah kehabisan tumpukan tidak menerima perhatian khusus yang harus Anda miliki untuk memberi keterangan pada ruang tumpukan yang disediakan. Di sini perbandingan akan menguntungkan bagi coroutine! Haruskah kita membuat panggilan fungsi normal lebih terlihat jika tidak sebaris? Apakah kita memerlukan panggilan ekor eksplisit untuk mengatasi masalah yang semakin sulit dengan perpustakaan ini?

Jawabannya adalah, imho, risiko terbesar saat menggunakan RefCell dan menunggu adalah ketidaktahuan. Ketika masalah lain di atas dapat disarikan dari perangkat keras, saya yakin bahwa kita sebagai programmer juga dapat belajar untuk tidak memegang RefCell dll. Di seluruh titik hasil kecuali jika diperiksa.

Pada Rabu, 23 Jan 2019 pukul 10.30.10 +0000, Elliott Mahler menulis:

Secara teori ya, dan untuk program sepele ya, tetapi dalam kenyataannya programmer
perlu tahu di mana titik penangguhan mereka.

: +1:

@Tokopedia

Perbedaan kritisnya adalah berapa banyak kode yang harus Anda periksa, berapa banyak
kemungkinan yang harus Anda perhitungkan. Programmer sudah terbiasa
memastikan bahwa tidak ada yang mereka panggil juga menggunakan RefCell yang telah mereka periksa
di luar. Tetapi dengan menunggu, alih-alih memeriksa satu tumpukan panggilan, kami tidak memiliki
kontrol atas apa yang akan dieksekusi selama menunggu - secara harfiah setiap baris file
Program mungkin dijalankan. Intuisi saya memberi tahu saya bahwa programmer rata-rata
apalagi diperlengkapi untuk menghadapi ledakan kemungkinan ini, karena mereka
harus berurusan dengan itu lebih jarang.

Untuk contoh lain dari suspend points menjadi informasi penting, dalam game
pemrograman, kami biasanya menulis coroutine yang dimaksudkan untuk dilanjutkan
sekali per frame, dan kami mengubah status game di setiap resume. Jika kita mengabaikan a
suspend point di salah satu coroutine ini, kita mungkin membiarkan game rusak
status untuk beberapa frame.

Semua ini dapat diatasi dengan mengatakan "jadilah lebih pintar dan hasilkan lebih sedikit
kesalahan ”tentu saja, tetapi pendekatan itu secara historis tidak berhasil.

Pada Rabu, 23 Jan 2019 pukul 14:44 Josh Triplett [email protected]
menulis:

Pada Rabu, 23 Jan 2019 pukul 10.30.10 +0000, Elliott Mahler menulis:

Secara teori ya, dan untuk program sepele ya, tetapi dalam kenyataannya programmer
perlu tahu di mana titik penangguhan mereka.

: +1:

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

Untuk contoh lain dari suspend point yang menjadi informasi penting, dalam pemrograman game, kami biasanya menulis coroutine yang dimaksudkan untuk dilanjutkan sekali per frame, dan kami mengubah status game di setiap resume. Jika kami melewatkan titik penangguhan di salah satu coroutine ini, kami dapat membiarkan game dalam keadaan rusak untuk beberapa bingkai.

Jika Anda menjatuhkan kunci ke SlotMap, Anda membocorkan memori. Bahasa itu sendiri tidak membantu Anda dalam hal ini secara inheren. Yang ingin saya katakan adalah, contoh Anda tentang bagaimana menunggu samar-samar tidak cukup terlihat (selama itu kata kunci kemunculannya akan unik) sepertinya tidak menggeneralisasi masalah yang belum terjadi untuk fitur lain. Saya pikir Anda harus mengevaluasi, menunggu lebih sedikit tentang fitur apa yang diberikan oleh bahasa tersebut dengan segera dan lebih banyak lagi tentang bagaimana hal itu memungkinkan Anda untuk mengekspresikan fitur Anda sendiri. Evaluasi alat Anda dan kemudian terapkan. Menyarankan solusi untuk status game, dan bagaimana hal ini ditangani dengan cukup baik: Tulis pembungkus yang menegaskan bahwa status game sebenarnya telah menandai jumlah yang dibutuhkan, asinkron memungkinkan Anda untuk meminjam status Anda sendiri untuk tujuan itu. Larang menunggu lainnya dalam kode yang terkotak-kotak itu.

Ledakan status di RefCell bukan berasal dari Anda yang tidak memeriksa bahwa itu tidak digunakan di tempat lain tetapi dari titik kode lain tanpa mengetahui bahwa Anda mengandalkan invarian itu dan menambahkannya secara diam-diam. Ini ditangani dengan cara yang sama, dokumentasi, komentar, pembungkusan yang benar.

Menahan RefCell tidak jauh berbeda dengan meringkuk Mutex saat melakukan pemblokiran io. Anda bisa mengalami kebuntuan, yang lebih buruk dan lebih sulit untuk di-debug daripada panik. Namun kami tidak membuat anotasi operasi pemblokiran dengan kata kunci block eksplisit. :).

Berbagi RefCell antara fungsi asinkron akan sangat membatasi kegunaan umum mereka ( !Send ), jadi menurut saya itu tidak akan umum. Dan RefCell umumnya harus dihindari, dan bila digunakan, dipinjam untuk waktu sesingkat mungkin.

Jadi saya tidak berpikir merogoh RefCell secara tidak sengaja atas poin hasil adalah masalah besar.

Jika kami melewatkan titik penangguhan di salah satu coroutine ini, kami dapat membiarkan game dalam keadaan rusak untuk beberapa bingkai.

Coroutine dan fungsi async adalah hal yang berbeda. Kami tidak mencoba membuat yield implisit.

Kepada siapa pun yang menyukai sintaks makro alih-alih sintaks kata kunci di sini: Jelaskan apa itu tentang await yang berarti itu harus makro dan apa bedanya dari, katakanlah, while , yang _could_ miliki baru saja menjadi makro while! ( bahkan hanya menggunakan macro_rules! ; tidak ada casing khusus sama sekali).

Sunting: Ini bukan retorika. Saya benar-benar tertarik pada hal seperti itu, dengan cara yang sama saya pikir saya ingin run-to-first-await (seperti C #) tetapi RFC meyakinkan saya sebaliknya.

Kepada siapa pun yang menyukai sintaks makro alih-alih sintaks kata kunci di sini: Jelaskan tentang await yang berarti itu harus makro dan bagaimana perbedaannya dari, katakanlah, sementara, yang mungkin hanya sebentar! makro (bahkan hanya menggunakan macro_rules !; tidak ada casing khusus sama sekali).

Saya sebagian besar setuju dengan alasan ini, dan saya ingin menggunakan kata kunci untuk sintaks prefiks.

Namun, seperti yang dijelaskan di atas (https://github.com/rust-lang/rust/issues/57640#issuecomment-456990831), saya mendukung juga memiliki makro postfix untuk sintaks postfix (mungkin .awaited!() untuk menghindari bentrokan nama dengan kata kunci awalan). Alasan saya sebagian adalah bahwa itu membuat segalanya lebih sederhana untuk memiliki kata kunci yang hanya dapat digunakan dengan satu cara, dan menyimpan variasi lebih lanjut untuk makro, tetapi terutama sejauh yang saya lihat, tidak ada yang bisa menghasilkan postfix sintaks kata kunci yang saya anggap dapat diterima:

  • foo.bar().await dan foo.bar().await() secara teknis akan menjadi kata kunci, tetapi mereka terlihat seperti akses bidang atau pemanggilan metode, dan menurut saya konstruksi pengubah aliran kontrol seperti itu tidak harus disembunyikan seperti ini.
  • foo.bar() await hanya membingungkan, terutama dengan rangkaian lebih lanjut seperti foo.bar() await.qux() . Saya belum pernah melihat kata kunci yang digunakan sebagai postfix seperti itu (saya yakin ada, tetapi saya sebenarnya tidak dapat memikirkan satu contoh pun dalam bahasa apa pun yang saya ketahui tentang kata kunci yang berfungsi seperti ini). Dan saya pikir itu terbaca sangat membingungkan seolah-olah menunggu berlaku untuk qux() bukan foo.bar() .
  • foo.bar()@await atau tanda baca lainnya dapat digunakan. Tapi itu tidak terlalu bagus, dan rasanya cukup ad-hoc. Saya sebenarnya tidak berpikir ada masalah signifikan dengan sintaks ini (tidak seperti opsi di atas). Saya hanya merasa begitu kita mulai menambahkan sigils, lalu keseimbangan mulai mengarah ke meninggalkan sintaks kustom, dan ke arah itu menjadi makro.

Saya akan mengulangi ide sebelumnya sekali lagi, meskipun disesuaikan dengan pemikiran dan pertimbangan baru, kemudian mencoba untuk tetap diam.

await sebagai kata kunci hanya berlaku dalam fungsi async , jadi kita harus mengizinkan pengenal await tempat lain. Jika sebuah variabel bernama await dalam lingkup luar, itu tidak dapat diakses dari dalam blok async kecuali itu digunakan sebagai pengenal mentah. Atau, misalnya, makro await! .

Selanjutnya, mengadaptasi kata kunci seperti itu akan memungkinkan poin utama saya: kita harus mengizinkan pencampuran dan pencocokan generator dan fungsi async, seperti:

// imaginary generator syntax stolen from JavaScript
fn* my_generator() -> T {
    yield some_value;

    // explicit return statements are only included to 
    // make it clear the generator/async functions are finished.
    return another_value;
}

// `await` keyword would not be allowed here, but the `yield` keyword is
#[async]
fn* my_async_generator() -> Result<T, E> {
    let item = some_op().await!()?; // uses the `.await!()` macro
    // which would really just use `yield` internally, but with the pinning API
    // same as the current nightly macro.

    yield future::ok(item.clone());

    return Ok(item);
}

// `yield` would not be allowed here, but the `await` keyword is.
async fn regular_async() -> Result<T, E> {
   let some_op = async || { /*...*/ };

   let item = await? some_op();

   return Ok(item);
}

Saya yakin ini cukup transparan untuk pengguna yang sangat mahir yang ingin melakukan hal-hal yang funky, tetapi memungkinkan pengguna baru dan moderat untuk hanya menggunakan apa yang diperlukan. 2 dan 3, jika digunakan tanpa pernyataan yield , secara efektif identik.

Saya juga berpikir awalan await? adalah singkatan yang bagus untuk menambahkan ? ke hasil operasi asinkron, tetapi itu tidak sepenuhnya diperlukan.

Jika makro postfix menjadi hal yang resmi (yang saya harap mereka akan melakukannya), await!(...) dan .await!() dapat ada bersama-sama, memungkinkan tiga cara yang setara untuk melakukan menunggu, untuk kasus penggunaan dan gaya tertentu . Saya tidak percaya ini akan menambah overhead kognitif, tetapi akan memungkinkan fleksibilitas yang lebih besar.

Idealnya, async fn dan #[async] fn* bahkan dapat berbagi kode implementasi untuk mengubah generator yang mendasari menjadi mesin status async.

Masalah-masalah ini telah memperjelas bahwa tidak ada gaya yang benar-benar disukai, jadi yang terbaik yang dapat saya harapkan adalah memberikan tingkat kerumitan yang bersih, fleksibel, mudah dibaca, dan mudah didekati. Menurut saya skema di atas adalah kompromi yang baik untuk masa depan.

await sebagai kata kunci hanya valid dalam fungsi async , jadi kita harus mengizinkan pengenal menunggu di tempat lain.

Saya tidak tahu apakah itu praktis di Rust, mengingat makro higienis. Jika saya memanggil foo!(x.await) atau foo!(await { x }) ketika menginginkan expr , saya pikir harus jelas bahwa kata kunci await diinginkan - bukan await atau ekspresi literal await struct dengan singkatan field init - bahkan dalam metode sinkron.

memungkinkan tiga cara setara untuk melakukan menunggu

Tolong jangan. (Setidaknya dalam core . Jelas orang dapat membuat makro dalam kodenya sendiri. Jika mereka mau)

Saat ini, makro postfix .await! () Akan membutuhkan sihir kompilator. Namun, jika varian awalan dari kata kunci await distabilkan, maka ini tidak akan berlaku lagi: makro postfix .await! () Dapat diimplementasikan dengan mudah sebagai makro yang mengawali argumennya (ekspresi) dengan kata kunci await dan digabungkan semuanya dalam tanda kurung.

Saya akan mencatat bahwa ini juga benar - tetapi lebih mudah! - ke arah lain: jika kita menstabilkan sintaks kata kunci .await , orang sudah dapat membuat makro awalan awaited!() jika mereka tidak cukup menyukai postfix.

Saya tidak menyukai gagasan untuk menambahkan beberapa varian yang diizinkan (seperti awalan dan postfix) tetapi untuk alasan yang sedikit berbeda dari yang diminta pengguna. Saya bekerja di perusahaan yang agak besar dan memperebutkan gaya kode itu nyata. Saya hanya ingin menggunakan satu formulir yang jelas dan benar. Bagaimanapun, Zen of python mungkin benar sehubungan dengan hal ini.

Saya tidak menyukai gagasan untuk menambahkan beberapa varian yang diizinkan (seperti awalan dan postfix) tetapi untuk alasan yang sedikit berbeda dari yang diminta pengguna. Saya bekerja di perusahaan yang agak besar dan memperebutkan gaya kode itu nyata. Saya hanya ingin menggunakan satu formulir yang jelas dan benar. Bagaimanapun, Zen of python mungkin benar sehubungan dengan hal ini.

Aku tahu apa yang kamu maksud. Namun, tidak ada cara untuk menghentikan pemrogram menentukan makro mereka sendiri dengan melakukan hal-hal seperti await!() . Structs simular akan selalu memungkinkan. Jadi memang AKAN ada varian yang berbeda.

Yah, bukan await!(...) setidaknya karena itu akan menjadi kesalahan; tetapi jika seorang pengguna mendefinisikan macro_rules! wait { ($e:expr) => { e.await }; } dan kemudian menggunakannya sebagai wait!(expr) maka akan terlihat jelas unidiomatik dan kemungkinan besar akan segera ketinggalan zaman. Itu secara signifikan mengurangi kemungkinan variasi dalam ekosistem dan memungkinkan pengguna untuk mempelajari lebih sedikit gaya. Jadi, menurut saya poin @yasammez tepat.

@Centril Jika seseorang ingin melakukan hal-hal buruk, mereka hampir tidak dapat dihentikan. Bagaimana dengan _await!() atau awai!() ?

atau, ketika pengenal unicode diaktifkan, sesuatu seperti àwait!() .

...

@earthengine Tujuannya adalah untuk menetapkan norma komunitas (mirip dengan apa yang kami lakukan dengan style lints dan rustfmt ), bukan untuk mencegah berbagai orang melakukan hal-hal aneh dengan sengaja. Kami berurusan dengan probabilitas di sini, bukan jaminan mutlak tidak melihat _await!() .

Mari kita rangkum dan tinjau argumen untuk setiap sintaks postfix:

  • __ expr await (kata kunci postfix) __: Sintaks kata kunci Postfix menggunakan await kata kunci yang telah kami simpan. Await adalah transformasi ajaib, dan menggunakan kata kunci membantu yang menonjol dengan tepat. Ini tidak terlihat seperti akses lapangan atau metode atau panggilan makro, dan kami tidak perlu menjelaskan ini sebagai pengecualian. Ini cocok dengan aturan parsing saat ini dan dengan operator ? . Ruang dalam rangkaian metode bisa dibilang tidak lebih buruk daripada dengan Generalized Type Ascription , RFC yang tampaknya diterima dalam beberapa bentuk. Sisi negatifnya, IDE mungkin perlu melakukan lebih banyak keajaiban untuk menyediakan pelengkapan otomatis sebesar await . Orang-orang mungkin menganggap ruang dalam metode perangkaian terlalu merepotkan (meskipun @withoutboats berpendapat bahwa mungkin menguntungkan), terutama jika kode tidak diformat sedemikian rupa sehingga await mengakhiri setiap baris. Ini menggunakan kata kunci yang mungkin bisa kita hindari jika kita mengambil pendekatan lain.

  • __ expr.await (field postfix) __: sintaks field Postfix memanfaatkan "kekuatan titik" - terlihat dan terasa alami dalam rangkaian dan memungkinkan IDE untuk melengkapi otomatis await tanpa melakukan operasi lain (seperti seperti menghapus titik secara otomatis ). Ini sesingkat kata kunci postfix. Titik di depan menunggu dapat membuat contoh lebih mudah ditemukan dengan grep. Sisi negatifnya, ini terlihat seperti akses lapangan. Sebagai akses lapangan yang jelas, tidak ada isyarat visual bahwa "ini melakukan sesuatu."

  • __ expr.await() (metode postfix) __: Sintaks metode postfix mirip dengan field postfix. Sisi baiknya, sintaks panggilan () menunjukkan kepada pembaca, "ini melakukan sesuatu." Dilihat secara lokal, hampir masuk akal dengan cara yang sama bahwa panggilan ke metode pemblokiran akan menghasilkan eksekusi dalam program multi-utas. Sisi negatifnya, ini sedikit lebih lama dan lebih berisik, dan menyamarkan perilaku ajaib await sebagai sebuah metode mungkin akan membingungkan. Kita mungkin mengatakan bahwa await adalah metode dalam pengertian terbatas yang sama bahwa call/cc dalam Skema adalah sebuah fungsi. Sebagai metode, kita perlu mempertimbangkan apakah Future::await(expr) harus bekerja secara konsisten dengan UFCS .

  • __ expr.await!() (makro postfix) __: Sintaks makro Postfix juga memanfaatkan "kekuatan titik". Makro ! bang menunjukkan "ini mungkin melakukan sesuatu yang ajaib." Sisi negatifnya, ini bahkan lebih ribut, dan sementara makro melakukan keajaiban, mereka biasanya tidak melakukan sihir ke kode sekitarnya seperti await . Juga di sisi negatifnya, dengan asumsi kita menstandarisasi sintaks makro postfix tujuan umum , mungkin ada masalah dengan terus memperlakukan await sebagai kata kunci.

  • __ expr@ , expr# , expr~ , dan simbol karakter tunggal lainnya__: Menggunakan satu karakter seperti yang kita lakukan dengan ? memaksimalkan keringkasan dan mungkin membuat sintaks postfix tampak lebih alami. Seperti ? , kita mungkin akan menghargai keringkasan ini jika await mulai memenuhi kode kita. Pada sisi negatifnya, sampai dan kecuali rasa sakit karena kode kita dikotori dengan await menjadi masalah, sulit untuk melihat konsensus terbentuk di sekitar trade-off yang melekat dalam mengadopsi sintaks seperti itu.

Saya ingin mengambil postingan untuk mengucapkan terima kasih kepada @traviscross atas postingan ringkasan ini! Mereka secara konsisten ditulis dengan baik dan saling terkait dengan baik. Itu sangat dihargai. :jantung:

Saya punya ide bahwa menambahkan "operator pipa" seperti F #, maka pengguna dapat menggunakan prefiks atau postfix (dengan perbedaan sintaks eksplisit).

// use `|>` for instance, Rust can choose other sigils if there are conflicts with current syntax
await expr
expr |> await

// and we can use this operator on normal function calls too
f(g(h(x))) 
x |> h |> g |> f
// this is more convenient than "postfix macro"
x.h!().g!().f!()

@traviscross Ringkasan yang luar biasa. Ada juga beberapa diskusi tentang menggabungkan sigil dan kata kunci, misalnya fut@await , jadi saya hanya akan menambahkan ini di sini untuk orang-orang yang datang ke utas ini.

Saya telah meminta pro dan kontra dari sintaks ini di sini . @earthengine mengatakan bahwa sigil selain @ dimungkinkan, seperti ~ . @BenoitZugmeyer menyukai @await , dan menanyakan apakah makro postfix ala expr!await adalah ide yang bagus. @dpc berpendapat bahwa @await terlalu ad-hoc dan tidak terintegrasi dengan baik dengan apa yang sudah kita miliki, juga, bahwa Rust sudah sigil-heavy; @cenwangumass setuju bahwa ini terlalu ad-hoc. @newpavlov mengatakan bahwa bagian await terasa mubazir, terutama jika kami tidak menambahkan kata kunci serupa di masa mendatang. @nicoburns mengatakan sintaksnya bisa berfungsi dan tidak ada banyak masalah dengannya, tetapi itu terlalu ad-hoc sebagai solusi.

@traviscross ringkasan yang bagus!

$ 0,02 saya di urutan dari yang terburuk hingga yang terbaik menurut saya:

  • 3 jelas tidak bisa digunakan karena ini bahkan bukan pemanggilan metode.
  • 2 bukanlah bidang, ini sangat membingungkan, terutama bagi pendatang baru. memiliki await pada daftar penyelesaian tidak terlalu membantu. Setelah Anda menulis banyak sekali, Anda cukup menuliskannya sebagai fn atau extern . Penyelesaian tambahan bahkan lebih buruk daripada tidak sama sekali karena alih-alih metode / bidang yang berguna saya akan disarankan oleh kata kunci.
  • 4 Makro adalah sesuatu yang cocok di sini, tetapi tidak cocok bagi saya. Maksud saya asimetri dengan async menjadi kata kunci, seperti yang saya sebutkan di atas
  • 5 Sigil mungkin terlalu singkat dan sulit dikenali, tetapi ini adalah entitas yang terpisah dan mungkin diperlakukan demikian. Tidak terlihat mirip dengan yang lain sehingga tidak menimbulkan kebingungan bagi pengguna
  • 1 Pendekatan terbaik IMHO, itu hanya sigil yang mudah dikenali dan yang sudah menjadi kata kunci yang dipesan. Pemisahan ruang, seperti disebutkan di atas, merupakan keuntungan, bukan cacat. Tidak masalah untuk tetap ada saat tidak ada pemformatan yang dilakukan, tetapi dengan rustfmt itu bahkan kurang signifikan.

Sebagai seseorang yang sangat menantikan momen ini, inilah $ 0,02 saya juga.

Untuk sebagian besar, saya setuju dengan @Pzixel , kecuali pada dua poin terakhir, dalam arti bahwa saya akan menukarnya, atau, mungkin, bahkan memberikan keduanya sebagai opsi, seperti try!() / ? pasangan. Saya masih melihat orang-orang berdebat mendukung try!() lebih dari ? untuk tujuan kejelasan, dan saya pikir memiliki beberapa agensi atas tampilan kode Anda dalam kasus khusus ini adalah pro daripada penipu , bahkan dengan mengorbankan dua sintaks yang berbeda.

Secara khusus, await kata kunci postfix bagus jika Anda menulis kode async Anda sebagai urutan langkah yang eksplisit, mis.

let val1 = my_async() await;
...
let val2 = another_async(val1) await;
...
let val3 = yet_another_async(val2) await;

Di sisi lain, Anda mungkin lebih suka melakukannya sesuatu yang lebih rumit, dalam gaya rangkaian metode Rust-y yang khas misalnya

let my_final_value = commit(get_some_data()
                        .and_then(|s| get_another_data(s))
                        .or_else(|s| report_error(s))~);

Saya pikir dalam kasus ini sudah cukup jelas dari konteks bahwa kita berurusan dengan masa depan, dan verbositas keyboard await berlebihan. Membandingkan:

let my_final_value = commit(get_some_data()
                        .and_then(|s| get_another_data(s))
                        .or_else(|s| report_error(s)) await);

Satu hal lain yang saya suka tentang sintaksis simbol tunggal postfix adalah menjelaskan bahwa simbol itu milik ekspresi , sedangkan (bagi saya, secara pribadi) await berdiri bebas terlihat sedikit ... hilang ? :)

Saya rasa apa yang ingin saya katakan adalah bahwa await terlihat lebih baik dalam pernyataan , sedangkan postfix simbol tunggal terlihat lebih baik dalam ekspresi .

Ini hanya pikiran saya.

Karena ini sudah menjadi induk dari semua utas pelepasan sepeda, saya ingin menambahkan sigil lain yang sejauh ini belum disebutkan AFAICT: -> .

Idenya adalah bahwa itu mencerminkan -> dari deklarasi tipe kembalian dari fungsi yang mengikutinya, yang - fungsinya menjadi async - adalah tipe kembalian yang ditunggu.

async fn send() -> Result<Response, HttpError> {...}
async fn into_json() -> Result<Json, EncodingError> {...}

let body: MyResponse = client.get("http://api").send()->?.into_json()->?;

Di atas, apa yang Anda dapatkan dari send()-> adalah Result<Response, HttpError> , seperti yang tertulis di deklarasi fungsi.

Ini adalah $ 0,02 saya setelah membaca sebagian besar pembahasan di atas dan mempertimbangkan opsi yang diusulkan selama beberapa hari. Tidak ada alasan untuk memberikan pendapat saya bobot apa pun - saya hanyalah orang acak di internet. Saya mungkin tidak akan berkomentar lebih jauh karena saya berhati-hati agar tidak menambah keributan pada diskusi.


Saya suka sigil postfix. Ada prioritas untuk sigils postfix dan saya pikir itu akan terasa konsisten dengan bahasa lainnya. Saya tidak memiliki preferensi khusus tentang sigil tertentu yang digunakan - tidak ada yang menarik tetapi saya pikir itu akan memudar dengan familiar. Sigil menghasilkan noise paling sedikit dibandingkan opsi postfix lainnya.

Saya tidak keberatan dengan ide untuk mengganti . (saat merangkai) dengan -> untuk menunggu. Ini masih ringkas dan tidak ambisius tetapi saya lebih suka sesuatu yang dapat digunakan dalam konteks yang sama seperti ? .

Saya sangat tidak menyukai opsi postfix lain yang telah disajikan. await bukan bidang atau metode dan oleh karena itu terasa sepenuhnya tidak konsisten dengan bahasa lain untuk konstruksi aliran kontrol untuk disajikan seperti itu. Tidak ada makro atau kata kunci postfix dalam bahasa tersebut, jadi itu juga tampak tidak konsisten.

Jika saya baru mengenal bahasa tersebut, tidak mengetahuinya sepenuhnya, maka saya akan berasumsi bahwa .await atau .await() bukanlah fitur bahasa khusus dan merupakan bidang atau metode pada suatu jenis - seperti halnya dengan setiap bidang dan metode lain dalam bahasa yang akan dilihat pengguna. Setelah mendapatkan pengalaman lagi, jika .await!() dipilih, pengguna mungkin berusaha mempelajari bagaimana mendefinisikan makro postfix mereka sendiri (seperti mereka telah belajar mendefinisikan makro prefiks mereka sendiri) - mereka tidak bisa. Jika pengguna tersebut melihat sigil, mereka mungkin perlu mencari dokumentasi (seperti ? ), tetapi mereka tidak akan bingung dengan hal lain dan membuang waktu mencoba menemukan definisi .await() atau dokumentasi untuk field .await .

Saya suka awalan await { .. } . Pendekatan ini memiliki prioritas yang jelas tetapi memiliki masalah (yang telah dibahas secara rinci). Meskipun demikian, menurut saya akan bermanfaat bagi mereka yang lebih suka menggunakan kombinator. Saya tidak ingin ini menjadi satu-satunya pilihan yang diimplementasikan, karena tidak ergonomis dengan method chaining, tapi saya pikir ini akan melengkapi sigil postfix dengan baik.

Saya tidak suka opsi awalan lainnya, mereka tidak merasa konsisten dengan konstruksi aliran kontrol lain dalam bahasa tersebut. Sama halnya dengan metode postfix, fungsi awalan tidak konsisten, tidak ada fungsi global lainnya yang dibangun di dalam bahasa yang digunakan untuk aliran kontrol. Juga tidak ada makro yang digunakan untuk aliran kontrol (dengan pengecualian try!(..) tetapi itu tidak digunakan lagi karena kami memiliki solusi yang lebih baik - sigil postfix).


Hampir semua preferensi saya bermuara pada apa yang terasa alami dan konsisten (bagi saya). Solusi mana pun yang dipilih harus diberi waktu untuk eksperimen sebelum stabilisasi - pengalaman langsung akan menjadi penilai yang jauh lebih baik tentang opsi mana yang terbaik daripada spekulasi.

Perlu juga dipertimbangkan bahwa mungkin ada mayoritas diam yang dapat memiliki pendapat yang sama sekali berbeda - mereka yang terlibat dalam diskusi ini (termasuk saya) tidak selalu mewakili semua pengguna Rust (ini tidak dimaksudkan sebagai sedikit atau dengan cara apapun menyinggung).

tl; dr Sigil Postfix adalah cara alami untuk mengekspresikan menunggu (karena prioritas) dan merupakan pendekatan yang ringkas dan konsisten. Saya akan menambahkan awalan await { .. } dan sebuah postfix @ (yang dapat digunakan dalam konteks sebagai ? ). Sangat penting bagi saya bahwa Rust tetap konsisten secara internal.

@Samuel

Saya pikir dalam kasus ini sudah cukup jelas dari konteks bahwa kita sedang berurusan dengan masa depan, dan verbositas keyboard await adalah berlebihan. Membandingkan:

Maaf, tapi saya bahkan tidak melihat sekilas ~ . Saya membaca ulang seluruh komentar dan pembacaan kedua saya lebih berhasil. Memberikan penjelasan yang bagus mengapa menurut saya sigil lebih buruk. Itu hanya pendapat, tapi baru saja dikonfirmasi sekali lagi.

Hal lain yang menentang sigils adalah Rust menjadi J. Sebagai contoh:

let res: MyResponse = client.get("https://my_api").send()?@?.json()?@?;`

?@? singkatan dari "fungsi yang dapat mengembalikan kesalahan atau masa depan, yang harus ditunggu, dengan kesalahan, jika ada, disebarkan ke pemanggil"

Saya ingin memiliki lebih banyak

let res: MyResponse = client.get("https://my_api").send()? await?.json()? await?;`

@kontenvideo

Karena ini sudah menjadi induk dari semua utas pelepasan sepeda, saya ingin menambahkan sigil lain yang sejauh ini belum disebutkan AFAICT: ->.

Membuat tata bahasa bergantung pada konteks tidak membuat segalanya lebih baik, tetapi hanya lebih buruk. Ini menyebabkan kesalahan yang lebih buruk dan waktu kompilasi yang lebih lambat.

Kami memiliki arti terpisah untuk -> , tidak ada hubungannya dengan async/await .

Saya lebih memilih awalan await { .. } dan, jika memungkinkan, awalan ! sigil.
Tanda seru secara halus akan menekankan kemalasan di masa depan. Mereka tidak akan mengeksekusi sampai diberi perintah dengan tanda seru.

Contoh di atas akan terlihat seperti ini:

let res: MyResponse = client.get("https://my_api").send()?!?.json()?!?;

Maaf jika pendapat saya tidak relevan karena saya hanya memiliki sedikit pengalaman asinkron dan tidak ada pengalaman dengan ekosistem fungsi asinkron Rust.

Namun, melihat .send()?!?.json()?!?; dan kombinasi lain seperti itu, saya memahami alasan dasar proposal berbasis sigil terlihat salah bagi saya.

Pertama, saya merasa bahwa sigils yang dirantai dengan cepat menjadi tidak dapat dibaca, di mana pun itu ?!? atau ?~? atau ?->? . Ini akan menjadi satu hal lagi yang pemula tersandung, menebak apakah itu satu operator atau beberapa. Informasinya terlalu padat.

Kedua, secara umum, menurut saya poin tunggu kurang umum dibandingkan poin penyebaran kesalahan dan lebih signifikan. Poin menunggu cukup signifikan bagi saya untuk pantas menjadi panggung dalam rantai itu sendiri, bukan "transformasi kecil" yang melekat pada tahap lain (dan terutama bukan "hanya salah satu dari transformasi kecil"). Saya mungkin akan baik-baik saja bahkan dengan bentuk kata kunci awalan saja (yang hampir memaksa menunggu untuk memutus rantai), tetapi secara umum itu terasa terlalu membatasi. Pilihan ideal saya mungkin akan menjadi makro seperti metode .await!() dengan kemungkinan masa depan untuk memperluas sistem makro untuk memungkinkan makro pengguna seperti metode.

Garis dasar minimal untuk stabilisasi, menurut saya, adalah operator awalan await my_future . Semua yang lain bisa mengikuti.

expr....await akan mencerminkan konteks untuk sesuatu yang terjadi sampai menunggu dan konsisten dengan operator rustlang. Juga async await adalah pola paralel .. tunggu tidak dapat diekspresikan sebagai metode atau properti sejenis

Saya memiliki kecenderungan untuk await!(foo) , tetapi karena orang lain telah menunjukkan bahwa melakukan hal itu akan memaksa kita untuk menunggu tanpa syarat dan dengan demikian menghalangi penggunaannya di masa depan sebagai operator hingga tahun 2021, saya akan menggunakan await { foo } sebagai preferensi saya. Adapun postfix menunggu, saya tidak punya pendapat khusus.

Saya tahu ini mungkin bukan tempat terbaik untuk berbicara tentang menunggu implisit, tetapi apakah sesuatu seperti menunggu implisit eksplisit bekerja? Jadi, pertama-tama kita memiliki awalan menunggu pendaratan:

await { future }?

Dan kemudian kami menambahkan sesuatu yang mirip dengan

let result = implicit await { client.get("https://my_api").send()?.json()?; }

atau

let result = auto await { client.get("https://my_api").send()?.json()?; }

Ketika memilih mode implisit, segala sesuatu antara {} secara otomatis ditunggu.

Ini telah menyatukan sintaks await dan akan menyeimbangkan kebutuhan untuk prefiks await, chaining, dan sejelas mungkin.

Saya memutuskan untuk rg --type csharp '[^ ]await' untuk mensurvei contoh tempat awalan kurang optimal. Mungkin saja tidak semua ini sempurna, tetapi itu adalah kode asli yang melalui peninjauan kode. (Contoh sedikit dibersihkan untuk menghapus hal-hal model domain tertentu.)

(await response.Content.ReadAsStringAsync()).Should().Be(text);

Menggunakan FluentAssertions sebagai cara yang lebih baik untuk melakukan sesuatu daripada MSTest biasa assert_eq! Assert.Equal .

var previous = (await branch.ListHistoryAsync(timestampUtc, null, cancellationToken, 1)).HistoryEntries.SingleOrDefault();

Hal umum tentang "lihat, saya benar-benar hanya perlu satu hal dari itu" adalah sekumpulan dari mereka.

id = id ?? (await this.storageCoordinator.GetDefaultWidgetAsync(cancellationToken)).Identity;

Lain lagi "Saya hanya membutuhkan satu properti". (Selain: "man, saya senang Rust tidak membutuhkan CancellationToken s.)

var pending = (await transaction.Connection.QueryAsync<EventView>(command)).ToList();

.collect() yang sama yang disebutkan orang di Rust.

foreach (var key in changes.Keys.Intersect((await neededChangesTask).Keys))

Saya telah berpikir tentang mungkin menyukai kata kunci postfix, dengan rustfmt baris baru setelahnya (dan setelah ? , jika ada), tetapi sepertinya inilah yang membuat saya berpikir bahwa baris baru tidak baik secara umum.

else if (!await container.ExistsAsync())

Salah satu yang langka di mana awalan sebenarnya berguna.

var response = (HttpWebResponse)await request.GetResponseAsync();

Ada beberapa pemeran, meskipun tentu saja pemeran adalah tempat lain di mana Rust adalah postfix tetapi C # adalah awalan.

using (var response = await this.httpClient.SendAsync(requestMsg))

Awalan vs postfix yang satu ini tidak masalah, tetapi saya pikir itu perbedaan menarik lainnya: karena C # tidak memiliki Drop , banyak hal yang akhirnya _needing_ untuk masuk ke variabel dan tidak dirantai.

beberapa @scottmcm contoh 's rustified di berbagai postfix varian:

// keyword
response.content.read_as_string()) await?.should().be(text);
// field
response.content.read_as_string()).await?.should().be(text);
// function
response.content.read_as_string()).await()?.should().be(text);
// macro
response.content.read_as_string()).await!()?.should().be(text);
// sigil
response.content.read_as_string())@?.should().be(text);
// sigil + keyword
response.content.read_as_string())@await?.should().be(text);
// keyword
let previous = branch.list_history(timestamp_utc, None, 1) await?.history_entries.single_or_default();
// field
let previous = branch.list_history(timestamp_utc, None, 1).await?.history_entries.single_or_default();
// function
let previous = branch.list_history(timestamp_utc, None, 1).await()?.history_entries.single_or_default();
// macro
let previous = branch.list_history(timestamp_utc, None, 1).await!()?.history_entries.single_or_default();
// sigil
let previous = branch.list_history(timestamp_utc, None, 1)@?.history_entries.single_or_default();
// sigil + keyword
let previous = branch.list_history(timestamp_utc, None, 1)@await?.history_entries.single_or_default();
// keyword
id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;
// field
id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async().await?.identity).await?;
// function
id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async().await()?.identity).await()?;
// macro
id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async().await!()?.identity).await!()?;
// sigil
id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async()@?.identity)@?;
// sigil + keyword
id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async()@await?.identity)@await?;

(terima kasih kepada @ Nemo157 untuk koreksinya)

// keyword
let pending = transaction.connection.query(command) await.into_iter().collect::<Vec<EventView>>();
// field
let pending = transaction.connection.query(command).await.into_iter().collect::<Vec<EventView>>();
// function
let pending = transaction.connection.query(command).await().into_iter().collect::<Vec<EventView>>();
// macro
let pending = transaction.connection.query(command).await!().into_iter().collect::<Vec<EventView>>();
// sigil
let pending = transaction.connection.query(command)@.into_iter().collect::<Vec<EventView>>();
// sigil + keyword
let pending = transaction.connection.query(command)@await.into_iter().collect::<Vec<EventView>>();

Bagi saya, setelah membaca ini, @ sigil tidak ada lagi, karena tidak terlihat terutama di depan ? .

Saya belum melihat siapa pun di utas ini membahas varian menunggu untuk Stream. Saya tahu ini di luar jangkauan tetapi haruskah kita memikirkannya?

Akan sangat disayangkan jika kami membuat keputusan yang ternyata adalah pemblokir menunggu di Streams.

// keyword
id = id.or_else(|| self.storage_coordinator.get_default_widget_async() await?.identity);

Anda tidak dapat menggunakan await di dalam penutupan seperti ini, perlu ada metode ekstensi tambahan pada Option yang mengambil penutupan asinkron dan mengembalikan Future itu sendiri (pada saat saya percaya bahwa tanda tangan metode ekstensi tidak mungkin ditentukan, tetapi mudah-mudahan kita akan mendapatkan cara untuk membuat penutupan asinkron dapat digunakan di beberapa titik) .

// keyword
id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;

(atau terjemahan yang lebih langsung dari kode menggunakan if let alih-alih kombinator)

@ Nemo157 Yeal, tetapi kami mungkin dapat melakukannya tanpa fungsi tambahan:

id = ok(id).transpose().or_else(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;

Tetapi pendekatan if let tampaknya lebih alami bagi saya.

Bagi saya, setelah membaca ini, @ sigil tidak ada lagi, karena tidak terlihat terutama di depan?.

Jangan lupa tentang skema penyorotan kode alternatif, bagi saya kode ini terlihat seperti ini:
1

Secara pribadi saya tidak berpikir bahwa "tembus pandang" di sini adalah masalah yang lebih besar daripada untuk mandiri ? .

Dan skema warna yang cerdas dapat membuatnya lebih terlihat (mis. Dengan menggunakan warna yang berbeda dari ? ).

@newpavlov Anda tidak dapat memilih skema warna di alat eksternal, misalnya di tab ulasan gitlab / github.

Dikatakan, bukan praktik yang baik untuk mengandalkan penyorotan saja. Orang lain mungkin memiliki preferensi lain.

Halo semua, saya adalah pembelajar Rust baru yang berasal dari C ++. Hanya ingin memberi komentar yang tidak disukai

id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;

atau

id = id.or_else_async(async || { 
    self.storage_coordinator.get_default_widget_async() await?.identity 
}) await?;

pada dasarnya tidak bisa dimengerti? await didorong ke akhir baris sementara fokus utama kami terkonsentrasi di awal (seperti saat Anda menggunakan mesin pencari).
Sesuatu seperti

id =  await? id.or_else_async(async || {
    let widget = await? self.storage_coordinator.get_default_widget_async();
    widget.identity
});

atau

id = auto await {
    id.or_else_async(async || { self.storage_coordinator.get_default_widget_async()?.identity })
}?;

disarankan sebelumnya terlihat jauh lebih baik bagi saya.

Saya setuju. Beberapa kata pertama adalah hal pertama yang dilihat orang saat memindai kode, hasil pencarian, paragraf teks, dll. Hal ini langsung membuat segala jenis postfix berada pada posisi yang kurang menguntungkan.

Untuk penempatan ? , saya yakin await? foo cukup berbeda dan mudah dipelajari.

Jika kita menstabilkan ini sekarang dan setelah satu tahun dua penggunaan, kita memutuskan bahwa kita benar-benar menginginkan sesuatu yang lebih baik untuk rangkaian, kita dapat mempertimbangkan makro postfix sebagai fitur umum.

Saya ingin mengusulkan variasi gagasan agar awalan dan postfix menunggu oleh
Kami dapat memiliki 2 hal:

  • awalan await kata kunci (misalnya, rasa dengan ikatan yang lebih kuat dari ? , tetapi ini kurang penting)
  • metode baru pada std::Future , misalnya fn awaited(self) -> Self::Output { await self } . Namanya juga bisa block_on , atau blocking , atau sesuatu yang lebih baik yang akan dibuat orang lain.

Ini akan memungkinkan penggunaan awalan "sederhana", dan juga memungkinkan untuk merangkai, sambil menghindari keharusan menunggu kata kunci kontekstual.

Secara teknis poin poin kedua juga dapat dicapai dengan makro postfix, dalam hal ini kita akan menulis .awaited!() .

Ini akan memungkinkan kode yang terlihat seperti ini:

let done = await delayed;

let value = await delayed_result?;

let value2 = await some.thing()?;

let value3 = some.other().thing().awaited()?;

let value4 = promise
        .awaited()
        .map_err(|e| e.into())?
        .obtain_other_future()
        .awaited();

Selain masalah lain, poin utama dari proposal ini adalah memiliki await sebagai blok penyusun dasar dari mekanisme tersebut, seperti match , dan kemudian memiliki penggabung untuk menyelamatkan kita dari keharusan mengetik banyak semua jenis kata kunci dan tanda kurung. Saya pikir ini mensyaratkan bahwa mereka dapat diajar dengan cara yang sama: pertama yang sederhana await , kemudian untuk menghindari terlalu banyak tanda kurung seseorang dapat menggunakan .awaited() dan meninggalkannya.


Sebagai alternatif, versi yang lebih ajaib dapat sepenuhnya menjatuhkan kata kunci await , dan mengandalkan metode ajaib .awaited() pada std :: Future, yang tidak dapat diterapkan oleh orang lain yang menulis masa depan mereka sendiri, tapi saya pikir ini akan cukup berlawanan dengan intuisi dan terlalu banyak kasus khusus.

metode baru pada std::Future , misalnya fn awaited(self) -> Self::Output { await self }

Saya cukup yakin itu tidak mungkin (tanpa membuat fungsi ajaib), karena untuk menunggu di dalamnya perlu async fn awaited(self) -> Self::Output { await self } , yang masih harus await ed. Dan jika kita berpikir untuk membuat fungsi ajaib, itu mungkin juga menjadi kata kunci, IMO.

Sudah ada Future::wait (meskipun tampaknya 0,3 belum memilikinya?), Yang menjalankan pemblokiran thread di masa mendatang.

Masalahnya adalah bahwa _ inti dari await adalah untuk _tidak_ memblokir utas_. Jika sintaks untuk await adalah prefiks dan kita menginginkan opsi postfix, itu harus berupa makro postfix dan bukan metode.

Dan jika Anda akan mengatakan menggunakan metode ajaib, sebut saja .await() , yang telah dibahas beberapa kali di utas, baik sebagai metode kata kunci dan keajaiban extern "rust-await-magic" "nyata "fn.

EDIT: scottmcm ninja'd me dan GitHub tidak memberi tahu saya (karena seluler?), Akan tetap meninggalkan ini.

@scottmcm Apakah Anda juga dapat mensurvei hitungan frekuensi di mana await tampak OK vs suboptimal? Saya pikir survei untuk penghitungan frekuensi prefiks vs postfix mungkin dapat membantu menjawab beberapa pertanyaan.

  1. Saya mendapat kesan bahwa sejauh ini kasus penggunaan terbaik dari postfix await adalah
client.get("https://my_api").send() await?.json() await?

sebanyak posting digunakan sebagai contoh. Mungkin saya melewatkan sesuatu, tetapi apakah ada kasus lain? Bukankah lebih baik jika Anda mengekstrak baris ini menjadi fungsi jika sering muncul di seluruh basis kode?

  1. Seperti yang saya bahas sebelumnya, kalau sebagian orang menulis
id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;

apa yang mereka lakukan sebenarnya menyembunyikan await daripada membuatnya eksplisit sehingga setiap titik hasil dapat dilihat dengan jelas .

  1. Kata kunci Postfix akan membutuhkan penemuan sesuatu yang tidak ada dalam bahasa utama lainnya. Jika tidak menawarkan hasil yang jauh lebih baik, menciptakannya tidak akan berguna.

Penantian didorong hingga akhir baris sementara fokus kami terutama terkonsentrasi di awal (seperti saat Anda menggunakan mesin pencari).

Saya jelas tidak dapat menyangkal bahwa orang memindai konten web menggunakan pola berbentuk F , @ dowchris97.

Namun, tidak diberikan bahwa mereka menggunakan pola yang sama untuk kode. Misalnya, yang lain di halaman ini sepertinya lebih cocok dengan cara orang mencari ? , dan dengan demikian mungkin mencari await :

Pola berbintik terdiri dari melewatkan potongan besar teks dan memindai seolah-olah mencari sesuatu yang spesifik, seperti tautan, angka, kata tertentu, atau sekumpulan kata dengan bentuk yang berbeda (seperti alamat atau tanda tangan).

Sebagai perbandingan, mari kita ambil contoh yang sama jika mengembalikan Result bukan impl Future :

let id = id.try_or_else(|| Ok(self.storage_coordinator.try_get_default_widget()?.identity))?;

Saya pikir itu cukup jelas daripada pola membaca berbentuk F untuk kode seperti itu tidak membantu menemukan ? s, juga tidak membantu jika itu dibagi menjadi beberapa baris seperti

let id = id.try_or_else(|| {
    let widget = self.storage_coordinator.try_get_default_widget()?;
    Ok(widget.identity)
})?;

Saya pikir deskripsi yang serupa dengan Anda dapat digunakan untuk menyatakan bahwa posisi postfix adalah "secara visual menyembunyikan ? daripada membuatnya eksplisit sehingga titik balik yang pernah kembali dapat terlihat dengan jelas", yang saya setuju adalah salah satu dari kekhawatiran asli tentang ? , tetapi tampaknya tidak menjadi masalah dalam praktiknya.

Jadi secara keseluruhan, saya pikir meletakkannya di tempat orang telah dilatih untuk memindai ? adalah cara terbaik untuk memastikan bahwa orang melihatnya. Saya pasti lebih suka mereka tidak harus menggunakan dua pemindaian berbeda secara bersamaan.

@tokopedia

Namun, tidak diberikan bahwa mereka menggunakan pola yang sama untuk kode. Misalnya, yang lain di halaman ini sepertinya lebih cocok dengan cara orang mencari ? , dan dengan demikian mungkin mencari await :

Pola berbintik terdiri dari melewatkan potongan besar teks dan memindai seolah-olah mencari sesuatu yang spesifik, seperti tautan, angka, kata tertentu, atau sekumpulan kata dengan bentuk yang berbeda (seperti alamat atau tanda tangan).

Juga pasti tidak ada bukti bahwa menggunakan pola bintik membantu memahami kode dengan lebih baik / lebih cepat. Terutama ketika manusia paling sering menggunakan pola bentuk F, yang akan saya sebutkan nanti.

Ambil baris ini sebagai contoh, kapan Anda mulai menggunakan pola berbintik, misalkan Anda belum pernah membaca ini sebelumnya.

id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;

Bagi saya, saya mulai ketika saya melihat async , lalu saya pergi ke await? , lalu self.storage_coordinator.get_default_widget_async() , lalu .identity , lalu saya akhirnya menyadari seluruh baris asinkron. Saya akan mengatakan ini jelas bukan pengalaman membaca yang saya suka. Salah satu alasannya adalah bahwa sistem bahasa tertulis kita tidak memiliki lompatan maju dan mundur semacam ini . Ketika Anda melompat, itu mengganggu pembangunan model mental dari apa yang dilakukan garis ini.

Sebagai perbandingan, apa yang dilakukan baris ini, bagaimana Anda tahu itu?

id = await? id.or_else_async(async || {
    let widget = await? self.storage_coordinator.get_default_widget_async();
    widget.identity
});

Segera setelah saya mencapai await? , saya langsung mendapat peringatan bahwa ini asynchronous. Saya kemudian membaca let widget = await? , sekali lagi, tanpa kesulitan, saya tahu ini asinkron, ada sesuatu yang terjadi. Saya merasa saya mengikuti pola bentuk F. Menurut https://thenextweb.com/dd/2015/04/10/how-to-design-websites-that-mirror-how-our-eyes-work/ , bentuk F adalah pola yang paling umum digunakan. Jadi, apakah kita akan merancang sistem yang sesuai dengan sifat manusia atau menciptakan sistem yang membutuhkan pendidikan khusus dan bekerja melawan sifat kita ? Saya lebih suka yang pertama. Perbedaannya akan semakin kuat jika garis asinkron dan normal disisipkan seperti ini

await? id.or_else_async(async || {
    let widget1 = await? self.storage_coordinator.get_default_widget_async();
    let result1 = do_some_wierd_computation_on(widget1.identity);
    let widget2 = await? self.network_coordinator.get_default_widget_async();
    let result2 = do_some_strange_computation_on(widget2.identity);
});

Apakah saya harus mencari await? lintas baris?

biarkan id = id.try_or_else (|| Oke (self.storage_coordinator.try_get_default_widget () ?. identitas)) ?;

Untuk ini, menurut saya perbandingannya tidak bagus. Pertama, ? tidak banyak mengubah model mental. Kedua, kesuksesan ? terletak pada kenyataan bahwa Anda tidak harus maju dan mundur di antara kata-kata. Perasaan saya saat membaca ke ? dalam .try_get_default_widget()? adalah, "ok, Anda mendapatkan hasil yang baik darinya". Itu dia. Saya tidak perlu melompat kembali untuk membaca sesuatu yang lain untuk memahami baris ini.

Jadi, kesimpulan saya secara keseluruhan adalah bahwa postfix dapat memberikan beberapa kemudahan yang terbatas saat menulis kode. Namun, ini dapat menyebabkan masalah yang lebih besar untuk membaca kode, yang menurut saya lebih sering terjadi daripada menulis.

Bagaimana ini akan terlihat dengan satu gaya rustfmt diusulkan dan penyorotan sintaks ( while -> async , match -> await ):

while fn foo() {
    identity = identity
        .or_else_async(while || {
            self.storage_coordinator
                .get_default_widget_async().match?
                .identity
        }).match?;
}

Saya tidak tahu tentang Anda, tapi saya langsung melihat match es.

(Hai @ CAD97 , saya sudah memperbaikinya!)

@bayu_joo

Jika Anda berpendapat bahwa ? tidak mengubah model mental, apa yang salah dengan argumen saya bahwa await tidak boleh mengubah model mental kode? (Inilah sebabnya mengapa saya lebih menyukai opsi menunggu implisit sebelumnya, meskipun saya yakin itu tidak cocok untuk Rust.)

Secara khusus, Anda membaca async di _beginning_ dari tajuk fungsi. Jika fungsinya sangat besar sehingga tidak muat di satu layar, hampir pasti terlalu besar dan Anda memiliki masalah keterbacaan lain yang lebih besar daripada menemukan poin await .

Setelah Anda async , Anda harus memegang konteks itu. Tapi pada titik itu semua menunggu adalah, adalah transformasi yang mengubah perhitungan yang ditangguhkan Future menjadi hasil Output dengan memarkir rangkaian eksekusi saat ini.

Tidak masalah bahwa ini berarti transformasi mesin status yang rumit. Itu adalah detail implementasi. Satu-satunya perbedaan dari utas OS dan pemblokiran adalah bahwa kode lain dapat berjalan di utas saat ini sementara Anda menunggu penghitungan yang ditangguhkan, jadi Sync tidak terlalu melindungi Anda. (Jika ada, saya akan membacanya sebagai persyaratan untuk async fn menjadi Send + Sync alih-alih disimpulkan dan diizinkan untuk menjadi thread-unsafe.)

Dalam gaya yang lebih berorientasi pada fungsi, widget dan hasil Anda akan terlihat seperti (ya, saya tahu ini tidak menggunakan Monad dan kemurnian nyata, dll, maafkan saya):

    let widget1 = await(get_default_widget_async(storage_coordinator(self)));
    let result1 = do_some_wierd_computation_on(identity(widget1));
    let widget2 = await(get_default_widget_async(network_coordinator(self)));
    let result2 = do_some_strange_computation_on(identity(widget2));

Tapi karena itu urutan terbalik dari pipeline proses, kerumunan fungsional menemukan operator "pipeline", |> :

    let widget1 = self |> storage_coordinator |> get_default_widget_async |> await;
    let result1 = widget1 |> identity |> do_some_wierd_computation_on;
    let widget2 = self |> network_coordinator |> get_default_widget_async |> await;
    let result2 = widget2 |> identity |> do_some_strange_computation_on;

Dan di Rust, operator pipelining itu adalah . , yang menyediakan pelingkupan dari apa yang bisa dipipel dan pencarian diarahkan tipe melalui aplikasi metode:

    let widget1 = self.storage_coordinator.get_default_widget_async().await();
    let result1 = widget1.identity.do_some_wierd_computation_on();
    let widget2 = self.network_coordinator.get_default_widget_async().await;
    let result2 = widget2.identity.do_some_strange_computation_on();

Ketika Anda menganggap . sebagai data pipelining dengan cara yang sama seperti |> , rantai yang lebih panjang yang sering terlihat di Rust mulai lebih masuk akal, dan ketika diformat dengan baik (seperti dalam contoh Centril) Anda tidak Jangan lewatkan keterbacaan karena Anda hanya memiliki alur transformasi vertikal pada data.

await tidak memberi tahu Anda "hei ini asinkron". async tidak. await hanyalah cara Anda memarkir dan menunggu penghitungan yang ditangguhkan, dan sangat masuk akal untuk membuatnya tersedia bagi operator pipelining Rust.

(Hai @Centril Anda lupa membuatnya menjadi async fn (atau while fn ), yang agak melemahkan maksud saya 😛)

apakah kita bisa mendefinisikan ulang pemanggilan makro atau tidak

m!(item1, item2)

sama seperti

item1.m!(item2)

jadi kita bisa menggunakan await baik gaya awalan maupun postfix

await!(future)

dan

future.await!()

@ CAD97

await tidak memberi tahu Anda "hei ini asinkron". async tidak. await hanyalah cara Anda memarkir dan menunggu penghitungan yang ditangguhkan, dan sangat masuk akal untuk membuatnya tersedia bagi operator pipelining Rust.
Ya, saya rasa saya mengerti dengan benar tetapi tidak menulis dengan teliti.

Saya juga mengerti maksud Anda. Tapi tidak yakin, saya masih berpikir menempatkan await di depan akan jauh lebih baik.

Saya tahu |> digunakan dalam bahasa lain untuk mengartikan sesuatu yang lain , tetapi itu terlihat cukup bagus dan sangat jelas bagi saya di Rust sebagai pengganti awalan await :

// A
if |> db.is_trusted_identity(recipient.clone(), message.key.clone())? {
    info!("recipient: {}", recipient);
}

// B
match |> db.load(message.key)? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = |> client.get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .send()?
    .error_for_status()?;

// D
let mut res: InboxResponse =
    |> client.get(inbox_url)
        .headers(inbox_headers)
        .send()?
        .error_for_status()?
    |> .json()?;

// E
let mut res: Response =
    |> client.post(url)
        .multipart(form)
        .headers(headers.clone())
        .send()?
        .error_for_status()?
    |> .json()?;

// F
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = |> self.request(url, Method::GET, None, true)?
               |> .res.json::<UserResponse>()?
                  .user
                  .into();

    Ok(user)
}

Argumen tentang urutan membaca akan berlaku sama baiknya pada operator ? menggantikan try!() . Lagi pula, "hai ini mungkin akan menghasilkan" itu penting, tetapi "hai ini mungkin kembali lebih awal" juga penting, jika tidak lebih. Dan memang, kekhawatiran tentang visibilitas muncul berulang kali dalam diskusi bikeshed tentang ? (termasuk utas internal ini dan ? dan await muncul di sisi berlawanan dari ekspresi, hanya karena, pada dasarnya, komunitas berubah pikiran tentang betapa pentingnya visibilitas.

Argumen tentang urutan membaca akan berlaku sama baiknya pada operator ? menggantikan try!() . Lagi pula, "hai ini mungkin akan menghasilkan" itu penting, tetapi "hai ini mungkin kembali lebih awal" juga penting, jika tidak lebih. Dan memang, kekhawatiran tentang visibilitas muncul berulang kali dalam diskusi bikeshed tentang ? (termasuk utas internal ini dan ? dan await muncul di sisi berlawanan dari ekspresi, hanya karena, pada dasarnya, komunitas berubah pikiran tentang betapa pentingnya visibilitas.

Ini benar-benar bukan tentang apa yang dilakukan kode Anda. Ini tentang model mental Anda tentang apa yang dilakukan kode Anda, apakah model tersebut dapat dengan mudah dibuat atau tidak, apakah ada guncangan atau tidak, apakah itu intuitif atau tidak. Ini bisa sangat berbeda satu sama lain.

Saya tidak ingin memperdebatkan ini lebih jauh. Saya pikir komunitas masih jauh dari penyelesaian pada solusi postfix meskipun banyak orang di sini mungkin mendukungnya. Tapi menurut saya mungkin ada solusi:

Mozilla yang membuat Firefox, bukan? Ini semua tentang UI / UX! Bagaimana dengan penelitian HCI yang serius tentang masalah ini? Jadi kami benar-benar dapat meyakinkan satu sama lain menggunakan data bukan dugaan.

@ dowchris97 Ada (hanya) dua contoh membandingkan kode dunia nyata di utas ini: Satu perbandingan ~ 24k baris dengan database dan reqwest dan satu perbandingan yang mencontohkan mengapa C # bukan perbandingan yang akurat untuk basis sintaks Rust . Keduanya ternyata dalam kode Rust realworld, menunggu di postfix tampak lebih alami, dan tidak mengalami masalah yang dimiliki bahasa lain tanpa semantik perpindahan nilai alami. Kecuali jika seseorang muncul dengan contoh dunia nyata berukuran layak lainnya yang cenderung menunjukkan sebaliknya, saya cukup yakin bahwa sintaks prefiks adalah kebutuhan yang dikenakan oleh bahasa lain pada diri mereka sendiri karena mereka tidak memiliki nilai semantik yang jelas dari pipelining Rust (di mana membaca kode idiomatik dari kiri-ke-kanan hampir selalu melakukan persis seperti yang disarankan model mental).

Sunting: Hanya jika tidak cukup jelas, tidak satupun dari C #, Python, C ++, Javascript memiliki metode anggota yang mengambil self dengan nilai alih-alih referensi. C ++ paling dekat dengan referensi rvalue tetapi urutan destruktornya masih membingungkan dibandingkan dengan Rust.

Saya pikir argumen bahwa await lebih baik sebagai awalan bukan berasal dari bagaimana Anda harus mengubah model mental kode, tetapi lebih pada bagaimana karat kita memiliki kata kunci awalan, tetapi tidak memiliki kata kunci postfix (untuk postfixes, rust menggunakan sigils seperti yang dicontohkan oleh ? ). Itulah mengapa await foo() terasa lebih mudah dibaca daripada foo() await dan mengapa beberapa orang menginginkan @ di akhir pernyataan dan tidak suka memiliki await sana.

Untuk alasan yang sama .await terasa aneh untuk digunakan: operator titik digunakan untuk mengakses bidang dan metode struct (yang juga mengapa itu tidak dapat dilihat sebagai operator pipeline murni), jadi memiliki .await seperti mengatakan "titik digunakan untuk mengakses bidang dan metode dari sebuah struct atau untuk mengakses await , yang bukan merupakan bidang atau fungsi".

Secara pribadi, saya ingin melihat awalan await atau sigil postfix (tidak harus @ ).

Keduanya ternyata di realworld kode Rust, menunggu di postfix tampak lebih natural

Itu pernyataan yang kontroversial. Contoh reqwest hanya menampilkan satu versi sintaks postfix.

Pada catatan lain, jika diskusi ini mengarah pada pemungutan suara siapa yang suka apa lagi, harap sebutkan di reddit sehingga orang tidak mengeluh seperti yang mereka lakukan dengan impl Trait dalam argumen fungsi.

@ eugene2k Untuk diskusi mendasar tentang apakah postfix cocok dengan model mental programmer Rust, kebanyakan atau semua sintaks postfix kira-kira pada skala yang sama dibandingkan dengan prefiks. Saya rasa tidak ada perbedaan keterbacaan yang signifikan antara varian postfix seperti antara prefix dan postfix. Lihat juga perbandingan tingkat rendah saya tentang prioritas operator yang menyimpulkan bahwa semantik mereka sama di sebagian besar penggunaan, jadi ini adalah masalah operator mana yang paling baik menyampaikan makna (saat ini saya lebih suka sintaks panggilan fungsi yang sebenarnya, tetapi jangan memiliki preferensi yang kuat atas yang lain).

@ eugene2k Keputusan di Rust tidak pernah dibuat dengan voting. Rust bukanlah demokrasi, itu meritokrasi.

Tim inti / lang melihat semua argumen dan perspektif yang berbeda dan kemudian memutuskan. Keputusan ini dilakukan dengan musyawarah (di antara anggota tim), bukan pemungutan suara.

Meskipun tim Rust benar-benar memperhitungkan keinginan komunitas secara keseluruhan, pada akhirnya mereka memutuskan berdasarkan apa yang menurut mereka terbaik untuk Rust dalam jangka panjang.

Cara terbaik untuk memengaruhi Rust adalah dengan menyajikan informasi baru , atau membuat argumen baru , atau menunjukkan perspektif baru .

Mengulangi argumen yang ada atau mengatakan "saya juga" (atau yang serupa) tidak meningkatkan kemungkinan proposal diterima. Proposal tidak pernah diterima berdasarkan popularitas.

Itu juga berarti berbagai suara positif / suara negatif di utas ini sama sekali tidak penting bagi proposal mana yang diterima.

(Saya tidak merujuk kepada Anda secara khusus, saya menjelaskan bagaimana segala sesuatunya bekerja demi semua orang di utas ini.)

@Pauan Telah disebutkan sebelumnya bahwa tim inti / lang melihat berbagai proposal dan kemudian memutuskan. Tetapi keputusan "yang lebih mudah dibaca" adalah keputusan pribadi. Tidak ada argumen logis sebanyak apa pun yang disajikan kepada pembuat keputusan yang akan mengubah pandangan pribadi mereka tentang apa yang mereka sukai. Lebih jauh, argumen seperti 'beginilah cara orang membaca hasil penelusuran' dan 'penelitian yang dilakukan oleh penelitian ini dan itu menunjukkan bahwa orang lebih suka ini dan itu' mudah diperebutkan (yang bukan hal yang buruk). Apa yang mungkin mengubah pikiran pembuat keputusan adalah melihat dan tidak menyukai hasil penerapan keputusan mereka dalam konteks yang tidak mereka pikirkan. Jadi, ketika semua konteks ini telah dilihat dan para pembuat keputusan di tim menyukai satu pendekatan, sementara sebagian besar pengguna lain, yang tidak berada dalam tim menyukai pendekatan lain, apa yang harus menjadi keputusan akhir?

Jadi, ketika semua konteks ini telah dilihat dan para pembuat keputusan di tim menyukai satu pendekatan, sementara sebagian besar pengguna lain, yang tidak berada dalam tim menyukai pendekatan lain, apa yang harus menjadi keputusan akhir?

Keputusan selalu dilakukan oleh tim. Titik. Begitulah aturan sengaja dirancang.

Dan fitur tersebut sering diterapkan oleh anggota tim. Dan anggota tim juga telah membangun kepercayaan di dalam komunitas. Jadi mereka memiliki otoritas de jure dan de facto.

Jika situasinya berubah (mungkin berdasarkan umpan balik) dan anggota tim berubah pikiran, maka mereka dapat mengubah keputusan mereka. Namun meski begitu, keputusan selalu dilakukan oleh anggota tim.

Seperti yang Anda katakan, keputusan sering kali melibatkan sejumlah subjektivitas, sehingga tidak mungkin menyenangkan semua orang, tetapi keputusan harus dibuat. Untuk mencapai keputusan, sistem yang digunakan Rust didasarkan pada anggota tim yang mencapai konsensus.

Setiap diskusi tentang apakah Rust harus diatur secara berbeda adalah di luar topik dan harus didiskusikan di tempat lain.

(PS Saya bukan tim inti atau lang, jadi saya tidak memiliki kewenangan apa pun dalam keputusan ini, jadi saya harus tunduk kepada mereka, sama seperti Anda)

@Tokopedia

Saya tidak berpikir ada banyak perbedaan keterbacaan yang signifikan antara varian postfix

Saya tidak setuju. Saya menemukan foo().await()?.bar().await()? lebih mudah dibaca daripada foo() await?.bar() await? atau bahkan foo()@?.bar()@? Meskipun demikian, saya merasa memiliki metode yang tidak benar-benar metode menetapkan preseden buruk.

Saya ingin mengusulkan ide lain. Saya setuju bahwa prefiks menunggu tidak mudah untuk dihubungkan dengan fungsi lain. Bagaimana dengan sintaks postfix ini: foo(){await}?.bar()?{await} ? Tidak bisa disamakan dengan pemanggilan fungsi dan menurut saya cukup mudah untuk dibaca secara berantai.

Dan proposal lain yang saya tulis. Mari pertimbangkan sintaks panggilan metode berikut:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.[await request(url, Method::GET, None, true)]?
        .res.[await json::<UserResponse>()]?
        .user
        .into();

    Ok(user)
}

Apa yang membuatnya unik di antara proposal lainnya:

  • Tanda kurung siku membuat prioritas dan cakupan jauh lebih bersih.
  • Sintaks cukup dapat dikembangkan untuk memungkinkan penghapusan binding sementara dalam konteks lain juga.

Saya pikir ekstensibilitas adalah keuntungan terkuat di sini. Sintaks ini akan memungkinkan untuk mengimplementasikan fitur bahasa yang dalam bentuk umumnya tidak mungkin dilakukan di Rust karena rasio kompleksitas / kegunaan yang tinggi. Daftar konstruksi bahasa yang mungkin disediakan di bawah ini:

  1. Menunda semua operator awalan (termasuk await - begitulah seharusnya bekerja):
let result = api.method().[await returns_future()];
let cond = long.method().chain().[!is_empty()];
let val = something.[*returns_ref()];
  1. Fungsionalitas operator pipa:
// from https://users.rust-lang.org/t/pipe-results-like-elixir/11175/19
let deserialized: DataType =
    Path::new("path/to/file.json")
        .[File::open(&it)].expect("file not found")
        .[serde_json::from_reader(it)].expect("error while reading json");
  1. Mengganti pengembalian fungsi:
let sorted_vec = iter
    .map(mapper)
    .collect::<Vec<_>>()
    .[sort(),];
  1. Fungsionalitas layu:
consume(&HashMap::new(). [
    insert("key1", val1),
    insert("key2", val2),
]);
  1. Pemisahan rantai:
let sf = surface(). [
    draw_circle(ci_dimens).draw_rectangle(rect_dimens).finish()?,
    draw_something_custom().finish()?,
];
  1. Makro Postfix:
let x = long().method().[dbg!(it)].chain();

Saya pikir memperkenalkan jenis sintaks baru (bidang ajaib, makro postfix, tanda kurung) memiliki dampak yang lebih besar pada bahasa daripada fitur ini saja, dan seharusnya memerlukan RFC.

Apakah ada repositori yang sudah banyak menggunakan await ? Saya akan menawarkan untuk menulis ulang bagian yang lebih besar dalam setiap gaya yang diusulkan, sehingga kita bisa mendapatkan perasaan yang lebih baik tentang bagaimana tampilannya dan seberapa mudah kodenya.

Saya menulis ulang dengan pembatas wajib:

// A
if await {db.is_trusted_identity(recipient.clone(), message.key.clone())}? {
    info!("recipient: {}", recipient);
}

// B
match await {db.load(message.key)}  {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = await { client
    .get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .send()
}?.error_for_status()?;

// D
let mut res = await {client.get(inbox_url).headers(inbox_headers).send()}?.error_for_status()?;

let mut res: InboxResponse = await {res.json()}?;

// E
let mut res = await { client
    .post(url)
    .multipart(form)
    .headers(headers.clone())
    .send()
}?.error_for_status()?;

let res: Response = await {res.json()}?;

// F
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let (_, mut res) = await {self.request(url, Method::GET, None, true)}?;
    let user = await {res.json::<UserResponse>()}?
        .user
        .into();

    Ok(user)
}

Ini hampir identik dengan await!() . Jadi, itu terlihat cantik! Setelah menggunakan await!() selama satu atau dua tahun, mengapa Anda tiba-tiba membuat postfix menunggu yang tidak muncul di mana pun dalam sejarah bahasa pemrograman.

Hah. @ I60R expr.[await it.foo()] Sintaks dengan kontekstual it kata kunci cukup rapi. Saya tidak berharap untuk menyukai proposal sintaks baru yang mewah, tetapi itu benar-benar cukup bagus, adalah penggunaan ruang sintaks yang cerdas (karena IIRC .[ saat ini tidak valid sintaks di mana pun), dan akan menyelesaikan lebih banyak lagi masalah dari sekedar menunggu.

Setuju bahwa itu pasti akan membutuhkan RFC, dan itu mungkin bukan pilihan terbaik. Tapi saya pikir itu adalah poin lain di sisi penyelesaian sintaks prefiks untuk await untuk saat ini, dengan pengetahuan bahwa ada sejumlah opsi untuk memecahkan masalah "operator prefiks canggung untuk berantai" dengan cara yang lebih umum yang menguntungkan lebih dari sekadar async / menunggu di masa mendatang.

Dan proposal lain yang saya tulis. Mari pertimbangkan sintaks panggilan metode berikut:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.[await request(url, Method::GET, None, true)]?
        .res.[await json::<UserResponse>()]?
        .user
        .into();

    Ok(user)
}

Apa yang membuatnya unik di antara proposal lainnya:

* Square brackets makes precedence and scoping much cleaner.

* Syntax is extensible enough to allow temporary bindings removal in other contexts as well.

Saya pikir ekstensibilitas adalah keuntungan terkuat di sini. Sintaks ini akan memungkinkan untuk mengimplementasikan fitur bahasa yang dalam bentuk umumnya tidak mungkin dilakukan di Rust karena rasio kompleksitas / kegunaan yang tinggi. Daftar konstruksi bahasa yang mungkin disediakan di bawah ini:

1. Deferring of all prefix operators (including `await` - it's how it supposed to work):
let result = api.method().[await returns_future()];
let cond = long.method().chain().[!is_empty()];
let val = something.[*returns_ref()];
1. Pipeline operator functionality:
// from https://users.rust-lang.org/t/pipe-results-like-elixir/11175/19
let deserialized: DataType =
    Path::new("path/to/file.json")
        .[File::open(&it)].expect("file not found")
        .[serde_json::from_reader(it)].expect("error while reading json");
1. Overriding function return:
let sorted_vec = iter
    .map(mapper)
    .collect::<Vec<_>>()
    .[sort(),];
1. Wither functionality:
consume(&HashMap::new(). [
    insert("key1", val1),
    insert("key2", val2),
]);
1. Chain splitting:
let sf = surface(). [
    draw_circle(ci_dimens).draw_rectangle(rect_dimens).finish()?,
    draw_something_custom().finish()?,
];
1. Postfix macros:
let x = long().method().[dbg!(it)].chain();

Benar-benar tidak bisa dimengerti.

@tajimaha Melihat contoh Anda, menurut saya await {} sebenarnya jauh lebih baik daripada await!() jika kita menggunakan pembatas wajib, karena ini menghindari masalah "terlalu banyak tanda kurung" yang dapat menyebabkan keterbacaan masalah dengan sintaks await!() .

Membandingkan:
`` c #
menunggu {foo.bar (url, false, qux.clone ())};

with

```c#
await!(foo.bar(url, false, qux.clone()));

(ps Anda bisa mendapatkan penyorotan sintaks async dan await untuk contoh sederhana dengan mengatur bahasa ke c #.)

@nicoburns Anda dapat menggunakan () , {} , atau [] dengan makro.

@sgrif Itu poin yang bagus. Dan karena ini kasusnya, saya menyarankan bahwa "kata kunci awalan dengan pembatas wajib" sangat tidak masuk akal sebagai opsi. Karena merusak konsistensi dengan makro lain yang pada dasarnya tidak bermanfaat.

(FWIW, saya masih berpikir bahwa "awalan tanpa pembatas" dengan solusi umum seperti makro postfix atau saran @ I60R untuk postfix paling masuk akal. Tetapi opsi "tetap dengan makro yang ada" berkembang pada saya ...)

Saya menyarankan bahwa "kata kunci awalan dengan pembatas wajib" tidak terlalu masuk akal sebagai opsi. Karena merusak konsistensi dengan makro lain yang pada dasarnya tidak bermanfaat.

Mengapa ini tidak masuk akal dan mengapa kata kunci yang tidak memiliki sintaks yang sama dengan makro menjadi masalah?

@tajja

Saya menyarankan bahwa "kata kunci awalan dengan pembatas wajib" tidak terlalu masuk akal sebagai opsi. Karena merusak konsistensi dengan makro lain yang pada dasarnya tidak bermanfaat.

Mengapa ini tidak masuk akal dan mengapa kata kunci yang tidak memiliki sintaks yang sama dengan makro menjadi masalah?

Nah jika await adalah makro, itu akan memiliki keuntungan karena tidak menambahkan sintaks tambahan apa pun ke bahasa. Sehingga mengurangi kompleksitas bahasa tersebut. Namun, ada argumen bagus untuk menggunakan kata kunci: return , break , continue , dan konstruksi pengubah aliran kontrol lainnya juga merupakan kata kunci. Tapi ini semua bekerja tanpa pembatas, jadi agar konsisten dengan konstruksi ini, await perlu bekerja tanpa pembatas juga.

Jika Anda memiliki await-with-mandatory delimiters maka Anda memiliki:

// Macros using `macro!(foo);` syntax 
format!("{}", foo);
println!("hello world");

// Normal keywords using `keyword foo;`
continue foo;
return foo;

// *and* the await keyword which is kind of in between the other two syntaxes:
await(foo);
await{foo};

Ini berpotensi membingungkan karena Anda sekarang harus mengingat 3 bentuk sintaks, bukan 2. Dan melihat sebagai kata kunci-dengan-pemisah-wajib tidak menawarkan manfaat apa pun atas sintaks makro, saya pikir akan lebih baik jika tetap menggunakan standar sintaks makro jika kita ingin menerapkan pembatas (yang saya sama sekali tidak yakin bahwa kita harus melakukannya).

Sebuah pertanyaan untuk mereka yang menggunakan banyak async / await di Rust hari ini: seberapa sering Anda menunggu fungsi / metode vs variabel / bidang?

Konteks:

Saya tahu bahwa di C # itu umum untuk melakukan hal-hal yang bermuara pada pola ini:

var fooTask = this.FooAsync();
var bar = await this.BarAsync();
var foo = await fooTask;

Dengan begitu keduanya berjalan secara paralel. (Beberapa orang akan mengatakan bahwa Task.WhenAll harus digunakan di sini, tetapi perbedaan kinerja kecil, dan itu membuat kode lebih berantakan karena harus melalui indeks array.)

Tapi menurut pemahaman saya, di Rust, itu tidak akan benar-benar berjalan secara paralel sama sekali, karena poll untuk fooTask tidak akan dipanggil sampai bar mendapatkan nilainya, dan mungkin _perlukan_ untuk menggunakan kombinator

let (foo, bar) = when_all!(
    self.foo_async(),
    self.bar_async(),
).await;

Jadi mengingat itu, saya ingin tahu apakah seseorang secara teratur akhirnya memiliki masa depan dalam variabel atau bidang yang perlu Anda tunggu, atau jika seseorang hampir selalu menunggu ekspresi panggilan. Karena jika yang terakhir, ada varian pemformatan kecil dari kata kunci postfix yang belum benar-benar kita diskusikan: kata kunci postfix _no-space_.

Saya belum banyak memikirkan apakah itu bagus, tetapi mungkin untuk menulis kode seperti itu

client.get("https://my_api").send()await?.json()await?

(Saya sebenarnya tidak ingin berdiskusi tentang rustfmt, seperti yang telah saya katakan, tetapi saya ingat bahwa salah satu alasan untuk tidak menyukai kata kunci postfix _was_ ruang yang memisahkan potongan visual.)

Jika kita melakukannya, sebaiknya gunakan sintaks .await untuk leverage
kekuatan titik, bukan?

Sebuah pertanyaan untuk mereka yang menggunakan banyak async / await di Rust hari ini: seberapa sering Anda menunggu fungsi / metode vs variabel / bidang?

Tapi menurut pemahaman saya, di Rust, itu tidak akan benar-benar berjalan secara paralel sama sekali [...]

Benar. Dari basis kode yang sama seperti sebelumnya, berikut ini contohnya:

let self__ = self_.clone();
let responses: Vec<Response> = {
    let futures = all_ids.into_iter().map(move |id| {
        self__.request(URL, Method::GET, vec![("info".into(), id.into())])
            .and_then(|mut response| response.json().from_err())
    });

    await!(futures_unordered(futures).collect())?
};

Jika saya menulis ulang penutupan dengan penutupan async :

let self__ = self_.clone();
let responses: Vec<Response> = {
    let futures = all_ids.into_iter().map(async move |id| {
        let mut res =
            await!(self__.request(URL, Method::GET, vec![("info".into(), id.into())]))?;

        Ok(await!(res.json())?)
    });

    await!(futures_unordered(futures).collect())?
};

Jika saya beralih ke sintaks .await (dan menyatukannya):

let self__ = self_.clone();
let responses: Vec<Response> =
    futures_unordered(all_ids.into_iter().map(async move |id| {
        Ok(self__
            .request(URL, Method::GET, vec![("info".into(), id.into())]).await?
            .json().await?)
    }))
    .collect().await?;

Apakah ada repositori yang sudah banyak menggunakan await? Saya menawarkan untuk menulis ulang bagian yang lebih besar dalam setiap gaya yang diusulkan

@gralpli Sayangnya, tidak ada yang bisa saya buka dengan menggunakan await! . Ini pasti lebih cocok untuk kode aplikasi saat ini (terutama karena sangat tidak stabil).

let self__ = self_.clone();
let responses: Vec<Response> =
    futures_unordered(all_ids.into_iter().map(async move |id| {
        Ok(self__
            .request(URL, Method::GET, vec![("info".into(), id.into())]).await?
            .json().await?)
    }))
    .collect().await?;

Baris-baris ini secara tepat menunjukkan bagaimana kode dikacaukan dengan terlalu banyak menggunakan postfix dan chaining .

Mari kita lihat versi awalannya:

let func = async move |id| {
    let req = await { self.request(URL, Method::GET, vec![("info".into(), id.into())]) }?;
    Ok(await(req.json())?)
}
let responses: Vec<Response> = await {
    futures_unordered(all_ids.into_iter().map(func)).collect()
}?;

Kedua versi keduanya menggunakan 7 baris tetapi IMO yang kedua lebih bersih. Ada juga dua cara untuk menggunakan pembatas wajib:

  1. await { future }? tidak terlihat berisik jika bagian future panjang. Lihat let req = await { self.request(URL, Method::GET, vec![("info".into(), id.into())]) }?;
  2. Ketika garis pendek, menggunakan await(future) bisa lebih baik. Lihat Ok(await(req.json())?)

IMO, dengan beralih di antara dua varian, keterbacaan kode ini jauh lebih baik dari sebelumnya.

Contoh pertama berformat buruk. Saya tidak berpikir Rustfmt memformatnya seperti itu
bahwa. Bisakah Anda menjalankan rustfmt di atasnya dan mempostingnya lagi di sini?

@ivandardi @mehcode Bisakah Anda melakukan itu? Saya tidak tahu bagaimana saya dapat memformat sintaks .await . Saya baru saja menyalin kodenya. Terima kasih!

Saya ingin menambahkan bahwa contoh ini menunjukkan:

  1. Kode produksi tidak hanya berupa rantai yang bagus dan sederhana seperti:
client.get("https://my_api").send().await?.json().await?
  1. Orang dapat menggunakan rantai secara berlebihan atau menyalahgunakan.
let responses: Vec<Response> =
    futures_unordered(all_ids.into_iter().map(async move |id| {
        Ok(self__
            .request(URL, Method::GET, vec![("info".into(), id.into())]).await?
            .json().await?)
    }))
    .collect().await?;

Di sini, penutupan asinkron menangani setiap ID, ini tidak ada hubungannya dengan kontrol tingkat yang lebih tinggi futures_unordered . Menyatukannya secara signifikan mengurangi kemampuan Anda untuk memahaminya.

Semuanya _dari_ dijalankan melalui rustfmt dari posting saya (dengan beberapa perubahan kecil untuk membuatnya dikompilasi). Dimana .await? ditempatkan belum diputuskan dan saat ini saya menempatkannya di akhir baris yang sedang ditunggu.


Sekarang saya setuju bahwa semuanya terlihat sangat buruk. Ini adalah kode yang ditulis pada tenggat waktu dan hal-hal pasti akan terlihat mengerikan ketika Anda memiliki ruangan juru masak yang bekerja keras untuk mengeluarkan sesuatu.

Saya ingin menunjukkan (dari poin Anda) bahwa awalan yang menyalahgunakan dapat terlihat jauh lebih buruk (menurut saya tentu saja):

let responses: Vec<Response> = await!(futures_unordered(all_ids.into_iter().map(async move |id| {
    Ok(await!(await!(self__
        .request(URL, Method::GET, vec![("info".into(), id.into())]))?
        .json())?)
}))
.collect())?;

Sekarang mari bersenang-senang dan buat jauh lebih bagus menggunakan melihat ke belakang dan beberapa adaptor baru di masa depan v0.3

Awalan dengan Urutan "Jelas" (dan gula)
let responses: Vec<Response> = await? stream::iter(all_ids)
    .then(|id| self.request(URL, Method::GET, vec![("info".into(), id.into())]))
    .and_then(|mut res| res.json().err_into())
    .try_buffer_unordered(10)
    .try_collect();
Awalan dengan Diutamakan "Berguna"
let responses: Vec<Response> = await stream::iter(all_ids)
    .then(|id| self.request(URL, Method::GET, vec![("info".into(), id.into())]))
    .and_then(|mut res| res.json().err_into())
    .try_buffer_unordered(10)
    .try_collect()?;
Awalan dengan Pembatas Wajib
let responses: Vec<Response> = await {
    stream::iter(all_ids)
        .then(|id| self.request(URL, Method::GET, vec![("info".into(), id.into())]))
        .and_then(|mut res| res.json().err_into())
        .try_buffer_unordered(10)
        .try_collect()
}?;
Bidang Postfix
let responses: Vec<Response> = stream::iter(all_ids)
    .then(|id| self.request(URL, Method::GET, vec![("info".into(), id.into())]))
    .and_then(|mut res| res.json().err_into())
    .try_buffer_unordered(10)
    .try_collect().await?;
Kata Kunci Postfix
let responses: Vec<Response> = stream::iter(all_ids)
    .then(|id| self.request(URL, Method::GET, vec![("info".into(), id.into())]))
    .and_then(|mut res| res.json().err_into())
    .try_buffer_unordered(10)
    .try_collect() await?;

Nit kecil di sini. Tidak ada TryStreamExt::and_then , mungkin seharusnya ada. Kedengarannya seperti PR yang mudah bagi siapa saja dengan waktu yang ingin berkontribusi.


  • Saya ingin, sekali lagi, mengungkapkan ketidaksukaan saya yang kuat untuk await? Dalam rantai panjang saya benar-benar kehilangan jejak ? yang telah saya cari di akhir ekspresi untuk menandakan bahwa ekspresi ini adalah bisa salah dan mungkin _keluar dari fungsi_.

  • Saya selanjutnya ingin mengungkapkan ketidaksukaan saya yang semakin besar untuk await .... ? (prioritas yang berguna) sebagai pertimbangan apa yang akan terjadi jika kita memiliki fn foo() -> Result<impl Future<Output = Result<_>>>

    // Is this an error? Does`await .. ?` bind outer-most to inner?
    await foo()??
    

Saya ingin menunjukkan (dari poin Anda) bahwa awalan yang menyalahgunakan dapat terlihat jauh lebih buruk (menurut saya tentu saja):

let responses: Vec<Response> = await!(futures_unordered(all_ids.into_iter().map(async move |id| {
    Ok(await!(await!(self__
        .request(URL, Method::GET, vec![("info".into(), id.into())]))?
        .json())?)
}))
.collect())?;

Ini sebenarnya bukan masalah karena di python javascript, orang lebih cenderung menulisnya di baris terpisah. Saya belum benar-benar melihat await (await f) dalam python.

Awalan dengan Pembatas Wajib

let responses: Vec<Response> = await {
    stream::iter(all_ids)
        .then(|id| self.request(URL, Method::GET, vec![("info".into(), id.into())]))
        .and_then(|mut res| res.json().err_into())
        .try_buffer_unordered(10)
        .try_collect()
}?;

Ini juga tampaknya kembali menggunakan kombinator. Sementara inti dari memperkenalkan async / await adalah untuk mengurangi penggunaannya jika sesuai.

Nah, itulah inti dari menunggu postfix. Karena ini adalah Rust, orang cenderung menulisnya dalam baris terpisah, karena Rust mendorong perangkaian. Dan untuk itu, sintaks postfix pada dasarnya bersifat mandatory sehingga aliran instruksi mengikuti alur pembacaan baris yang sama. Jika kita tidak memiliki sintaks postfix, maka akan ada banyak kode dengan temporaries yang juga dirantai, sedangkan jika kita memiliki postfix await itu semua bisa direduksi menjadi satu rantai.

@ivandardi @mehcode Menyalin dari Rust RFC di async / await:

Setelah mendapatkan pengalaman & umpan balik pengguna dengan ekosistem berbasis masa depan, kami menemukan tantangan ergonomi tertentu. Menggunakan status yang perlu dibagikan di seluruh titik menunggu sangat tidak ergonomis - membutuhkan baik Arcs atau rantai gabungan - dan sementara kombinator sering kali lebih ergonomis daripada menulis masa depan secara manual, mereka masih sering menyebabkan kumpulan callback bersarang dan dirantai yang berantakan.

... digunakan dengan gula sintaksis yang telah menjadi umum dalam banyak bahasa dengan asinkron IO - kata kunci asinkron dan menunggu.

Dari perspektif pengguna, mereka dapat menggunakan async / await seolah-olah itu adalah kode sinkron, dan hanya perlu memberi keterangan pada fungsi dan panggilan mereka.

Jadi, inti dari memperkenalkan async await adalah mengurangi rantai dan membuat kode asinkron seolah-olah disinkronkan . Perangkaian hanya disebutkan dua kali dalam RFC ini, "memerlukan baik Arcs atau rangkaian gabungan" dan "masih sering menyebabkan kumpulan callback bersarang dan dirantai yang berantakan". Kedengarannya tidak terlalu positif bagi saya.

Berdebat untuk chaining dan kata kunci postfix mungkin akan membutuhkan penulisan ulang utama dari RFC ini.

@tajimaha Anda salah paham tentang RFC. Ini berbicara secara khusus tentang kombinator Masa Depan ( map , and_then , dll.), Ini tidak berbicara tentang perangkaian secara umum (misalnya metode yang mengembalikan impl Future ).

Saya pikir aman untuk mengatakan bahwa async metode akan sangat umum, jadi merangkai memang cukup penting.

Selain itu, Anda salah memahami prosesnya: RFC tidak perlu ditulis ulang. RFC adalah titik awal, tetapi ini bukan spesifikasi. Tak satu pun dari RFC yang ditetapkan (juga seharusnya tidak!)

Proses RFC lancar, tidak sekaku yang Anda maksudkan. Tidak ada di RFC yang menghalangi kita untuk membahas postfix await .

Selain itu, setiap perubahan akan dimasukkan ke dalam RFC stabilisasi , bukan RFC asli (yang telah diterima, dan dengan demikian tidak akan diubah).

Berdebat untuk chaining dan dengan demikian kata kunci postfix mungkin akan membutuhkan penulisan ulang utama dari RFC ini.

Saya bercanda di sini.

Satu hal mungkin benar, saat menulis RFC. Orang-orang secara khusus menginginkan alat baru "yang dapat menggunakan async / await seolah-olah itu adalah kode sinkron". Tradisi mengikuti sintaks akan lebih baik memenuhi janji ini.
Dan di sini Anda lihat, ini bukan kombinator masa depan,

let responses: Vec<Response> =
    futures_unordered(all_ids.into_iter().map(async move |id| {
        Ok(self__
            .request(URL, Method::GET, vec![("info".into(), id.into())]).await?
            .json().await?)
    }))
    .collect().await?;

Namun, "mereka masih sering menyebabkan kumpulan callback bersarang dan dirantai yang berantakan".

Orang-orang secara khusus menginginkan alat baru "yang dapat menggunakan async / await seolah-olah itu adalah kode sinkron". Tradisi mengikuti sintaks akan lebih baik memenuhi janji ini.

Saya tidak melihat bagaimana itu benar: baik awalan dan postfix await memenuhi keinginan itu.

Faktanya, postfix await mungkin memenuhi keinginan itu dengan lebih baik, karena sangat alami dengan rantai metode (yang sangat umum dalam kode Rust sinkron!)

Menggunakan awalan await sangat mendorong banyak variabel sementara, yang seringkali bukan gaya Rust idiomatik.

Namun, "mereka masih sering menyebabkan kumpulan callback bersarang dan dirantai yang berantakan".

Saya melihat persis satu penutupan, dan ini bukan panggilan balik, itu hanya menelepon map dengan Iterator (tidak ada hubungannya sama sekali dengan Futures!)

Mohon jangan mencoba memutarbalikkan kata-kata RFC untuk membenarkan awalan await .

Menggunakan RFC untuk membenarkan prefiks await sangat aneh, karena RFC sendiri mengatakan bahwa sintaksnya belum final dan akan ditentukan kemudian. Saatnya untuk mengambil keputusan itu sekarang.

Keputusan akan dibuat berdasarkan manfaat dari berbagai proposal, RFC asli sama sekali tidak relevan (kecuali sebagai referensi sejarah yang berguna).

Catatan, contoh terbaru @ mehcode sebagian besar menggunakan kombinator _stream_ (dan satu kombinator masa depan bisa dengan mudah diganti dengan blok asinkron). Ini sama dengan menggunakan kombinator iterator dalam kode sinkron, jadi dapat digunakan dalam beberapa situasi ketika lebih cocok daripada loop.

Ini offtopic, tapi sebagian besar percakapan di sini dilakukan oleh selusin atau lebih pemberi komentar. Dari 383 komentar pada saat saya mengorek masalah ini, hanya ada 88 poster unik. Dalam upaya untuk menghindari membebani / membebani siapa pun yang harus pergi dan membaca komentar ini, saya akan merekomendasikan agar komentar Anda selengkap mungkin dan memastikan bahwa itu bukan pengulangan poin sebelumnya.


Histogram komentar

HeroicKatora:(32)********************************
Centril:(22)**********************
ivandardi:(21)*********************
I60R:(21)*********************
Pzixel:(16)****************
novacrazy:(15)***************
scottmcm:(13)*************
EyeOfPython:(11)***********
mehcode:(11)***********
Pauan:(10)**********
XX:(9)*********
nicoburns:(9)*********
tajimaha:(9)*********
skade:(8)********
CAD97:(8)********
Laaas:(8)********
dpc:(8)********
ejmahler:(7)*******
Nemo157:(7)*******
yazaddaruvala:(6)******
traviscross:(6)******
CryZe:(6)******
Matthias247:(5)*****
dowchris97:(5)*****
rolandsteiner:(5)*****
earthengine:(5)*****
H2CO3:(5)*****
eugene2k:(5)*****
jplatte:(4)****
lnicola:(4)****
andreytkachenko:(4)****
cenwangumass:(4)****
richardanaya:(4)****
chpio:(3)***
joshtriplett:(3)***
phaylon:(3)***
phaazon:(3)***
ben0x539:(2)**
newpavlov:(2)**
comex:(2)**
DDOtten:(2)**
withoutboats:(2)**
valff:(2)**
darkwater:(2)**
tanriol:(1)*
liigo:(1)*
yasammez:(1)*
mitsuhiko:(1)*
mokeyish:(1)*
unraised:(1)*
mzji:(1)*
swfsql:(1)*
spacekookie:(1)*
sgrif:(1)*
nikonthethird:(1)*
edwin-durai:(1)*
norcalli:(1)*
quodlibetor:(1)*
chescock:(1)*
BenoitZugmeyer:(1)*
F001:(1)*
FuGangqiang:(1)*
Keruspe:(1)*
LegNeato:(1)*
MSleepyPanda:(1)*
SamuelMoriarty:(1)*
Swoorup:(1)*
Uristqwerty:(1)*
alexmaco:(1)*
arabidopsis:(1)*
arielb1:(1)*
axelf4:(1)*
casey:(1)*
lholden:(1)*
cramertj:(1)*
crlf0710:(1)*
davidtwco:(1)*
dyxushuai:(1)*
eaglgenes101:(1)*
AaronFriel:(1)*
gralpli:(1)*
huxi:(1)*
ian-p-cooke:(1)*
jonimake:(1)*
josalhor:(1)*
jsdw:(1)*
kjetilkjeka:(1)*
kvinwang:(1)*

Catatan, contoh terbaru @ mehcode sebagian besar menggunakan kombinator _stream_ (dan satu kombinator masa depan bisa dengan mudah diganti dengan blok asinkron). Ini sama dengan menggunakan kombinator iterator dalam kode sinkron, jadi dapat digunakan dalam beberapa situasi ketika lebih cocok daripada loop.

Hal yang sama dapat diperdebatkan di sini bahwa saya dapat / harus menggunakan awalan menunggu di mana mereka lebih sesuai daripada merangkai.

@Pauan Ternyata itu bukan sekedar memutarbalikkan kata-kata. Saya menunjukkan masalah kode sebenarnya, yang ditulis oleh pendukung sintaks postfix. Dan seperti yang saya katakan, kode gaya prefiks menggambarkan niat Anda dengan lebih baik sementara tidak selalu memiliki banyak temporer seperti yang dikeluhkan pendukung postfix (setidaknya dalam kasus ini). juga, misalkan kode Anda memiliki rantai satu liner dengan dua menunggu, bagaimana cara men-debug yang pertama? (ini adalah pertanyaan yang benar dan saya tidak tahu).
Kedua, komunitas karat menjadi lebih besar, orang-orang dari berbagai latar belakang (seperti saya, saya paling sering menggunakan python / c / java) tidak semua setuju bahwa rantai metode adalah cara terbaik untuk melakukan sesuatu. Saya harap saat membuat keputusan, ini tidak (tidak boleh) hanya berdasarkan sudut pandang pengguna awal.

@tajimaha Perubahan kejelasan terbesar dari pasca-perbaikan ke awalan tampaknya menggunakan penutupan lokal untuk menghapus beberapa argumen fungsi bertingkat. Bagi saya ini tidak tampak unik untuk awalan, itu cukup ortogonal. Anda dapat melakukan hal yang sama untuk postfix, dan menurut saya ini lebih jelas. Saya setuju ini mungkin penyalahgunaan rangkaian untuk beberapa basis kode tetapi saya tidak melihat bagaimana penyalahgunaan ini unik atau terkait dengan postfix secara utama.

let get_one_id = async move |id| {
    self.request(URL, Method::GET, vec![("info".into(), id.into())])
        .await?
        .json().await
};

let responses: Vec<Response> = futures_unordered(all_ids.into_iter().map(get_one_id))
    .collect().await?;

Tapi, di postfix, let -binding dan Ok pada hasil dapat dihilangkan bersama-sama dengan ? untuk langsung memberikan hasil dan blok kode juga tidak diperlukan tergantung berdasarkan selera pribadi. Ini tidak bekerja dengan baik di awalan sama sekali karena dua menunggu dalam pernyataan yang sama.

Saya tidak memahami sentimen yang dinyatakan secara teratur yang membiarkan binding tidak otomatis dalam kode Rust. Mereka cukup sering dan umum dalam contoh kode, terutama seputar penanganan hasil. Saya jarang melihat lebih dari 2 ? dalam kode yang saya tangani.

Juga, apa idiomatic adalah perubahan selama masa bahasa, jadi saya akan berhati-hati menggunakannya sebagai argumen.

Saya tidak tahu apakah sesuatu seperti ini telah disarankan sebelumnya, tetapi dapatkah awalan await kata kunci berlaku untuk seluruh ekspresi? Mengambil contoh yang telah dikemukakan sebelumnya:

let result = await client.get("url").send()?.json()?

di mana get , send , dan json adalah asinkron.

Bagi saya (memiliki sedikit pengalaman asinkron dalam bahasa pemrograman lain) postfix expr await terlihat natural: “Lakukan ini, _then_ tunggu hasilnya.”

Ada beberapa kekhawatiran bahwa contoh berikut terlihat aneh:

client.get("https://my_api").send() await?.json() await? // or
client.get("https://my_api").send()await?.json()await?

Namun, saya berpendapat bahwa ini harus dibagi menjadi beberapa baris:

client.get("https://my_api").send() await?
    .json() await?

Ini jauh lebih jelas dan memiliki keuntungan tambahan bahwa await mudah dikenali, jika selalu berada di akhir baris.

Dalam IDE, sintaks ini tidak memiliki "kekuatan titik", tetapi masih lebih baik daripada versi awalan: Saat Anda mengetik titik dan kemudian menyadari bahwa Anda perlu await , Anda hanya perlu menghapus titik dan ketik " await ". Artinya, jika IDE tidak menawarkan pelengkapan otomatis untuk kata kunci.

Sintaks titik expr.await membingungkan karena tidak ada kata kunci aliran kontrol lain yang menggunakan titik.

Saya pikir masalahnya adalah meskipun kita memiliki rantai, yang kadang-kadang bisa cantik, kita tidak boleh terlalu ekstrim mengatakan bahwa semuanya harus dilakukan dalam rantai. Kami juga harus menyediakan alat untuk pemrograman gaya C atau Python. Meskipun Python hampir tidak memiliki komponen rantai di sana, kodenya sering dipuji karena dapat dibaca. Pemrogram Python juga tidak mengeluh kami memiliki terlalu banyak variabel temp.

Bagaimana dengan postfix then ?

Saya tidak memahami sentimen yang dinyatakan secara teratur yang membiarkan binding tidak otomatis dalam kode Rust. Mereka cukup sering dan umum dalam contoh kode, terutama seputar penanganan hasil. Saya jarang melihat lebih dari 2 ? dalam kode yang saya tangani.

Juga, apa idiomatic adalah perubahan selama masa bahasa, jadi saya akan berhati-hati menggunakannya sebagai argumen.

Ini menginspirasi saya untuk mensurvei kode Rust saat ini di mana ada dua atau lebih ? dalam satu baris (mungkin seseorang dapat mensurvei penggunaan multiline). Saya telah mensurvei xi-editor, alacritty, ripgrep, bat, xray, fd, firecracker, yew, Rocket, exa, iron, parity-ethereum, tikv. Ini adalah proyek Rust dengan bintang terbanyak.

Apa yang saya temukan adalah bahwa kira-kira hanya sekitar 40 baris dari total 585562 baris menggunakan dua atau lebih ? dalam satu baris. Itu 0,006% .

Saya juga ingin menunjukkan bahwa mempelajari pola penggunaan kode yang ada tidak akan mengungkapkan pengalaman pengguna dalam menulis kode baru.

Misalkan Anda diberi pekerjaan untuk berinteraksi dengan API baru atau Anda baru dalam menggunakan permintaan. Apakah kemungkinan Anda akan menulis

client.get("https://my_api").send().await?.json().await?

hanya dalam satu kesempatan ? Jika Anda baru mengenal API, saya ragu Anda ingin memastikan Anda membuat permintaan dengan benar, melihat status pengembalian, memverifikasi asumsi Anda tentang apa yang dikembalikan API ini atau hanya bermain dengan API seperti ini:

let request = client.get("https://my_api").header("k", "v");
dbg!(request);
let response = await(request.send())?;
dbg!(response);
let data = await(response.json())?;
dbg!(data);

API jaringan tidak seperti dalam data memori, Anda tidak tahu apa yang ada di sana. Ini sangat alami untuk pembuatan prototipe. Dan, saat Anda membuat prototipe, Anda khawatir semuanya akan beres TIDAK terlalu banyak variabel temp . Anda bisa mengatakan kita bisa menggunakan sintaks postfix seperti:

let request = client.get("https://my_api").header("k", "v");
dbg!(request);
let response = request.send().await?;
dbg!(response);
let data = response.json().await?;
dbg!(data);

Tapi, kalau sudah punya ini:

let request = client.get("https://my_api").header("k", "v");
dbg!(request);
let response = await(request.send())?;
dbg!(response);
let data = await(response.json())?;
dbg!(data);

Yang harus Anda lakukan mungkin membungkusnya dalam sebuah fungsi dan tugas Anda selesai, rangkaian bahkan tidak muncul dalam proses ini.

Apa yang saya temukan adalah kira-kira hanya sekitar 40 baris dari total 585562 baris menggunakan dua atau lebih? dalam satu baris.

Saya ingin menyarankan bahwa ini bukan pengukuran yang membantu. Yang terpenting adalah lebih dari satu operator aliran kontrol _per ekspresi_. Sesuai dengan gaya tipikal (rustfmt), mereka hampir selalu berakhir di baris yang berbeda dalam file meskipun mereka memiliki ekspresi yang sama, dan dengan demikian akan dirantai dalam jumlah yang penting postfix (secara teoritis) untuk await .

kita tidak boleh mengatakan secara ekstrim bahwa segala sesuatu harus dilakukan dalam rantai

Adakah yang mengatakan bahwa segala sesuatu _harus_ dilakukan dengan rantai? Yang saya lihat sejauh ini adalah bahwa _ergonomis_ untuk dirantai dalam kasus-kasus yang masuk akal, sama seperti yang terjadi pada kode sinkron.

hanya sekitar 40 baris dari total 585562 baris menggunakan dua atau lebih? dalam satu baris.

Saya tidak yakin itu relevan dengan awalan vs postfix. Saya akan mencatat bahwa _none_ dari C # saya await s dalam satu baris, atau bahkan beberapa await s dalam sebuah pernyataan. Dan contoh @Centril tentang potensi layout postfix juga tidak menempatkan banyak await s pada satu baris.

Perbandingan yang lebih baik mungkin untuk hal-hal yang dirantai ? , seperti contoh berikut dari kompiler:

Ok(&self.get_bytes(cx, ptr, size_with_null)?[..size])
self.try_to_scalar()?.to_ptr().ok()
let idx = decoder.read_u32()? as usize;
.extend(self.at(cause, param_env).eq(v1, v2)?.into_obligations());
for line in BufReader::new(File::open(path)?).lines() {

Sunting: Sepertinya Anda mengalahkan saya kali ini , @ CAD97 :

Ini sangat mirip dengan kode janji javascipt dengan banyak then s. Saya tidak akan menyebut ini sinkron. Hampir pasti berantai kebetulan memiliki await dan berpura-pura menjadi sinkron.

Awalan dengan Pembatas Wajib

let responses: Vec<Response> = await {
    stream::iter(all_ids)
        .then(|id| self.request(URL, Method::GET, vec![("info".into(), id.into())]))
        .and_then(|mut res| res.json().err_into())
        .try_buffer_unordered(10)
        .try_collect()
}?;

@ CAD97 @ottcm Poin bagus. Saya tahu ada batasan pada apa yang saya ukur:

(mungkin seseorang dapat mensurvei penggunaan multiline)

Saya melakukan ini karena @skade menyebutkan kesamaan antara await dan ? , jadi saya melakukan analisis cepat. Saya memberikan ide untuk tidak melakukan penelitian yang serius. Saya pikir analisis yang lebih terperinci akan sulit dilakukan dengan melihat kodenya, bukan? Mungkin perlu parsing dan mengidentifikasi ekspresi, yang saya tidak kenal. Saya berharap seseorang dapat melakukan analisis ini.

Adakah yang mengatakan bahwa semuanya harus dilakukan dengan rantai? Yang saya lihat sejauh ini adalah bahwa rantai harus ergonomis dalam kasus-kasus yang masuk akal, sama seperti yang terjadi pada kode sinkron.

Apa yang saya katakan adalah jika hanya postfix yang ditambahkan, itu akan menjadi tidak ergonomis ketika gaya C / Python terlihat bagus (tentu saja imo). Saya juga membahas rantai mungkin tidak diperlukan saat Anda membuat prototipe .

Saya merasa arah utas ini terlalu terfokus pada perangkaian yang berlebihan, dan cara membuat kode menunggu sesingkat mungkin.

Saya ingin mendorong semua orang untuk melihat bagaimana kode asinkron berbeda dari varian sinkron, dan bagaimana hal ini akan memengaruhi penggunaan, dan pemanfaatan sumber daya. Kurang dari 5 komentar di 400 di utas ini menyebutkan perbedaan tersebut. Jika Anda tidak mengetahui perbedaan ini, gunakan versi nightly saat ini, dan coba tulis kode asinkron yang layak. Termasuk mencoba untuk mendapatkan versi idiomatic (non-combinator) async / await dari potongan kode yang dibahas dalam 20 posting terakhir.

Ini akan membuat perbedaan apakah ada hal-hal yang murni antara titik hasil / menunggu, apakah referensi tetap ada di seluruh titik menunggu, dan seringkali beberapa persyaratan khusus seputar masa depan membuat tidak mungkin untuk menulis kode sesingkat yang dibayangkan di utas ini. Misalnya, kami tidak dapat menempatkan fungsi asinkron arbitrer di kombinator arbitrer, karena fungsi tersebut mungkin tidak berfungsi dengan tipe !Unpin . Jika kita membuat futures dari blok asinkron, mereka mungkin tidak secara langsung kompatibel dengan kombinator seperti join! atau select! , karena mereka membutuhkan tipe yang disematkan dan digabungkan, jadi panggilan tambahan ke pin_mut! dan .fuse() mungkin dibutuhkan dalam.

Selain itu untuk bekerja dengan blok asinkron, utilitas berbasis makro baru berfungsi join! dan select! bekerja jauh lebih baik daripada varian kombinator lama. Dan ini dalam mode berlebihan yang di sini sering diberikan sebagai contoh

Saya tidak tahu bagaimana postfix menunggu dapat bekerja dengan .unwrap() dalam contoh tokio yang paling sederhana ini

let response = await!({
    client.get(uri)
        .timeout(Duration::from_secs(10))
}).unwrap();

Jika prefiks diadopsi, itu akan menjadi

let response = await {
    client.get(uri).timeout(Duration::from_secs(10))
}.unwrap();

Tetapi jika postfix diadopsi,

client.get(uri).timeout(Duration::from_secs(10)).await.unwrap()
client.get(uri).timeout(Duration::from_secs(10)) await.unwrap()

Apakah ada penjelasan intuitif yang bisa kami berikan kepada pengguna? Ini bertentangan dengan aturan yang ada. await adalah bidang? atau await mengikat yang memiliki metode yang disebut unwrap() ? SANGAT BURUK! Kami membuka banyak bungkus ketika kami memulai sebuah proyek. Melanggar beberapa aturan desain di The Zen of Python.

Kasus khusus tidak cukup istimewa untuk melanggar aturan.
Jika implementasinya sulit dijelaskan, itu ide yang buruk.
Dalam menghadapi ambiguitas, tolak godaan untuk menebak.

Apakah ada penjelasan intuitif yang bisa kami berikan kepada pengguna? Ini bertentangan dengan aturan yang ada. await adalah bidang? atau await mengikat yang memiliki metode yang disebut unwrap() ? SANGAT BURUK! Kami membuka banyak bungkus ketika kami memulai sebuah proyek. Melanggar beberapa aturan desain di The Zen of Python.

Saya akan mengatakan, meskipun ada terlalu banyak dokumen di docs.rs memanggil unwrap , unwrap harus diganti dengan ? dalam banyak kasus dunia nyata. Di sewa, inilah praktik saya.

Apa yang saya temukan adalah kira-kira hanya sekitar 40 baris dari total 585562 baris menggunakan dua atau lebih? dalam satu baris.

Saya ingin menyarankan bahwa ini bukan pengukuran yang membantu. Yang terpenting adalah lebih dari satu operator aliran kontrol _per ekspresi_. Sesuai dengan gaya tipikal (rustfmt), mereka hampir selalu berakhir di baris yang berbeda dalam file meskipun mereka memiliki ekspresi yang sama, dan dengan demikian akan dirantai dalam jumlah yang penting postfix (secara teoritis) untuk await .

Saya menyangkal mungkin ada batasan untuk pendekatan ini.

Saya mensurvei lagi untuk xi-editor, alacritty, ripgrep, bat, xray, fd, firecracker, yew, Rocket, exa, iron, parity-ethereum, tikv. Ini adalah proyek Rust dengan bintang terbanyak. Kali ini saya mencari pola:

xxx
  .f1()
  .f2()
  .f3()
  ...

dan apakah ada beberapa operator aliran kontrol dalam ekspresi ini.

Saya mengidentifikasi HANYA 15 dari 7066 rantai memiliki beberapa operator aliran kontrol. Itu 0,2% . Baris-baris ini merentang 167 dari 585562 baris kode. Itu 0,03% .

@cenwangumass Terima kasih telah meluangkan waktu dan mengukur. :jantung:

Salah satu pertimbangannya adalah karena Rust memiliki variabel rebinding dengan let, hal itu dapat membuat argumen yang menarik untuk awalan await , jika digunakan secara konsisten, Anda akan memiliki baris kode terpisah untuk setiap await await -titik. Keuntungannya ada dua: stacktraces, karena nomor baris memberikan konteks yang lebih besar tentang di mana masalah terjadi, dan kemudahan debugging breakpoint, karena biasanya ingin menetapkan breakpoint pada setiap titik tunggu terpisah untuk memeriksa variabel, mungkin lebih besar daripada keringkasannya. satu baris kode.

Secara pribadi saya terpecah antara prefix-style dan postfix sigil meskipun setelah membaca https://github.com/rust-lang/rust/issues/57640#issuecomment -457457727 Saya mungkin lebih menyukai postfix sigil.

Gaya awalan yang diberikan:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = await? self.request(url, Method::GET, None, true));
    let user = await? user.res.json::<UserResponse>();
    let user = user.user.into();

    Ok(user)
}

Gaya postfix yang diberikan:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.request(url, Method::GET, None, true)) await?;
    let user = user.res.json::<UserResponse>() await?;
    let user = user.user.into();

    Ok(user)
}

Sigil @:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.request(url, Method::GET, None, true))@?;
    let user = user.res.json::<UserResponse>()@?;
    let user = user.user.into();

    Ok(user)
}

#:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.request(url, Method::GET, None, true))#?;
    let user = user.res.json::<UserResponse>()#?;
    let user = user.user.into();

    Ok(user)
}

Saya belum melihat cukup banyak orang berbicara tentang await untuk Stream s. Meskipun di luar ruang lingkup, membuat keputusan sekitar await dengan sedikit tinjauan ke depan dari Stream mungkin sepadan dengan usaha.

Kami kemungkinan akan membutuhkan sesuatu seperti ini:
Sintaks prefiks

for await response in stream {
    let response = response?;
    ...
}

// In which case an `await?` variant might be beneficial
for await? response in stream {
    ...
}

Sintaks postfix

for response in stream await {
    let response = response?;
    ...
}
for response in stream.await!() {
    let response = response?;
    ...
}

// Or a specialized variant of `await` and `?`
//     Note (Not Obvious): The `?` actually applies to each response of `await`
for response in stream await? {
    ...
}
for response in stream.await!()? {
    ...
}

Sintaks yang mungkin paling ergonomis / konsisten

let results: Vec<Result<_, _>> = ...;
for value in? results {
    ...
}
for response await? stream {
    ...
}

Saya hanya ingin memastikan bahwa contoh-contoh ini dibahas setidaknya sedikit, karena walaupun postfix bagus untuk merangkai Future , pada pandangan pertama tampaknya paling tidak intuitif untuk Stream . Saya tidak yakin apa solusi yang tepat di sini. Mungkin postfix await untuk Future dan gaya lain await untuk Stream ? Tetapi untuk sintaks yang berbeda, kami perlu memastikan penguraiannya tidak ambigu.

Ini semua hanya pemikiran awal saya, mungkin ada baiknya untuk memikirkan lebih banyak lagi Stream usecase dari perspektif postfix. Adakah yang punya pemikiran tentang bagaimana postfix dapat bekerja dengan baik dengan Stream , atau jika kita harus memiliki dua sintaks?

Sintaks untuk pengulangan aliran bukanlah sesuatu yang akan terjadi untuk beberapa waktu yang saya bayangkan. Sangat mudah untuk menggunakan loop while let dan melakukannya secara manual:

Bidang Postfix
while let Some(value) = stream.try_next().await? {
}
Awali dengan Diutamakan "Konsisten" dan Gula
while let Some(value) = await? stream.try_next() {
}
Awalan dengan Pembatas Wajib
while let Some(value) = await { stream.try_next() }? {
}

Akan menjadi cara saat ini untuk melakukan sesuatu (ditambah await ).


Saya perhatikan bahwa contoh ini membuat "awalan dengan pembatas" terlihat sangat buruk bagi saya. Sementara await(...)? mungkin terlihat lebih bagus, jika kita harus melakukan "awalan dengan pembatas" saya berharap kita hanya mengizinkan _one_ jenis pembatas (seperti try { ... } ).

Garis singgung cepat, tetapi bukankah "menunggu dengan pembatas" hanya menjadi ... awalan normal menunggu? await menunggu ekspresi, jadi memiliki await expr dan await { expr } pada dasarnya sama. Tidaklah masuk akal untuk hanya menunggu dengan pembatas dan tidak memilikinya tanpa pembatas, terutama karena ekspresi apa pun bisa diapit oleh {} dan terus menjadi ekspresi yang sama.

Pertanyaan tentang stream membuat saya berpikir bahwa bagi Rust, wajar jika menunggu tersedia tidak hanya sebagai operator dalam ekspresi, tetapi juga sebagai pengubah dalam pola:

// These two lines mean the same - both await the future
let x = await my_future;
let async x = my_future;

yang kemudian dapat bekerja secara alami dengan for

for async x in my_stream { ... }

@ivardi

Masalah yang diselesaikan oleh "awalan menunggu dengan pembatas wajib" adalah pertanyaan prioritas sekitar ? . Dengan pembatas _mandatory_, tidak ada pertanyaan prioritas.

Lihat try { .... } untuk sintaks _similar_ (stabil). Try memiliki pembatas wajib untuk alasan yang hampir sama - bagaimana ia berinteraksi dengan ? - karena ? di dalam kurung sangat berbeda dari yang di luar.

@yazaddaruvala Saya rasa seharusnya tidak ada cara khusus asinkron untuk menangani perulangan for dengan hasil yang lebih bagus, yang seharusnya hanya berasal dari beberapa fitur umum yang juga berhubungan dengan Iterator<Item = Result<...>> .

// postfix syntax
for response in stream await {
    ...
}

Ini menyiratkan bahwa stream: impl Future<Output = Iterator> dan Anda menunggu iterator tersedia, bukan setiap elemen. Saya tidak melihat banyak peluang untuk sesuatu yang lebih baik dari async for item in stream (tanpa fitur yang lebih umum seperti @tanriol mention).


@ivandardi perbedaan diutamakan setelah Anda mulai merangkai ke akhir pembatas, berikut dua contoh yang menguraikan secara berbeda dengan "awalan prioritas yang jelas", "awalan prioritas yang berguna" (setidaknya pemahaman saya tentang apa yang diutamakan "berguna") dan "awalan pembatas wajib".

await (foo.bar()).baz()?;
await { let foo = quux(); foo.bar() }.baz()?;

which parse to (dengan pembatas yang cukup sehingga tidak ambigu untuk ketiga varian)

// obvious precedence prefix
await ((foo.bar()).baz()?);
await ({ let foo = quux(); foo.bar() }.baz()?);

// useful precedence prefix
(await ((foo.bar()).baz()))?;
(await ({ let foo = quux(); foo.bar() }.baz())?;

// mandatory delimiters prefix
(await (foo.bar())).baz()?;
(await { let foo = quux(); foo.bar() }).baz()?;

Menurut pendapat saya, contoh @scottmcm dari C # di https://github.com/rust-lang/rust/issues/57640#issuecomment -457457727 terlihat baik-baik saja dengan pembatas dipindahkan dari posisi (await foo).bar ke posisi await(foo).bar :

await(response.Content.ReadAsStringAsync()).Should().Be(text);
var previous = await(branch.ListHistoryAsync(timestampUtc, null, cancellationToken, 1)).HistoryEntries.SingleOrDefault();

Dan lain-lain. Tata letak ini familiar dari pemanggilan fungsi normal, familiar dari aliran kendali berbasis kata kunci yang ada, dan familiar dari bahasa lain (termasuk C #). Hal ini untuk menghindari melanggar anggaran keanehan, dan tampaknya tidak menyebabkan masalah apa pun untuk perangkaian await .

Meskipun memang memiliki masalah yang sama dengan try! untuk multi- await rantai, itu tampaknya bukan masalah besar seperti pada Result ? Saya lebih suka membatasi keanehan sintaks di sini daripada mengakomodasi pola yang relatif tidak umum dan bisa dibilang tidak terbaca.

dan akrab dari bahasa lain (termasuk C #)

Ini bukan cara kerja prioritas di C # (atau Javascript, atau Python)

await(response.Content.ReadAsStringAsync()).Should().Be(text);

setara dengan

var future = (response.Content.ReadAsStringAsync()).Should().Be(text);
await future;

(operator titik memiliki prioritas lebih tinggi dari await , jadi mengikat lebih erat bahkan jika Anda mencoba membuat await terlihat seperti pemanggilan fungsi).

Saya tahu, itu bukan klaim yang saya buat. Hanya saja urutannya tetap sama, jadi satu-satunya hal yang perlu ditambahkan adalah tanda kurung, dan (secara terpisah) sintaks pemanggilan fungsi yang ada memiliki prioritas yang benar.

Tata letak ini [...] familiar dari aliran kontrol berbasis kata kunci yang ada

Saya tidak setuju. Kami sebenarnya _peringatkan_ menggunakan tata letak itu dalam aliran kontrol berbasis kata kunci yang ada:

warning: unnecessary parentheses around `return` value
 --> src/lib.rs:2:9
  |
2 |   return(4);
  |         ^^^ help: remove these parentheses
  |
  = note: #[warn(unused_parens)] on by default

Dan rustfmt mengubahnya menjadi return (4); , _menambahkan dalam_ spasi.

Itu tidak terlalu memengaruhi poin yang ingin saya sampaikan, dan terasa seperti membelah rambut. return (4) , return(4) , ini tidak lebih dari masalah gaya.

Saya membaca keseluruhan terbitan dan saya mulai merasa bahwa {} kurung dan awalan baik-baik saja, tapi kemudian menghilang

Ayo lihat:

let foo = await some_future();
let bar = await {other_future()}?.bar

Terlihat bagus, bukan? Tetapi bagaimana jika kita ingin merantai lebih banyak await dalam satu rantai?

let foo = await some_future();
let bar = await {
                await {other_future()}?.bar_async()
          }?;

IMHO terlihat jauh lebih buruk dari

let foo = some_future() await;
let bar = other_future() await?
           .bar_async() await?;

Namun, dikatakan, saya tidak percaya pada async chaining, seperti yang tertulis di postingan @cenwangumass . Berikut adalah contoh saya menggunakan async/await di server hiper, tolong, tunjukkan di mana rantai dapat berguna menggunakan contoh konkret ini. Saya benar-benar tergoda, tidak ada lelucon.


Tentang contoh sebelumnya, kebanyakan dari mereka "dirusak" dalam beberapa cara oleh kombinator. Anda hampir tidak pernah membutuhkannya sejak Anda menunggu. Misalnya

let responses: Vec<Response> = await {
    stream::iter(all_ids)
        .then(|id| self.request(URL, Method::GET, vec![("info".into(), id.into())]))
        .and_then(|mut res| res.json().err_into())
        .try_buffer_unordered(10)
        .try_collect()
}?;

hanya

let responses: Vec<Response> = all_ids
   .map(async |id|  {
      let response = self.request(URL, Method::GET, vec![("info".into(), id.into())]) await?;
      Ok(res.json() await?)
   })
   .join_all() await
   .collect()?

jika futures dapat mengembalikan kesalahan sesederhana:

let responses: Vec<Response> = all_ids
   .map(async |id|  {
      let response = self.request(URL, Method::GET, vec![("info".into(), id.into())])? await?;
      Ok(res.json()? await?)
   })
   .join_all()? await
   .collect()?

@lnicola, @nicoburns,

Saya telah membuat utas Pra-RFC untuk sintaks val.[await future] di https://internals.rust-lang.org/t/pre-rfc-extended-dot-operator-as-possible-syntax-for-await -chaining / 9304

Pertanyaan prosedural https://internals.rust-lang.org

Saya pikir kita memiliki dua kubu dalam diskusi ini: orang yang ingin menekankan poin hasil vs. orang yang ingin menghilangkan penekanannya.

Async di Rust, sekitar tahun 2018 berisi uraian berikut:

Notasi async / await adalah cara untuk membuat pemrograman asinkron lebih mirip dengan pemrograman sinkron.

Mengambil contoh tokio sederhana dari atas (mengubah unwrap() menjadi ? ):

let response = await!({
    client.get(uri).timeout(Duration::from_secs(10))
})?;

dan menerapkan arahan sigil postfix

let response = client.get(uri).timeout(Duration::from_secs(10))!?

yang sangat mirip dengan pemrograman sinkron dan tidak memiliki await mengganggu, baik dari awalan maupun notasi postfix.

Saya menggunakan ! sebagai sigil postfix dalam contoh ini meskipun saran itu memberi saya beberapa downvote dalam komentar sebelumnya. Saya melakukannya karena tanda seru memiliki arti yang melekat bagi saya dalam konteks ini yang mana kekurangan @ (yang dibaca otak saya sebagai "at") dan # . Tapi itu hanya masalah selera dan bukan maksud saya.

Saya hanya lebih memilih satu karakter tunggal postfix sigil daripada semua alternatif lain justru karena itu sangat tidak mengganggu, sehingga membuatnya lebih mudah untuk memahami apa yang sebenarnya dilakukan kode yang Anda baca versus apakah itu asinkron atau tidak, yang mana Saya akan mempertimbangkan detail implementasi. Saya tidak akan mengatakan itu tidak penting sama sekali tetapi saya berpendapat bahwa itu jauh kurang penting untuk aliran kode daripada pengembalian awal dalam kasus ? .

Dengan kata lain: Anda sangat mementingkan poin hasil saat menulis kode asinkron, bukan terlalu banyak saat membacanya. Karena kode lebih sering dibaca daripada ditulis, sintaks yang tidak mengganggu untuk await akan membantu.

Saya ingin mengingatkan pengalaman tim C # (penekanan adalah milik saya):

Ini juga mengapa kami tidak menggunakan bentuk 'implisit' untuk 'menunggu'. Dalam praktiknya, ini adalah sesuatu yang orang ingin pikirkan dengan sangat jelas, dan yang mereka inginkan di bagian depan dan tengah dalam kode mereka sehingga mereka dapat memperhatikannya. Yang cukup menarik, bahkan bertahun-tahun kemudian, kecenderungan ini tetap ada. yaitu terkadang kita menyesali beberapa tahun kemudian bahwa ada sesuatu yang terlalu bertele-tele . Beberapa fitur bagus pada awalnya, tetapi begitu orang merasa nyaman dengannya, lebih cocok dengan sesuatu yang lebih singkat. Itu tidak terjadi dengan 'menunggu'. Orang-orang tampaknya masih sangat menyukai sifat berat kata kunci tersebut

Karakter Sigil hampir tidak terlihat tanpa highlight yang tepat dan kurang bersahabat untuk pendatang baru (seperti yang mungkin saya katakan sebelumnya).


Tetapi berbicara tentang sigils, @ mungkin tidak akan membingungkan penutur non-Inggris (misalnya saya) karena kita tidak membacanya sebagai at . Kami memiliki nama yang sama sekali lain untuk itu yang tidak terpicu secara otomatis ketika Anda membaca kode sehingga Anda hanya memahaminya sebagai hieroglif keseluruhan, dengan artinya sendiri.

@huxi Saya merasa bahwa sigils sudah cukup dikesampingkan. Lihat posting Centril lagi untuk alasan mengapa kita harus menggunakan kata await .


Juga, untuk semua orang yang tidak menyukai postfix menunggu sebanyak itu, dan menggunakan argumen pragmatis "well, tidak banyak kode kehidupan nyata di luar sana yang bisa dirantai, jadi oleh karena itu postfix await tidak boleh ditambahkan", inilah sedikit anekdot (bahwa saya mungkin akan membantai karena ingatan yang buruk):

Kembali ke Perang Dunia II, salah satu negara yang bertempur kehilangan banyak pesawat di luar sana. Entah bagaimana, mereka harus memperkuat pesawat mereka. Jadi cara yang jelas untuk mengetahui di mana mereka harus fokus adalah dengan melihat pesawat yang kembali dan melihat di mana peluru paling banyak mengenai. Ternyata dalam sebuah pesawat, rata-rata 70% lubang berada di sayap, 10% di area mesin, dan 20% di area lain di pesawat. Jadi dengan statistik itu, masuk akal untuk memperkuat sayap, bukan? Salah! Alasannya adalah Anda hanya melihat pesawat yang kembali. Dan di pesawat itu, sepertinya kerusakan peluru di sayap tidak terlalu buruk. Namun, semua pesawat yang kembali hanya mengalami kerusakan kecil pada area mesin, yang dapat menyimpulkan bahwa kerusakan besar pada area mesin berakibat fatal. Jadi area mesin harus diperkuat.

Maksud saya dengan anekdot ini adalah: mungkin tidak banyak contoh kode kehidupan nyata yang dapat memanfaatkan postfix menunggu karena tidak ada postfix yang menunggu dalam bahasa lain. Jadi setiap orang terbiasa menulis kode await prefix dan sangat terbiasa dengannya, tetapi kita tidak pernah tahu apakah orang akan mulai chaining lebih banyak jika kita memiliki postfix await.

Jadi menurut saya tindakan terbaik adalah memanfaatkan fleksibilitas yang diberikan nightly build kepada kita dan memilih satu sintaks prefiks dan satu sintaks postfix untuk ditambahkan ke bahasa. Saya memilih awalan "Jelas Precedence" menunggu, dan .await postfix menunggu. Ini bukan pilihan sintaks terakhir, tetapi kita perlu memilih satu untuk memulai, dan menurut saya kedua pilihan sintaks tersebut akan memberikan pengalaman yang lebih baru dibandingkan dengan pilihan sintaks lainnya. Setelah kami menerapkannya di malam hari, kemudian kami bisa mendapatkan statistik penggunaan dan pendapat tentang bekerja dengan kode nyata menggunakan kedua opsi dan kami kemudian dapat melanjutkan diskusi sintaks, kali ini didukung dengan argumen pragmatis yang lebih baik.

@ivandardi Anekdotnya cukup berat dan IMHO tidak cocok: ini adalah kisah yang memotivasi di awal perjalanan, untuk mengingatkan orang agar mencari yang tidak jelas dan mencakup semua sudut, _bukan_ yang digunakan untuk melawan oposisi di Diskusi. Itu cocok untuk Rust 2018, di mana ia diangkat dan tidak terikat pada masalah tertentu. Menggunakannya melawan pihak lain dalam debat adalah tidak sopan, Anda perlu menegaskan posisi orang yang melihat lebih banyak atau memiliki lebih banyak visi untuk membuatnya berhasil. Saya tidak berpikir ini yang Anda inginkan. Juga, tetap dalam gambar, mungkin postfix tidak ada dalam bahasa apa pun karena postfix tidak pernah membuatnya di rumah;).

Orang-orang telah benar-benar mengukur dan melihat: kami memiliki operator yang dapat dirantai di Rust ( ? ) yang jarang digunakan untuk merantai. https://github.com/rust-lang/rust/issues/57640#issuecomment -458022676

Juga telah dibahas dengan baik bahwa ini adalah _hanya_ pengukuran dari status saat ini, seperti yang dikatakan @cenwangumass dengan baik di sini: https://github.com/rust-lang/rust/issues/57640#issuecomment -457962730. Jadi tidak seperti orang menggunakan ini sebagai angka akhir.

Saya memang ingin sebuah cerita di mana chaining menjadi gaya setengah dominan jika postfix benar-benar cara yang tepat. Saya tidak yakin akan hal itu. Saya juga telah menyebutkan di atas bahwa contoh dominan yang digunakan di sini ( reqwest ) hanya membutuhkan 2 menunggu karena API memilihnya dan API rantai yang nyaman tanpa perlu 2 menunggu dapat dengan mudah dibangun hari ini.

Meskipun saya menghargai keinginan untuk fase penelitian, saya ingin menunjukkan bahwa menunggu sudah sangat tertunda, fase penelitian apa pun akan memperburuknya. Kami juga tidak memiliki cara untuk mengumpulkan statistik pada banyak basis kode, kami harus menyusun sendiri perangkat bicara. Saya ingin lebih banyak riset pengguna di sini, tetapi itu membutuhkan waktu, penyiapan yang belum ada dan orang-orang untuk menjalankannya.

@Pzixel karena biarkan rebinding, saya pikir Anda bisa menulis

let foo = await some_future();
let bar = await {
                await {other_future()}?.bar_async()
          }?;

sebagai

``
let foo = menunggu some_future ();
let bar = await {other_future ()} ?. bar_async ();
let bar = await {bar} ?;

@Pzixel Apakah Anda memiliki sumber untuk kutipan pengalaman tim C # itu? Karena satu-satunya yang bisa saya temukan adalah komentar ini . Ini tidak dimaksudkan sebagai tuduhan atau semacamnya. Saya hanya ingin membaca teks lengkapnya.

Otak saya menerjemahkan @ menjadi "di" karena penggunaannya dalam alamat email. Simbol itu disebut "Klammeraffe" dalam bahasa ibu saya yang secara kasar diterjemahkan menjadi "monyet menempel". Saya benar-benar menghargai bahwa otak saya memilih "di" sebagai gantinya.

2 sen saya, sebagai pengguna Rust yang relatif baru (dengan latar belakang C ++, tetapi itu tidak terlalu membantu).

Beberapa dari Anda menyebut pendatang baru, inilah yang saya pikirkan:

  • pertama-tama, makro await!( ... ) tampaknya sangat diperlukan bagi saya, karena sangat mudah digunakan dan tidak dapat disalahpahami. Terlihat mirip dengan println!() , panic!() , ... dan itulah yang terjadi pada try! .
  • Pembatas wajib juga sederhana dan tidak ambigu.
  • versi postfix, baik field, function atau macro, tidak akan sulit untuk membaca atau menulis IMHO, karena pendatang baru hanya akan berkata "ok, begitulah caranya". Argumen ini juga berlaku untuk kata kunci postfix, itu "tidak biasa" tetapi "mengapa tidak".
  • mengenai notasi awalan dengan prioritas yang berguna, menurut saya akan terlihat membingungkan. Fakta bahwa await mengikat lebih erat akan membuat beberapa pikiran terpesona dan saya pikir beberapa pengguna lebih suka meletakkan banyak tanda kurung untuk membuatnya jelas (berantai atau tidak).
  • preseden yang jelas tanpa gula mudah dipahami dan diajarkan. Kemudian, untuk memperkenalkan gula di sekitarnya, cukup panggil await? kata kunci yang berguna. Berguna karena menghilangkan kebutuhan akan tanda kurung yang rumit:
    `` c# let response = (await http::get("https://www.rust-lang.org/"))?; // see kids? menunggu ... unwraps the future, so you have to use ? to unwrap the Result // but there is some sugar if you want, thanks to the menunggu? `Operator
    biarkan respon = menunggu? http :: get ("https://www.rust-lang.org/");
    // tetapi Anda tidak boleh berantai, karena sintaks ini tidak mengarah ke kode yang dirantai yang dapat dibaca
- sigils can be understood quite easily *if the chosen character makes sense* if it is introduced to be "the `?` for futures".

That being said, since no agreement seems to be reached, I think it would be reasonable to ship `await!()` to stable Rust. Then this discussion can be extended without blocking the whole process. Same that what happened for `try!()`/`?`, so again newcomers won't be lost. And if [Simple postfix macros](https://github.com/rust-lang/rfcs/pull/2442) get accepted, the problem will disappear since we'll get postfix macro for "free".

---

Just a thought, what about a postfix keyword, but which can be put as prefix as well, similar in some ways to the `const` keyword of C++? (I don't know if that was already proposed) In prefix position, it behaves like "prefix `await` with obvious precedence and optional sugar":
```c#
// preferred without chaining:
let response = await? http::get("https://www.rust-lang.org/");

// but also possible: (rustfmt warning)
let response = http::get("https://www.rust-lang.org/") await?;
let response = (http::get("https://www.rust-lang.org/") await)?;
let response = (await http::get("https://www.rust-lang.org/"))?;

// chains well
let matches = http::get("https://www.rust-lang.org/") await?
    .body?
    .async_regex_search("(?=(\d+))\w+\1") await;

// any of these are also allowed, but arguably ugly (rustfmt warning again)
let matches = await ((http::get("https://www.rust-lang.org/") await?)
    .body?
    .async_regex_search("(?=(\d+))\w+\1"));
let matches = (await? http::get("https://www.rust-lang.org/"))
    .body?
    .async_regex_search("(?=(\d+))\w+\1") await;
let matches = await http::get("https://www.rust-lang.org/") await?
        .body?
        .async_regex_search("(?=(\d+))\w+\1");
let matches = await (await http::get("https://www.rust-lang.org/"))?
    .body?
    .async_regex_search("(?=(\d+))\w+\1");
let matches = await!(
    http::get("https://www.rust-lang.org/")) await?
        .body?
        .async_regex_search("(?=(\d+))\w+\1")
);
let matches = await { // <-- parenthesis or braces optional here, but they clarify
    (await? http::get("https://www.rust-lang.org/"))
        .body?
        .async_regex_search("(?=(\d+))\w+\1")
};

Bagaimana cara mengajarkan itu:

  • ( await!() makro mungkin)
  • prefiks disarankan jika tidak terjadi perantaian, dengan gula (lihat di atas)
  • postfix direkomendasikan dengan chaining
  • mungkin untuk mencampurnya, tetapi tidak disarankan
  • mungkin untuk menggunakan prefiks saat dirantai dengan kombinator

Berdasarkan pengalaman pribadi saya, saya akan mengatakan bahwa awalan menunggu bukanlah masalah untuk rantai.
Rantai menambahkan banyak kode Javascript dengan awalan menunggu dan kombinator seperti f.then(x => ...) tanpa kehilangan keterbacaan menurut saya dan mereka tampaknya tidak merasa perlu untuk berdagang kombinator untuk menunggu postfix.

Melakukan:

let ret = response.await!().json().await!().to_string();

sama dengan:

let ret = await future.then(|x| x.json()).map(|x| x.to_string());

Saya tidak terlalu melihat manfaat dari postfix menunggu di atas rantai kombinator.
Saya merasa lebih mudah untuk memahami apa yang terjadi pada contoh kedua.

Saya tidak melihat masalah keterbacaan, rangkaian, atau prioritas dalam kode berikut:

async fn fetch_user(name: &str) -> Result<Vec<Permission>, Error> {

    let user = await? fetch(format!("/user/{0}", name).as_str())
        .map(|x| serde_json::from_str::<User>(x?))
        .then(|x| fetch(format!("/permissions/{0}", x?.id).as_str()))
        .map(|x| serde_json::from_str::<Vec<Permission>>(x?));

    Ok(user)
}

Saya akan mendukung awalan ini menunggu karena:

  • dari keakraban dengan bahasa lain.
  • dari keselarasan dengan kata kunci lainnya (kembali, putus, lanjutkan, hasil, dll ...).
  • masih terasa seperti Rust (dengan kombinator seperti yang kita lakukan dengan iterator).

Async / await akan menjadi tambahan yang bagus untuk bahasa ini dan saya pikir lebih banyak orang akan menggunakan lebih banyak hal yang berhubungan dengan masa depan setelah penambahan ini dan stabilisasinya.
Beberapa bahkan mungkin menemukan pemrograman asinkron untuk pertama kalinya dengan Rust.
Jadi, mungkin bermanfaat untuk mengurangi kerumitan bahasa sementara konsep di balik asinkron mungkin sudah sulit dipelajari.

Dan menurut saya pengiriman awalan dan postfix menunggu bukanlah ide yang bagus tapi ini hanya pendapat pribadi.

@huxi Ya, saya sudah menautkannya di atas, tetapi tentu saja saya dapat mengulanginya: https://github.com/rust-lang/rust/issues/50547#issuecomment -388939886

Otak saya menerjemahkan @ menjadi "at" karena penggunaannya di alamat email. Simbol itu disebut "Klammeraffe" dalam bahasa ibu saya yang secara kasar diterjemahkan menjadi "monyet menempel". Saya benar-benar menghargai bahwa otak saya memilih "di" sebagai gantinya.

Saya punya cerita serupa: dalam bahasa saya itu "anjing", tetapi tidak memengaruhi pembacaan email.
Senang melihat pengalaman Anda.

@llambda pertanyaannya adalah tentang merantai. Tentu saja Anda bisa memasukkan variabel tambahan. Tapi bagaimanapun await { foo }? bukannya await? foo atau foo await? kelihatannya tidak benar.

@totorigolo
Pos yang bagus. Tapi menurut saya saran kedua Anda bukan cara yang baik untuk dilakukan. Ketika Anda memperkenalkan dua cara untuk melakukan sesuatu, Anda hanya menghasilkan kebingungan dan masalah, misalnya Anda memerlukan opsi rustfmt atau kode Anda menjadi berantakan.

@Hirevo async/await seharusnya menghilangkan kebutuhan pada kombinator. Mari kita tidak kembali ke "tetapi Anda dapat melakukannya dengan kombinator saja". Kode Anda sama dengan

future.then(|x| x.json()).map(|x| x.to_string()).map(|ret| ... );

Jadi mari kita hapus async/await alltogether?

Dikatakan, kombinator kurang ekspresif, kurang nyaman, dan terkadang Anda tidak bisa mengungkapkan apa yang bisa await , misalnya meminjam di antara titik menunggu (untuk apa dirancang Pin ).

Saya tidak melihat masalah keterbacaan, rangkaian, atau prioritas apa pun dalam kode berikut

async fn fetch_user(name: &str) -> Result<Vec<Permission>, Error> {

    let user = await? fetch(format!("/user/{0}", name).as_str())
        .map(|x| serde_json::from_str::<User>(x?))
        .then(|x| fetch(format!("/permissions/{0}", x?.id).as_str()))
        .map(|x| serde_json::from_str::<Vec<Permission>>(x?));

    Ok(user)
}

Saya melakukan:

async fn fetch_user(name: &str) -> Result<Vec<Permission>, Error> {
    let user = fetch(format!("/user/{0}", name).as_str()) await?;
    let user: User = serde_json::from_str(user);
    let permissions =  fetch(format!("/permissions/{0}", x.id).as_str()) await?;
    let permissions: Vec<Permission> = serde_json::from_str(permissions );
    Ok(user)
}

Sesuatu yang aneh sedang terjadi? Tidak masalah:

async fn fetch_user(name: &str) -> Result<Vec<Permission>, Error> {
    let user = dbg!(fetch(format!("/user/{0}", name).as_str()) await?);
    let user: User = dbg!(serde_json::from_str(user));
    let permissions = dbg!(fetch(format!("/permissions/{0}", x.id).as_str()) await?);
    let permissions: Vec<Permission> = dbg!(serde_json::from_str(permissions));
    Ok(user)
}

Lebih rumit untuk membuatnya berfungsi dengan kombinator. Belum lagi fungsi ? di then / map fungsi tidak akan berfungsi seperti yang diharapkan, dan kode Anda tidak akan berfungsi tanpa into_future() dan beberapa lainnya hal-hal aneh yang tidak Anda perlukan dalam aliran async/await .

Saya setuju bahwa proposisi saya tidak optimal karena memperkenalkan banyak sintaks hukum menggunakan kata kunci async . Namun saya yakin bahwa aturannya mudah dipahami dan memenuhi semua kasus penggunaan kami.

Tetapi sekali lagi, itu berarti banyak variasi yang diizinkan:

  • await!(...) , untuk konsistensi dengan try!() dan makro terkenal seperti println!()
  • await , await(...) , await { ... } , yaitu. sintaks awalan tanpa gula
  • await? , await?() , await? {} , yaitu. sintaks awalan dengan gula
  • ... await , yaitu. sintaks postfix (ditambah ... await? , tetapi yang bukan gula)
  • semua kombinasi dari itu, tetapi yang tidak didorong (lihat posting saya sebelumnya )

Tetapi dalam praktiknya kami hanya berharap untuk melihat:

  • awalan tanpa Result s: await , atau await { ... } untuk mengklarifikasi dengan ekspresi panjang
  • awalan dengan Result s: await? , atau await? {} untuk mengklarifikasi dengan ekspresi panjang
  • postfix saat merangkai: ... await , ... await?

1 untuk mengirim menunggu! () Makro ke stabil. Besok, jika memungkinkan 😄

Ada banyak spekulasi tentang bagaimana pola _akan_ digunakan dan kekhawatiran tentang ergonomi dalam kasus seperti itu. Saya menghargai kekhawatiran ini, tetapi saya tidak melihat alasan kuat mengapa ini tidak bisa menjadi perubahan yang berulang. Ini akan memungkinkan pembuatan metrik penggunaan nyata yang nantinya dapat menginformasikan pengoptimalan (dengan asumsi itu diperlukan).

Jika adopsi ke async Rust merupakan upaya yang lebih besar untuk peti / proyek besar daripada upaya refactoring _optional_ berikutnya untuk beralih ke sintaks yang lebih ringkas / ekspresif, maka saya sangat menganjurkan agar kami mengizinkan upaya adopsi tersebut untuk dimulai sekarang. Ketidakpastian yang terus berlanjut menyebabkan rasa sakit.

@Tokopedia
(Pertama, saya membuat kesalahan saat memanggil fungsi fetch_user, saya akan memperbaikinya di posting ini)

Saya tidak mengatakan bahwa async / await tidak berguna dan kita harus menghapusnya untuk kombinator.
Await memungkinkan untuk mengikat nilai dari masa depan ke variabel dalam lingkup yang sama setelah diselesaikan yang benar-benar berguna dan bahkan tidak mungkin hanya dengan kombinator.
Menghapus menunggu bukan maksud saya.
Saya baru saja mengatakan bahwa kombinator dapat bermain dengan sangat baik dengan menunggu, dan masalah rantai dapat diatasi hanya dengan menunggu seluruh ekspresi yang dibuat oleh kombinator (menghilangkan kebutuhan untuk await (await fetch("test")).json() atau await { await { fetch("test") }.json() } ).

Dalam contoh kode saya, ? berperilaku seperti yang dimaksudkan oleh hubungan arus pendek, membuat closure mengembalikan Err(...) , bukan seluruh fungsi (bagian itu ditangani oleh await? di seluruh rantai).

Anda secara efektif menulis ulang contoh kode saya tetapi Anda menghapus bagian rangkaiannya (dengan melakukan binding).
Misalnya, untuk bagian debugging, berikut ini memiliki perilaku yang sama persis hanya dengan dbg! dan tidak lebih (bahkan tanda kurung tambahan):

async fn fetch_permissions(name: &str) -> Result<Vec<Permission>, Error> {
    let user = await? fetch(format!("/user/{0}", name).as_str())
        .map(|x| dbg!(serde_json::from_str::<User>(dbg!(x)?)))
        .then(|x| fetch(format!("/permissions/{0}", x?.id).as_str())))
        .map(|x| dbg!(serde_json::from_str::<Vec<Permission>>(dbg!(x)?)));
    Ok(user)
}

Saya tidak tahu bagaimana melakukan hal yang sama tanpa kombinator dan tidak ada binding atau tanda kurung tambahan.
Beberapa orang melakukan perangkaian untuk menghindari mengisi ruang lingkup dengan variabel sementara.
Jadi saya hanya ingin mengatakan bahwa kombinator dapat berguna dan tidak boleh diabaikan saat membuat keputusan tentang sintaks tertentu terkait kemampuan perangkaiannya.

Dan, terakhir, karena penasaran, mengapa kode tersebut tidak akan berfungsi tanpa .into_future() , bukankah mereka sudah berjangka (saya bukan ahli dalam hal ini, tapi saya berharap mereka sudah berjangka)?

Saya ingin menyoroti masalah signifikan dengan fut await : ini sangat mengacaukan cara orang membaca kode. Ada nilai tertentu dalam ekspresi pemrograman yang mirip dengan frasa yang digunakan dalam bahasa natural, ini adalah salah satu alasan mengapa kami memiliki konstruksi seperti for value in collection {..} , mengapa di sebagian besar bahasa kami menulis a + b ("a plus b") daripada a b + , dan menulis / membaca "await something" jauh lebih natural untuk bahasa Inggris (dan bahasa SVO lainnya) daripada "something await". Bayangkan saja bahwa alih-alih ? kita akan menggunakan postfix try kata kunci: let val = foo() try; .

fut.await!() dan fut.await() tidak memiliki masalah ini karena mereka terlihat seperti panggilan pemblokiran yang sudah dikenal (tetapi "makro" juga menekankan "sihir" yang terkait), sehingga mereka akan dianggap berbeda dari spasi yang dipisahkan kata kunci. Sigil juga akan dianggap berbeda, dengan cara yang lebih abstrak, tanpa kesejajaran langsung dengan frasa bahasa alami, dan karena alasan ini menurut saya tidak relevan cara orang membaca sigil yang diusulkan.

Kesimpulannya: jika akan diputuskan untuk tetap menggunakan kata kunci, saya sangat yakin kita harus memilih dari varian awalan saja.

@rpjohnst Saya tidak yakin apa maksud Anda, kalau begitu. actual_fun(a + b)? dan break (a + b)? penting bagi saya karena prioritasnya berbeda, jadi saya tidak tahu seharusnya await(a + b)? itu.

Saya telah mengikuti diskusi ini dari jauh dan saya memiliki beberapa pertanyaan dan komentar.

Komentar pertama saya adalah saya percaya makro .await!() postfix memenuhi semua tujuan utama Centril dengan pengecualian poin pertama:

" await harus tetap menjadi kata kunci untuk memungkinkan desain bahasa di masa mendatang."

Apa kegunaan lain dari kata kunci await yang kita lihat di masa mendatang?


Edit : Saya salah paham bagaimana tepatnya await berfungsi. Saya telah menemukan pernyataan saya yang salah dan memperbarui contoh.

Komentar kedua saya adalah bahwa await kata kunci di Rust melakukan hal yang sama sekali berbeda dari await dalam bahasa lain dan ini berpotensi menyebabkan kebingungan dan kondisi balapan yang tidak terduga.

async function waitFor6SecondThenReturn6(){
  let result1 = await waitFor1SecondThenReturn1(); // executes first
  let result2 = await waitFor2SecondThenReturn2(); // executes second
  let result3 = await waitFor3SecondThenReturn3(); // executes third
  return result1 + result2 + result3;
}

Saya percaya bahwa awalan async melakukan pekerjaan yang lebih jelas yang menunjukkan bahwa nilai async adalah bagian dari mesin keadaan yang dibuat kompiler:

async function waitFor6SecondThenReturn6(){
  let async result1 = waitFor1SecondThenReturn1(); // executes first
  let async result2 = waitFor2SecondThenReturn2(); // executes second
  let async result3 = waitFor3SecondThenReturn3(); // executes third
  return result1 + result2 + result3;
}

Sangat intuitif bahwa fungsi async memungkinkan Anda menggunakan nilai async . Ini juga membangun intuisi bahwa ada mesin asinkron yang bekerja di latar belakang dan cara kerjanya.

Sintaksis ini memiliki beberapa pertanyaan yang belum terselesaikan dan masalah komposabilitas yang jelas, tetapi ini memperjelas di mana pekerjaan asinkron terjadi dan berfungsi sebagai pelengkap gaya sinkron untuk .await!() lebih dapat disusun dan dirantai.

Saya mengalami kesulitan memperhatikan postfix await di akhir beberapa ekspresi dalam contoh gaya prosedural sebelumnya jadi inilah yang akan terlihat dengan sintaks yang diusulkan ini:

async fn fetch_user(name: &str) -> Result<Vec<Permission>, Error> {
    let async user = dbg!(fetch(format!("/user/{0}", name).as_str()));
    let user: User = dbg!(serde_json::from_str(user?));
    let async permissions = dbg!(fetch(format!("/permissions/{0}", user.id).as_str()));
    let permissions: Vec<Permission> = dbg!(serde_json::from_str(permissions?));
    Ok(user)
}

(Ada juga argumen yang harus dibuat untuk makro .dbg!() disusun dan dirantai dengan mudah, tetapi itu untuk forum yang berbeda.)

Pada Selasa, 29 Jan 2019 pukul 23.31.32 -0800, Sphericon menulis:

Saya telah mengikuti diskusi ini dari jauh dan saya memiliki beberapa pertanyaan dan komentar.

Komentar pertama saya adalah saya percaya makro .await!() postfix memenuhi semua tujuan utama Centril dengan pengecualian poin pertama:

" await harus tetap menjadi kata kunci untuk memungkinkan desain bahasa di masa mendatang."

Sebagai salah satu pendukung utama sintaks .await!() , I
benar-benar berpikir await harus tetap menjadi kata kunci. Mengingat itu kami akan
perlu .await!() untuk dibangun ke dalam kompiler, sepertinya
sepele.

@Rumahsakitotak

Anda secara efektif menulis ulang contoh kode saya tetapi Anda menghapus bagian rangkaiannya (dengan melakukan binding).

Saya tidak mengerti pendekatan "rantai untuk mengikat" ini. Rangkaian bagus jika bagus, misalnya tidak membuat variabel temp yang tidak berguna. Anda mengatakan "Anda menghapus rantai", tetapi Anda dapat melihat bahwa itu tidak memberikan nilai apa-apa di sini.

Misalnya, untuk bagian debugging, berikut ini memiliki perilaku yang sama persis hanya dengan dbg! dan tidak lebih (bahkan tanda kurung tambahan)

Tidak, kamu melakukan ini

async fn fetch_user(name: &str) -> Result<Vec<Permission>, Error> {
    let user = fetch(format!("/user/{0}", name).as_str()) await?;
    let user: User = dbg!(serde_json::from_str(dbg!(user)));
    let permissions =  fetch(format!("/permissions/{0}", x.id).as_str()) await?;
    let permissions: Vec<Permission> = dbg!(serde_json::from_str(dbg!(permissions));
    Ok(user)
}

Itu bukan hal yang sama.

Saya baru saja mengatakan bahwa kombinator dapat bermain sangat baik dengan await, dan bahwa masalah rantai dapat diatasi dengan hanya menunggu seluruh ekspresi yang dibuat oleh kombinator (menghilangkan kebutuhan untuk await (await fetch ("test")). Json () or await {menunggu {fetch ("test")} .json ()}).

Ini hanya masalah untuk awalan await , tidak ada untuk bentuk lain itu.

Saya tidak tahu bagaimana melakukan hal yang sama tanpa kombinator dan tidak ada binding atau tanda kurung tambahan.

Mengapa tidak membuat binding ini? Binding selalu lebih baik untuk pembaca, misalnya, dan jika Anda memiliki debugger yang dapat mencetak nilai binding, tetapi tidak dapat mengevaluasi ekspresi.

Akhirnya, Anda tidak benar-benar menghapus binding apa pun. Anda baru saja menggunakan sintaks labmda |user| dan saya membuat let user = ... . Itu tidak menyelamatkan Anda apa pun, tetapi sekarang kode ini lebih sulit dibaca, lebih sulit untuk di-debug, dan tidak memiliki akses ke induknya (misalnya Anda harus membungkus kesalahan secara eksternal dalam rantai metode alih-alih melakukannya memanggil dirinya sendiri, mungkin).


Singkatnya: rantai tidak memberikan nilai dengan sendirinya. Ini mungkin berguna dalam beberapa skenario, tetapi itu bukan salah satunya. Dan karena saya menulis async / Tunggulah kode untuk lebih dari enam tahun, saya percaya Anda tidak ingin menulis dirantai async kode pernah. Bukan karena Anda tidak bisa, tetapi karena itu tidak nyaman dan pendekatan yang mengikat selalu lebih baik untuk dibaca dan sering ditulis. Bukan untuk mengatakan bahwa Anda tidak membutuhkan kombinator sama sekali. Jika Anda memiliki async/await , Anda tidak memerlukan ratusan metode ini pada futures / streams , Anda hanya perlu dua: join dan select . Segala sesuatu yang lain dapat dilakukan melalui iterator / bindings / ..., yaitu alat bahasa umum, yang tidak membuat Anda mempelajari infrastruktur lain.

Komunitas @Sphericon AFAIK setuju untuk menggunakan await sebagai kata kunci atau sigil, jadi untuk ide Anda tentang async memerlukan RFC lain.

Komentar kedua saya adalah bahwa kata kunci await di Rust melakukan hal yang sangat berbeda dari menunggu dalam bahasa lain dan ini berpotensi menyebabkan kebingungan dan kondisi balapan yang tidak terduga.

Bisakah Anda lebih detail? Saya tidak melihat perbedaan apa pun antara JS / C # await's, kecuali futures yang berbasis polling, tetapi tidak ada yang benar-benar ada hubungannya dengan async/await .

Mengenai sintaks prefiks await? diusulkan di beberapa tempat di sini:
`` C #
biarkan foo = menunggu? bar_async ();

How would this look with ~~futures of futures~~ result of results *) ? I.e., would it be arbitrarily extensible:
```C#
let foo = await?? double_trouble();

IOW, awalan await? tampak seperti sintaksis yang terlalu khusus bagi saya.

) * diedit.

Bagaimana ini akan terlihat dengan masa depan? Yaitu, apakah itu dapat diperluas secara sewenang-wenang:

let foo = await?? double_trouble();

IOW, await? tampak seperti sintaksis yang terlalu khusus bagi saya.

@rolandsteiner dengan "futures of futures" maksud Anda impl Future<Output = Result<Result<_, _>, _>> (satu await + dua ? menyiratkan "membuka bungkusan" satu masa depan dan dua hasil bagi saya, tidak menunggu berjangka berjangka ).

await? _is_ adalah kasus khusus, tetapi ini adalah kasus khusus yang mungkin berlaku untuk 90% + penggunaan await . Seluruh poin masa depan adalah sebagai cara untuk menunggu operasi _IO_ asinkron, IO bisa salah sehingga 90% + dari async fn kemungkinan akan mengembalikan io::Result<_> (atau beberapa jenis kesalahan lain yang menyertakan varian IO ). Fungsi yang mengembalikan Result<Result<_, _>, _> cukup langka saat ini, jadi saya tidak berharap mereka memerlukan sintaks kasus khusus.

@ Nemo157 Tentu saja Anda benar: Hasil dari Hasil. Memperbarui komentar saya.

Hari ini, kami menulis

  1. await!(future?) untuk future: Result<Future<Output=T>,E>
  2. await!(future)? untuk future: Future<Output=Result<T,E>>

Dan jika kita menulis await future? kita harus menentukan yang mana artinya.

Tapi apakah kasus 1 selalu bisa berubah menjadi kasus 2? Dalam kasus 1, ekspresi menghasilkan masa depan, atau kesalahan. Tapi kesalahan bisa ditunda dan dipindahkan ke masa depan. Jadi kita bisa menangani kasus 2 dan membuat konversi otomatis terjadi di sini.

Dalam sudut pandang programmer, Result<Future<Output=T>,E> garantees pengembalian awal untuk kasus kesalahan, tetapi kecuali bahwa keduanya memiliki sementik yang sama. Saya dapat membayangkan kompiler dapat bekerja dan menghindari panggilan tambahan poll jika kasus kesalahan segera terjadi.

Jadi proposalnya adalah:

await exp? dapat diartikan sebagai await (exp?) jika exp adalah Result<Future<Output=T>,E> , dan diartikan sebagai (await exp)? jika exp adalah Future<Output=Result<T,E>> . Dalam kedua kasus, ini akan mengembalikan kesalahan lebih awal, dan menyelesaikan ke hasil sebenarnya jika berjalan dengan baik.

Untuk kasus yang lebih rumit, kita dapat menerapkan sesuatu seperti dereferensi penerima metode otomatis:

> Saat menginterupsi await exp???? pertama-tama kita periksa exp dan jika Result , try itu dan terus berjalan ketika hasilnya masih Result sampai kehabisan ? atau memiliki sesuatu yang bukan Result . Maka itu harus menjadi masa depan dan kami await di atasnya dan menerapkan sisanya ? s.

Saya adalah pendukung kata kunci postfix / sigil dan masih. Namun saya hanya ingin menunjukkan bahwa awalan yang diutamakan mungkin bukan masalah besar dalam praktiknya dan ada solusi.

Saya tahu anggota tim Rust tidak menyukai hal-hal yang implisit, tetapi dalam kasus seperti itu, perbedaannya terlalu kecil antara potensi sementik dan kami memiliki cara yang baik untuk memastikan kami melakukan hal yang benar.

await? adalah kasus khusus, tetapi ini kasus khusus yang kemungkinan besar akan berlaku untuk 90% + penggunaan await . Inti dari masa depan adalah sebagai cara untuk menunggu operasi IO asinkron, IO bisa salah sehingga 90% + dari async fn kemungkinan akan mengembalikan io::Result<_> (atau beberapa jenis kesalahan lain yang menyertakan varian IO ). Fungsi yang mengembalikan Result<Result<_, _>, _> cukup langka saat ini, jadi saya tidak mengharapkan mereka memerlukan sintaks kasus khusus.

Kasus-kasus khusus buruk untuk dibuat, diperluas, atau dipelajari, dan akhirnya berubah menjadi bagasi. Bukan kompromi yang baik untuk membuat pengecualian pada aturan bahasa untuk satu kasus penggunaan kegunaan teoretis.

Apakah mungkin menerapkan Future untuk Result<T, E> where T: Future ? Dengan begitu Anda bisa hanya await result_of_future tanpa perlu membukanya dengan ? . Dan itu tentu saja akan mengembalikan Hasil, jadi Anda akan menyebutnya await result_of_future , yang berarti (await result_of_future)? . Dengan begitu kita tidak memerlukan sintaks await? dan sintaks prefiks akan sedikit lebih konsisten. Beri tahu saya jika ada yang salah dengan ini.

Argumen tambahan untuk await dengan pembatas wajib menyertakan (secara pribadi saya tidak yakin sintaks mana yang paling saya sukai secara keseluruhan):

  • Tidak ada casing khusus dari operator ? , tidak ada await? atau await??
  • Sesuai dengan operator aliran kontrol yang ada seperti loop , while , dan for , yang juga membutuhkan pembatas wajib
  • Terasa paling nyaman dengan konstruksi Rust serupa yang ada
  • Menghilangkan casing khusus membantu menghindari masalah saat menulis makro
  • Tidak menggunakan sigils atau postfix, menghindari pengeluaran dari keanehan anggaran

Contoh:

let p = if y > 0 { op1() } else { op2() };
let p = await { p }?;

Namun, setelah bermain-main dengan ini di editor, masih terasa tidak praktis. Saya pikir saya lebih suka memiliki await dan await? tanpa pembatas, seperti dengan break dan return .

Apakah mungkin untuk menerapkan Future for Resultdimana T: Masa Depan?

Anda pasti menginginkan kebalikannya. Yang paling umum ditunggu adalah Future di mana tipe outputnya adalah Result.

Ada kemudian argumen eksplisit agaisnt menyembunyikan atau menyerap ? menjadi hanya menunggu. Bagaimana jika Anda ingin mencocokkan hasilnya, dll.

Jika Anda memiliki Result<Future<Result<T, E2>>, E1> , menunggu itu akan menghasilkan Result<Result<T, E2>, E1> .

Jika Anda memiliki Future<Result<T, E1>> , maka menunggunya hanya akan mengembalikan Result<T, E1> .

Tidak ada menyembunyikan atau menyerap ? ke dalam menunggu, dan Anda dapat melakukan apapun yang diperlukan dengan Hasil sesudahnya.

Oh. Aku pasti salah paham denganmu. Saya tidak melihat bagaimana itu membantu karena kita masih perlu menggabungkan ? dengan menunggu 99% dari waktu.


Oh. Sintaks await? seharusnya menyiratkan (await future)? yang akan menjadi kasus umum.

Persis. Jadi kita hanya akan membuat await mengikat lebih ketat di await expr? , dan jika ekspresi itu adalah Result<Future<Result<T, E2>>, E1> maka itu akan mengevaluasi sesuatu dari jenis Result<T, E2> . Ini berarti tidak ada casing khusus untuk menunggu jenis Hasil. Ini hanya mengikuti implementasi sifat normal.

@ivandardi bagaimana dengan Result<Future<Item=i32, Error=SomeError>, FutCreationError> ?

@Pzixel Perhatikan, bentuk masa depan itu hilang. Ada satu jenis terkait sekarang, Output (yang mungkin akan menjadi Hasil).


@ivardi Baik. Saya melihatnya sekarang. Satu-satunya hal yang Anda miliki terhadap Anda di sana adalah diutamakan menjadi sesuatu yang aneh yang harus Anda pelajari di sana karena itu adalah penyimpangan tetapi begitu juga dengan await saya kira.

Meskipun Hasil yang mengembalikan masa depan sangat jarang, saya belum menemukan satu kasus pun selain sesuatu di inti tokio yang telah dihapus jadi saya rasa kita tidak memerlukan gula / sifat yang tersirat untuk membantu dalam kasus itu.

@ivandardi bagaimana dengan Result<Future<Item=i32, Error=SomeError>, FutCreationError> ?

Yah, saya akan berasumsi bahwa itu tidak mungkin, karena sifat Future hanya memiliki tipe terkait Output .


@mehcode Ya, ini mengatasi beberapa masalah yang telah

Yah, saya akan berasumsi bahwa itu tidak mungkin, melihat bahwa sifat Masa Depan hanya memiliki tipe terkait Output.

kenapa tidak?

fn probably_get_future(val: u32) -> Result<impl Future<Item=i32, Error=u32>, &'static str> {
    match val {
        0 => Ok(ok(15)),
        1 => Ok(err(100500)),
        _ => Err("Coulnd't create a future"),
    }
}

@Pzixel Lihat https://doc.rust-lang.org/std/future/trait.Future.html

Anda berbicara tentang sifat lama yang ada di peti futures .

Sejujurnya, saya tidak berpikir bahwa memiliki kata kunci di posisi awalan seperti yang seharusnya:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = (yield self.request(url, Method::GET, None, true)))?;
    let user = (yield user.res.json::<UserResponse>())?;
    let user = user.user.into();
    Ok(user)
}

memiliki keuntungan lebih besar dibandingkan memiliki sigil di posisi yang sama:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = (*self.request(url, Method::GET, None, true))?;
    let user = (*user.res.json::<UserResponse>())?;
    let user = user.user.into();
    Ok(user)
}

Saya pikir itu hanya terlihat tidak konsisten dengan operator awalan lain, menambahkan spasi kosong sebelum ekspresi, dan menggeser kode ke sisi kanan pada jarak yang terlihat.


Kami dapat mencoba menggunakan sigil dengan sintaks titik yang diperpanjang ( Pre-RFC ) yang menyelesaikan masalah dengan cakupan yang sangat bertingkat:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.[*request(url, Method::GET, None, true)]?;
    let user = user.res.[*json::<UserResponse>()]?;
    let user = user.user.into();
    Ok(user)
}

serta menambahkan kemungkinan untuk metode rantai:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.[*request(url, Method::GET, None, true)]?
        .res.[*json::<UserResponse>()]?
        .user
        .into();
    Ok(user)
}

Dan tentu saja, mari ganti * dengan @ yang lebih masuk akal di sini:

async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = (@self.request(url, Method::GET, None, true))?;
    let user = (@user.res.json::<UserResponse>())?;
    let user = user.user.into();
    Ok(user)
}
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.[@request(url, Method::GET, None, true)]?;
    let user = user.res.[<strong i="27">@json</strong>::<UserResponse>()]?;
    let user = user.user.into();
    Ok(user)
}
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.[@request(url, Method::GET, None, true)]?
        .res.[<strong i="30">@json</strong>::<UserResponse>()]?
        .user
        .into();
    Ok(user)
}

Yang saya suka di sini adalah bahwa @ mencerminkan await yang ditempatkan di kiri deklarasi fungsi, sedangkan ? mencerminkan Result<User> yang ditempatkan di kanan. deklarasi fungsi. Ini membuat @ sangat konsisten dengan ? .


Ada pemikiran tentang ini?

Ada pemikiran tentang ini?

Kawat gigi tambahan tidak boleh digunakan

@mehcode Ya, saya tidak menyadari bahwa futures sekarang kekurangan tipe Error . Tetapi intinya masih valid: Anda dapat memiliki funtion, yang mungkin mengembalikan fitur, yang, ketika selesai, dapat mengembalikan hasil atau kesalahan.

Ada pemikiran tentang ini?

Iya! Menurut komentar Centril , sigils tidak terlalu bisa diraih. Saya hanya akan mulai mempertimbangkan sigils jika seseorang dapat membuat regex yang mengidentifikasi semua titik menunggu.

Sedangkan untuk proposal sintaks titik diperpanjang Anda, Anda harus menjelaskannya lebih dalam, memberikan semantik dan desugaring dari setiap penggunaannya. Untuk saat ini, saya tidak dapat memahami arti cuplikan kode yang Anda posting dengan sintaks tersebut.


Tetapi intinya masih valid: Anda dapat memiliki funtion, yang mungkin mengembalikan fitur, yang, ketika selesai, dapat mengembalikan hasil atau kesalahan.

Jadi, Anda akan memiliki Future<Item=Result<T, E>> , bukan? Nah, kalau begitu, kamu hanya ... menunggu masa depan dan menghadapi hasilnya 😐

Misalkan foo adalah tipe Future<Item=Result<T, E>> . Kemudian await foo akan menjadi Result<T, E> dan Anda dapat menggunakan yang berikut ini untuk mengatasi kesalahan:

await foo?;
await foo.unwrap();
match await foo { ... }
await foo.and_then(|x| x.bar())

@melodstream

Jadi, Anda akan memiliki Masa Depan>, kan? Nah, kalau begitu, kamu hanya ... menunggu masa depan dan menghadapi hasilnya 😐

Tidak, maksud saya Result<Future<Item=Result<T, E>>, OtherE>

Dengan varian postfix yang baru saja Anda lakukan

let bar = foo()? await?.bar();

@melodicstream Saya memperbarui posting saya dan menambahkan tautan ke pra-RFC untuk fitur itu

Ya ampun ... Saya baru saja membaca ulang semua komentar untuk ketiga kalinya ...

Satu-satunya perasaan yang konsisten adalah ketidaksukaan saya terhadap notasi postfix .await dalam semua variasinya karena saya benar-benar mengharapkan await menjadi bagian dari Future dengan sintaks tersebut. "Kekuatan titik" mungkin berfungsi untuk IDE, tetapi tidak berfungsi sama sekali untuk saya. Saya pasti bisa beradaptasi dengannya jika itu adalah sintaks yang distabilkan tetapi saya ragu apakah itu akan benar-benar terasa "tepat" bagi saya.

Saya akan menggunakan notasi awalan await super dasar tanpa tanda kurung wajib, terutama karena prinsip KISS dan karena melakukannya mirip dengan kebanyakan bahasa lain sangat berharga.

De-sugaring await? future menjadi (await future)? akan baik-baik saja dan dihargai tetapi apa pun di luar itu tampaknya lebih dan lebih seperti solusi tanpa masalah bagi saya. Sederhana let rebinding meningkatkan keterbacaan kode di sebagian besar contoh dan saya, secara pribadi, kemungkinan akan turun rute itu sambil menulis kode bahkan jika rantai yang mudah adalah pilihan.

Dengan itu, saya cukup senang bahwa saya tidak dalam posisi untuk memutuskan tentang ini.

Sekarang saya akan membiarkan masalah ini sendiri alih-alih menambahkan kebisingan lebih lanjut dan menunggu (permainan kata-kata) putusan akhir.

... setidaknya saya akan dengan jujur ​​mencoba melakukan itu ...

@Pzixel Dengan asumsi foo adalah tipe Result<Future<Item=Result<T, E1>>, E2> , maka await foo akan menjadi tipe Result<Result<T, E1>, E2> dan kemudian Anda dapat menangani Hasil tersebut sesuai dengan itu.

await foo?;
await foo.and_then(|x| x.and_then(|y| y.bar()));
await foo.unwrap().unwrap();

@ Melodicstream tidak, tidak akan. Anda tidak bisa menunggu Result, Anda bisa menunggu Future. So you have to do foo ()? to unwrap Masa Depan from Hasil , then do menunggu to get a result, then again ? `Untuk membuka hasil dari masa mendatang.

Dalam cara postfix akan menjadi foo? await? , sebagai awalan ... Saya tidak yakin.

Jadi contoh Anda tidak berhasil, terutama yang terakhir, karena memang seharusnya begitu

(await foo.unwrap()).unwrap()

Namun, @huxi mungkin benar, kami sedang memecahkan masalah yang mungkin tidak ada. Cara terbaik untuk mengetahuinya memungkinkan makro postfix dan melihat basis kode nyata setelah adopsi dasar async/await .

@Pzixel Itulah mengapa saya membuat proposal untuk mengimplementasikan Future pada semua jenis Result<Future<Item=T>, E> . Melakukan itu akan memungkinkan apa yang saya katakan.

Meskipun saya OKE dengan await foo?? untuk Result<Future<Output=Result<T, E1>>, E2> , saya TIDAK senang dengan await foo.unwrap().unwrap() . Dalam model otak pertama saya, ini harus terjadi

(await foo.unwrap()).unwrap()

Kalau tidak, saya akan sangat membingungkan. Alasannya adalah bahwa ? adalah operator umum, dan unwrap adalah metode. Kompilator dapat melakukan sesuatu yang khusus untuk operator seperti . , tetapi jika ini adalah metode normal, saya akan menganggap itu selalu berhubungan dengan ekspresi terdekat di sisi kiri, saja.

Sintaks postfix, foo.unwrap() await.unwrap() , juga OK untuk saya, karena saya tahu bahwa await hanyalah sebuah kata kunci, bukan objek, jadi itu harus menjadi bagian dari ekspresi sebelum unwrap() .

makro gaya postfix memecahkan sebagian besar masalah ini dengan baik, tetapi pertanyaannya adalah apakah kita ingin mempertahankan keakraban dengan bahasa yang ada dan menjaganya tetap awalan. Saya akan memilih gaya postfix.

Apakah saya benar bahwa kode berikut tidak sama:

fn foo(n: u32) -> impl Future<Item = u32> {
   if n == 0 {
      panic!("Can't be zero");
   } else {
      do_async_call().map(|_| 10)
   }
}

dan

async fn foo(n: u32) -> u32 {
   if n == 0 {
      panic!("Can't be zero");
   } else {
      await!(do_async_call());
      10
   }
}

Yang pertama mengisi kepanikan ketika mencoba menciptakan masa depan, sedangkan yang kedua akan panik pada jajak pendapat pertama. Jika demikian, haruskah kita melakukan sesuatu dengannya? Ini bisa diselesaikan seperti

fn foo(n: u32) -> impl Future<Item = u32> {
   if n == 0 {
      panic!("Can't be zero");
   } else {
      async {
         await!(do_async_call());
         10 
      }
   }
}

Tapi itu jauh lebih tidak nyaman.

@Pzixel : ini adalah keputusan yang telah dibuat. async fn sepenuhnya malas dan tidak berjalan sama sekali hingga disurvei, dan dengan demikian menangkap semua masa pakai input untuk seluruh masa mendatang. Solusinya adalah fungsi pembungkus mengembalikan impl Future menjadi eksplisit, dan menggunakan blok async (atau fn) untuk penghitungan yang ditangguhkan. Ini _ diperlukan_ untuk menumpuk Future kombinator tanpa alokasi di antara masing-masing, karena setelah jajak pendapat pertama mereka tidak dapat dipindahkan.

Ini adalah keputusan yang sudah selesai dan mengapa sistem menunggu implisit tidak berfungsi (dengan baik) untuk Rust.

C # developer di sini, dan saya akan mendukung gaya awalan, jadi bersiaplah untuk downvote.

Sintaks Postfix dengan titik ( read().await atau read().await() ) sangat menyesatkan dan menyarankan akses field atau pemanggilan metode, dan await bukan salah satu dari hal-hal itu; itu adalah fitur bahasa. Saya bukan Rustacean berpengalaman dengan cara apa pun, tetapi saya tidak mengetahui kasus .something yang secara efektif merupakan kata kunci yang akan ditulis ulang oleh kompilator. Yang paling dekat yang dapat saya pikirkan adalah akhiran ? .

Setelah async / menunggu di C # selama beberapa tahun sekarang, sintaks prefiks-dengan-spasi standar ( await foo() ) tidak menyebabkan saya masalah selama itu. Dalam kasus di mana await bertingkat diperlukan, tidak sulit menggunakan tanda kurung, yang merupakan sintaks standar untuk semua prioritas operator eksplisit dan oleh karena itu mudah dilakukan, misalnya

`` c #
var list = (menunggu GetListAsync ()). ToList ();


`await` is essentially a unary operator, so treating it as such makes sense.

In C# 8.0, currently in preview, we have async enumerables (iterators), which comes with an `IAsyncEnumerable<T>` interface and `await foreach (var x in QueryAsync())` syntax. The lack of async enumerables has been an issue since async was added in C# 5; for example, we have ORMs that return a `Task<IEnumerable>` which is only half asynchronous; we have to block on calls to `MoveNext()`. Having proper async enumerables is a big deal.

If a postfix `await` is used and similar async iterator support is added to Rust, that would look something like

```rust
for val in v_async_iter {
    println!("Got: {}", val);
} await // or .await or .await()

Sepertinya aneh.

Saya pikir konsistensi dengan bahasa lain juga merupakan sesuatu yang perlu diberi bobot. Saat ini kata kunci await sebelum ekspresi asinkron adalah sintaks untuk C #, VB.NET, JavaScript dan Python, dan ini adalah sintaks yang diusulkan untuk C ++ juga. Itu lima dari tujuh bahasa teratas di dunia; C dan Java belum memiliki async / await, tetapi saya akan terkejut jika keduanya menggunakan cara lain.

Kecuali ada alasan yang benar-benar baik untuk pergi dengan cara yang berbeda, atau dengan kata lain, jika kedua awalan dan postfix menimbang sama dalam hal keuntungan dan kompromi, saya sarankan bahwa ada banyak yang harus dikatakan untuk tidak resmi " standar "sintaks.

@markrendle Saya juga C # dev, menulis async/await sejak 2013. Dan saya juga suka awalan. Tapi Rust sangat berbeda.

Anda mungkin tahu bahwa (await Foo()).ToList() adalah kasus yang jarang terjadi. Dalam kebanyakan kasus Anda hanya menulis await Foo() , dan jika Anda membutuhkan tipe lain Anda mungkin ingin memperbaiki tanda tangan Foo .

Tapi karat adalah hal lain. Kami memiliki ? sini dan kami harus menyebarkan kesalahan. Di C # async / await secara otomatis membungkus pengecualian, melemparkannya ke titik tunggu dan seterusnya. Anda tidak memilikinya di karat. Pertimbangkan setiap kali Anda menulis await di C # Anda harus menulis

var response = (await client.GetAsync("www.google.com")).HandleException();
var json =  (await response.ReadAsStreamAsync()).HandleException();
var somethingElse = (await DoMoreAsyncStuff(json)).HandleException();
...

Sangat membosankan memiliki semua kawat gigi ini.

OTOH dengan postfix yang Anda miliki

var response = client.GetAsync("www.google.com") await.HandleException();
var json =  response.ReadAsStreamAsync() await.HandleException();
var somethingElse = DoMoreAsyncStuff(json) await.HandleException();
...

atau dalam istilah karat

let response = client.get_async("www.google.com") await?;
let json =  response.read_as_Stream_async() await?;
let somethingElse = do_more_async_stuff(json) await?;
...

Dan imaine kita akan memiliki beberapa trafo lain, selain ? dan await , sebut saja @ . Jika kita menemukan sintaks tambahan untuk await? , maka kita harus memiliki await@ , @? , await@? , ... untuk membuatnya konsisten.


Saya suka awalan await juga, tapi saya tidak yakin apakah Rust baik-baik saja dengan itu. Menambahkan aturan tambahan ke dalam bahasa mungkin tidak masalah, tetapi ini adalah beban yang mungkin tidak sebanding dengan kemudahan dibaca.

Baca ulang semua komentar termasuk melacak utas masalah (menghabiskan hampir 3 jam untuk ini). Dan ringkasan saya adalah: postfix @ terlihat seperti solusi terbaik, dan sayangnya itu yang paling kurang dihargai.

Ada banyak pendapat (bias) tentang itu, dan di bawah ini adalah komentar saya untuk mereka:


async-await akan asing dengan postfix @

Sebenarnya, itu hanya separuh asing karena sintaks async fn akan disediakan, dan fungsionalitas yang sama seperti dengan await akan tetap tersedia.

Rust memiliki banyak sigil dan kita tidak boleh memasukkan sigil lagi

Tetapi sigil baik-baik saja sampai dapat dibaca dan konsisten dengan sintaks lainnya. Dan memperkenalkan sigil baru tidak sepenuhnya berarti itu akan memperburuk keadaan. Dalam perspektif konsistensi postfix @ adalah sintaks yang jauh lebih baik daripada prefiks / postfix await .

Potongan seperti ?!@# mengingatkan saya pada Perl

Pada kenyataannya kita jarang melihat kombinasi lain selain @? yang IMO terlihat cukup lugas dan ergonomis. Jenis pengembalian seperti impl Try<impl Future<T>> , impl Future<impl Try<impl Try<T>>> , dan bahkan impl Future<T> , akan terjadi paling banyak dalam 10% penggunaan seperti yang dapat kita lihat pada contoh dunia nyata ITT. Selain itu, konglomerasi sigil di 90% dari 10% ini akan menunjukkan bau kode ketika Anda harus memperbaiki API Anda atau memperkenalkan pengikatan sementara sebagai gantinya untuk memperjelasnya.

Postfix @ sangat sulit untuk diperhatikan!

Dan itu sebenarnya keuntungan besar dalam konteks async-await . Sintaks ini memperkenalkan kemampuan untuk mengekspresikan kode asinkron seperti itu sinkron, jadi lebih mengganggu await hanya mengganggu tujuan aslinya. Contoh di atas dengan jelas menunjukkan bahwa kemunculan poin hasil mungkin cukup padat untuk membengkakkan kode jika terlalu eksplisit. Tentu saja kita harus melihatnya, namun @ sigil akan baik-baik saja untuk itu serta await tanpa membengkakkan kode.

Menunggu masa depan adalah operasi yang mahal dan kami harus mengatakannya secara eksplisit

Ini benar-benar masuk akal, namun saya rasa kita tidak harus mengatakannya terlalu sering dan keras. Saya pikir bisa diberikan analogi dengan sintaks mutasi: mut diperlukan secara eksplisit hanya sekali dan selanjutnya kita dapat menggunakan pengikatan yang bisa berubah secara implisit. Analogi lain dapat diberikan dengan sintaks yang tidak aman: eksplisit unsafe diperlukan hanya sekali dan selanjutnya kita dapat membedakan petunjuk mentah, memanggil fungsi yang tidak aman, dll. Dan analogi lain dapat diberikan dengan operator gelembung: eksplisit impl Try return atau blok try akan datang hanya diperlukan sekali dan selanjutnya kita dapat menggunakan operator ? percaya diri. Jadi, dengan cara yang sama eksplisit async menjelaskan operasi yang mungkin tahan lama hanya sekali, dan selanjutnya kita dapat menerapkan @ operator yang sebenarnya akan melakukannya.

Aneh karena saya membacanya secara berbeda dari await , dan saya tidak dapat mengetiknya dengan mudah

Jika Anda membaca & sebagai "ref" dan ! sebagai "bukan", maka menurut saya cara Anda membaca @ saat ini bukanlah argumen yang kuat untuk tidak menggunakan ini simbol. Juga tidak masalah seberapa cepat Anda mengetiknya karena membaca kode selalu menjadi prioritas yang lebih penting. Dalam kedua kasus, mengadopsi @ sangat sederhana.

Ini ambigu karena @ sudah digunakan dalam pencocokan pola

Saya tidak berpikir itu masalah karena ! sudah digunakan di makro, & sudah digunakan untuk meminjam dan di operator && , | digunakan dalam penutupan, dalam pola, dan dalam || operator, + / - dapat diterapkan dalam posisi awalan juga, dan . belum dapat digunakan dalam konteks yang lebih berbeda. Tidak ada yang mengejutkan dan tidak ada yang salah dalam menggunakan kembali simbol yang sama dalam konstruksi sintaks yang berbeda.

Akan sulit untuk grep

Jika Anda ingin memeriksa apakah beberapa sumber berisi pola asinkron, maka rg async akan menjadi solusi yang lebih sederhana dan mudah. Jika Anda benar-benar ingin menemukan semua poin hasil dengan cara ini, maka Anda akan dapat menulis sesuatu seperti rg "@\s*[;.?|%^&*-+)}\]]" yang tidak langsung, tetapi juga bukan sesuatu yang luar biasa. Mempertimbangkan seberapa sering diperlukan, IMO regex ini baik-baik saja.

@ tidak ramah pemula, dan sulit untuk google

Saya pikir ini sebenarnya lebih mudah dipahami untuk pemula karena berfungsi secara konsisten dengan operator ? . Sebaliknya, awalan / postfix await tidak akan konsisten dengan sintaks Rust lainnya selain itu juga menyebabkan sakit kepala dengan asosiatif / pemformatan / makna. Saya tidak berpikir bahwa await akan menambah beberapa nilai yang mendokumentasikan diri sendiri karena kata kunci tunggal tidak dapat menggambarkan keseluruhan konsep yang mendasari dan pemula akan mempelajarinya dari buku. Dan Rust tidak pernah menjadi bahasa yang memberi Anda petunjuk teks biasa untuk setiap kemungkinan konstruksi sintaks. Jika seseorang lupa arti simbol @ (saya benar-benar ragu bahwa itu akan menjadi masalah karena ada banyak asosiasi yang mengingatnya dengan benar) - maka googling untuk itu seharusnya berhasil juga karena saat ini rust @ symbol mengembalikan semua informasi yang relevan tentang @ dalam pencocokan pola.

Kami sudah mencadangkan await kata kunci, jadi kami tetap harus menggunakannya

Mengapa? Tidak masalah untuk memiliki kata kunci cadangan yang tidak terpakai jika ternyata implementasi fitur akhir lebih baik tanpanya. Selain itu, tidak ada janji yang diberikan bahwa tepat await kata kunci akan diperkenalkan. Dan pengenal ini tidak terlalu umum dalam kode dunia nyata, jadi kami tidak akan kehilangan apa pun saat akan tetap dicadangkan hingga edisi berikutnya.

? adalah kesalahan dan @ akan menjadi kesalahan kedua

Mungkin Anda berpikir lebih leksikal, dan karena itu Anda lebih suka bekerja dengan kata-kata daripada simbol. Dan karena itu Anda tidak melihat nilai apa pun dalam sintaks yang lebih tipis. Namun perlu diingat bahwa lebih banyak orang yang berpikir lebih visual, dan bagi mereka kata-kata yang mengganggu lebih sulit untuk dikerjakan. Mungkin kompromi yang baik di sini akan menyediakan makro await!() bersama, seperti try!() disediakan bersama dengan ? , oleh karena itu Anda akan dapat menggunakannya daripada @ jika Anda merasa tidak nyaman dengan @ .

^^^^^^^ apakah Anda membaca ulang komentar saya juga?

Saya mendapat wahyu besar membaca komentar @ I60R yang telah membuat saya mendukung sigil postfix, dan saya tidak suka menggunakan sigil sampai sekarang (sebagai gantinya lebih memilih beberapa bentuk postfix menunggu).

Operasi menunggu kami bukanlah await . Setidaknya tidak dengan cara await digunakan dalam C # dan JavaScript, dua sumber terbesar dari keakraban async / await .

Dalam bahasa ini, semantik memulai Task (C #) / Promise (JS) adalah bahwa tugas segera dimasukkan ke antrian tugas untuk dieksekusi. Di C # ini adalah konteks multi-threaded, di JS ini adalah single-threaded.

Tetapi dalam salah satu bahasa ini, await foo semantik berarti "parkir tugas ini dan tunggu penyelesaian tugas foo , dan sementara itu sumber daya komputasi yang digunakan oleh tugas ini dapat digunakan oleh yang lain. tugas ".

Inilah sebabnya mengapa Anda mendapatkan paralelisme dengan memisahkan await s setelah membangun beberapa Task / Promise s (bahkan pada eksekutor utas tunggal): semantik await aren Bukan "menjalankan tugas ini" melainkan "menjalankan beberapa tugas" sampai selesai menunggu dan kita dapat melanjutkan.

Ini sepenuhnya terpisah dari lazy- atau eager-till-first-await dimulainya tugas saat dibuat, meskipun sangat terkait. Saya mengambil tentang mengantri sisa tugas setelah pekerjaan sinkron selesai.

Kami sudah memiliki kebingungan di sepanjang baris ini pada pengguna dari iterator malas dan masa depan saat ini. Saya mengandaikan sintaks await tidak membantu kita di sini.

Jadi await!(foo) kita bukanlah await foo dalam cara "tradisional" C # atau JS. Jadi bahasa apa yang lebih dekat hubungannya dengan semantik kita? Sekarang saya sangat yakin bahwa itu ( @matklad perbaiki saya jika saya memiliki detail yang salah) Kotlin suspend fun . Atau seperti yang dirujuk dalam diskusi yang berdekatan dengan Rust, "asinkron eksplisit, menunggu tersirat".

Ulasan singkat: di Kotlin, Anda hanya dapat memanggil suspend fun dari dalam konteks suspend . Ini segera menjalankan fungsi yang dipanggil hingga selesai, menangguhkan konteks yang ditumpuk seperti yang diperlukan oleh fungsi turunan. Jika Anda ingin menjalankan anak suspend fun secara paralel, Anda membuat yang setara dengan penutupan asinkron, dan menggabungkannya dengan kombinasi suspend fun untuk menjalankan beberapa penutupan suspend secara paralel. (Ini sedikit lebih terlibat dari itu.) Jika Anda ingin menjalankan anak suspend fun di latar belakang, Anda membuat tipe tugas yang menempatkan tugas itu pada eksekutor dan mengekspos .await() suspend fun metode untuk menggabungkan tugas Anda kembali dengan tugas latar belakang.

Arsitektur ini, meskipun dideskripsikan dengan kata kerja yang berbeda dari Rust's Future , terdengar sangat familiar.

Saya sebelumnya telah menjelaskan mengapa menunggu implisit tidak bekerja untuk Rust, dan berdiri di posisi itu. Kita membutuhkan async fn() -> T dan fn() -> Future<T> agar setara dalam penggunaan sehingga pola "lakukan beberapa pekerjaan awal" menjadi mungkin (untuk membuat masa aktif bekerja dalam kasus yang rumit) dengan masa depan yang malas. Kita membutuhkan masa depan yang malas sehingga kita dapat menumpuk masa depan tanpa lapisan tipuan di antara masing-masing untuk penyematan.

Tapi sekarang saya yakin sintaks "eksplisit tapi tenang" dari foo@ (atau sigil lain) masuk akal. Meskipun saya enggan mengucapkan kata di sini, @ dalam hal ini adalah operasi monadik di async seperti ? adalah operasi monadik di try .

Ada beberapa pendapat bahwa memercikkan ? dalam kode Anda hanya untuk membuat kompiler berhenti mengeluh ketika Anda mengembalikan Result . Dan dalam arti tertentu, Anda melakukan ini. Tapi itu untuk menjaga kejelasan kode lokal ideal kami. Saya perlu tahu apakah operasi ini dibongkar melalui monad atau dipahami secara lokal.

(Jika detail monad saya salah, saya yakin seseorang akan mengoreksi saya, tetapi saya yakin maksud saya masih berlaku.)

Jika @ berperilaku dengan cara yang sama, saya pikir ini adalah hal yang baik. Ini bukan tentang memutar sejumlah tugas lalu await menyelesaikan tugasnya, ini tentang membangun mesin status yang perlu dipompa orang lain hingga selesai, apakah itu dengan membangunnya ke dalam mesin status mereka sendiri atau memasukkannya ke dalam konteks eksekusi.

TL; DR: jika Anda mengambil satu hal dari posting blog mini ini, biarlah ini: Rust's await!() bukan await seperti dalam bahasa lain. Hasilnya _similar_ tetapi semantik tentang bagaimana tugas ditangani sangat berbeda. Dari sini kami mendapat manfaat dari tidak menggunakan sintaks umum, karena kami tidak memiliki perilaku umum.

(Telah disebutkan dalam diskusi sebelumnya bahasa yang berubah dari masa depan malas menjadi bersemangat. Saya percaya ini karena await foo menyarankan bahwa foo sudah terjadi, dan kami hanya menunggu itu menghasilkan hasil.)

EDIT: jika Anda ingin membahas ini dengan cara yang lebih sinkron, ping saya @ CAD di Discord pengembangan karat (tautannya 24j) atau internal atau pengguna (@ CAD97). Saya ingin menempatkan posisi saya terhadap beberapa pengawasan langsung.

EDIT 2: Maaf GitHub @ cad untuk ping yang tidak disengaja; Saya mencoba untuk melepaskan diri dari @ dan tidak bermaksud untuk menarik Anda ke dalamnya.

@ I60

Potongan seperti ?!@# mengingatkan saya pada Perl

Pada kenyataannya kita jarang melihat kombinasi lain selain @? yang IMO terlihat cukup lugas dan

Anda tidak dapat mengetahuinya sampai itu muncul. Jika kita memiliki trafo kode lain - yield sigil, misalnya - itu akan menjadi skrip Perl yang sebenarnya.

Postfix @ sangat sulit untuk diperhatikan!

Dan itu sebenarnya keuntungan besar dalam konteks async-await .

Kesulitan dalam memperhatikan await poin tidak bisa menjadi keuntungan.

Menunggu masa depan adalah operasi yang mahal dan kami harus mengatakannya secara eksplisit

Ini benar-benar masuk akal, namun saya rasa kita tidak harus mengatakannya terlalu sering dan keras.

Baca kembali tautan saya tentang pengalaman C #. Ini sebenarnya ini penting untuk mengatakan itu ini keras dan ini sering.


Poin lain cukup valid, tetapi yang saya balas adalah celah yang tidak dapat diperbaiki.

@ CAD97 Sebagai pengembang C # yang dapat mengatakan bagaimana async berbeda di Rust .. Yah, itu tidak jauh berbeda seperti yang Anda gambarkan. IMHO implikasi Anda didasarkan pada premis yang salah

Jadi await!(foo) kita bukanlah await foo dalam cara "tradisional" C # atau JS.

Ini hal yang sama dalam pikiran C # dev. Tentu saja, Anda harus ingat bahwa masa depan tidak akan dieksekusi sampai pemungutan suara, tetapi dalam banyak kasus tidak masalah. Dalam kebanyakan kasus await hanya berarti "Saya ingin membuka bungkusan nilai dari masa depan F ke dalam variabel x ". Memiliki masa depan yang tidak berjalan sampai pemungutan suara sebenarnya melegakan setelah dunia C # sehingga orang-orang akan senang. Selain itu, C # memiliki konsep yang mirip dengan Iterator / generatos, yaitu malas juga, sehingga C # crowd cukup familiar dengan semuanya.

Singkatnya, menangkap await bekerja sedikit berbeda lebih mudah daripada menggunakan sigil yang bekerja seperti await dalam bahasa% younameit% mereka, tetapi tidak cukup. Orang tidak akan bingung dengan perbedaannya karena itu bukan kesepakatan yang Anda pikirkan. Situasi ketika masalah eksekusi yang bersemangat / malas benar -

@Pzixel Ini hal yang sama dalam pikiran C # dev. Tentu saja, Anda harus ingat bahwa masa depan tidak akan dieksekusi sampai pemungutan suara, tetapi dalam banyak kasus tidak masalah.

Situasi ketika masalah eksekusi bersemangat / malas _sangat jarang terjadi, sehingga seharusnya tidak menjadi perhatian desain utama.

Sayangnya itu tidak benar: pola "menelurkan beberapa Promises dan kemudian await them" sangat umum dan diinginkan , tetapi tidak berfungsi dengan Rust .

Itu perbedaan yang sangat besar, yang bahkan mengejutkan beberapa anggota Rust!

async / await di Rust benar-benar lebih dekat dengan sistem monadik, pada dasarnya tidak ada kesamaan dengan JavaScript / C #:

  • Implementasinya benar-benar berbeda (membangun mesin status dan kemudian mengeksekusinya pada Tugas, yang konsisten dengan monad).

  • API benar-benar berbeda (tarik vs dorong).

  • Perilaku default menjadi malas sangat berbeda (yang konsisten dengan monad).

  • Hanya boleh ada satu konsumen, bukan banyak (yang konsisten dengan monad).

  • Memisahkan kesalahan dan menggunakan ? untuk menanganinya sama sekali berbeda (yang konsisten dengan transformator monad).

  • Model memori sangat berbeda, yang memiliki dampak besar pada implementasi Rust Futures, dan juga bagaimana pengguna menggunakan Futures.

Satu-satunya kesamaan adalah bahwa semua sistem dirancang untuk membuat kode asinkron lebih mudah. Itu dia. Itu sama sekali tidak memiliki banyak kesamaan.

Banyak banyak orang telah disebutkan C #. Kita tahu.

Kita tahu apa yang dilakukan C #, kita tahu apa yang dilakukan JavaScript. Kami tahu mengapa bahasa-bahasa itu membuat keputusan itu.

Anggota tim resmi C # telah berbicara dengan kami dan menjelaskan secara detail mengapa mereka membuat keputusan tersebut dengan async / await.

Tidak ada yang mengabaikan bahasa-bahasa tersebut, atau poin data tersebut, atau kasus penggunaan tersebut. Mereka diperhitungkan.

Tapi Rust benar-benar berbeda dari C # atau JavaScript (atau bahasa lain). Jadi kita tidak bisa begitu saja meniru apa yang dilakukan bahasa lain.

Namun dalam salah satu bahasa ini, await foo secara semantik berarti "parkir tugas ini dan tunggu penyelesaian tugas foo

Ini persis sama di Rust. Semantik fungsi asinkron sama. Jika Anda memanggil async fn (yang menciptakan Future), itu belum memulai eksekusi. Itu memang berbeda dengan dunia JS dan C #.

Menunggu itu akan mendorong masa depan sampai selesai, yang sama seperti di tempat lain.

Sayangnya itu tidak benar: pola "menelurkan beberapa Promises dan kemudian await them" sangat umum dan diinginkan , tetapi tidak berfungsi dengan Rust .

Bisa lebih detailnya? Saya telah membaca posting tetapi tidak menemukan apa yang salah

let foos = (1..10).map(|x| some_future(x)); // create futures, unlike C#/JS don't actually run anything
let results = await foos.join(); // awaits them

Tentu saja, mereka tidak akan aktif hingga baris 2, tetapi dalam banyak kasus itu adalah perilaku yang diinginkan, dan saya masih yakin ini adalah perbedaan besar yang tidak memungkinkan untuk menggunakan await kata kunci lagi. Nama topik itu sendiri mengisyaratkan bahwa await kata masuk akal di sini.

Tidak ada yang mengabaikan bahasa-bahasa tersebut, atau poin data tersebut, atau kasus penggunaan tersebut. Mereka diperhitungkan.

Saya tidak akan mengganggu untuk mengulangi argumen yang sama berulang kali. Saya pikir saya telah menyampaikan apa yang seharusnya saya lakukan, jadi saya tidak akan melakukannya lagi.


PS

  • API benar-benar berbeda (tarik vs dorong).

Itu membuat perbedaan hanya ketika menerapkan masa depan Anda sendiri. Tetapi dalam 99 (100?)% Kasus Anda hanya menggunakan kombinator pf futures yang menyembunyikan perbedaan ini.

  • Model memori sangat berbeda, yang memiliki dampak besar pada implementasi Rust Futures, dan juga bagaimana pengguna menggunakan Futures.

Bisa lebih detailnya? Saya sebenarnya telah bermain dengan kode async saat menulis server web sederhana di Hyper dan saya tidak melihat perbedaan apa pun. Taruh async sini, await sana, selesai .

Argumen bahwa rust's async / await memiliki implementasi yang berbeda dari bahasa lain dan kemudian kita harus melakukan sesuatu yang berbeda cukup tidak meyakinkan. C's

for (int i = 0; i < n; i++)

dan Python

for i in range(n)

pasti tidak berbagi mekanisme dasar yang sama tetapi mengapa Python memilih untuk menggunakan for ? Ini harus menggunakan i in range(n) @ atau i in range(n) --->> atau apapun untuk menunjukkan perbedaan penting ini!

Pertanyaannya di sini adalah apakah perbedaan itu penting untuk pekerjaan harian pengguna!

Kehidupan sehari-hari pengguna karat pada umumnya adalah tentang:

  1. Berinteraksi dengan beberapa API HTTP
  2. Tulis beberapa kode jaringan

dan dia sangat peduli

  1. Dia bisa menyelesaikan pekerjaan dengan cepat dan ergonomis.
  2. Rust sangat baik untuk pekerjaannya.
  3. Dia memiliki kontrol level rendah jika dia mau

NOT rust memiliki satu juta detail implementasi yang berbeda dari C #, JS . Ini adalah tugas desainer bahasa untuk menyembunyikan perbedaan yang tidak relevan, hanya memperlihatkan perbedaan yang berguna kepada pengguna .

Selain itu, saat ini tidak ada yang mengeluh await!() untuk alasan seperti "Saya tidak tahu ini adalah mesin negara", "Saya tidak tahu itu berbasis tarik".

Pengguna tidak dan tidak akan peduli dengan perbedaan ini.

Menunggu itu akan mendorong masa depan sampai selesai, yang sama seperti di tempat lain.

Yah, kurang tepat, bukan? Jika saya hanya menulis let result = await!(future) dalam async fn dan memanggil fungsi itu, tidak ada yang akan terjadi sampai ia ditempatkan pada eksekutor dan disurvei olehnya.

@ ben0x539 ya, saya sudah membacanya lagi ... Tapi saat ini saya tidak punya apa-apa untuk ditambahkan.

@ CAD97 senang melihat kiriman saya menginspirasi. Saya dapat menjawab tentang Kotlin: secara default ia bekerja dengan cara yang sama seperti bahasa lain, meskipun Anda dapat mencapai perilaku malas seperti Rust jika Anda menginginkannya (misalnya val lazy = async(start=LAZY) { deferred_operation() }; ). Dan tentang semantik serupa: IMO semantik terdekat disediakan dalam pemrograman reaktif, karena observable reaktif bersifat dingin (malas) secara default (mis. val lazy = Observable.fromCallable { deferred_operation() }; ). Berlangganan ke mereka menjadwalkan pekerjaan aktual dengan cara yang sama seperti await!() di Rust, tetapi ada juga perbedaan besar bahwa berlangganan secara default adalah operasi non-pemblokiran dan hasil yang dihitung secara asinkron hampir selalu ditangani dalam penutupan secara terpisah dari aliran kontrol saat ini. Dan ada perbedaan besar dalam cara kerja pembatalan. Jadi, menurut saya perilaku Rust async itu unik, dan saya sepenuhnya mendukung argumen Anda bahwa await membingungkan dan sintaks yang berbeda hanya menguntungkan di sini!

@Pzixel Saya cukup yakin bahwa tidak akan muncul sigil baru. Menerapkan yield sebagai sigil tidak masuk akal karena konstruksi aliran kontrolnya seperti if / match / loop / return . Jelas semua konstruksi aliran kontrol harus menggunakan kata kunci karena menggambarkan logika bisnis dan logika bisnis selalu dalam prioritas. Sebaliknya, @ serta ? adalah pengecualian penanganan konstruksi dan logika penanganan pengecualian kurang penting, oleh karena itu harus halus. Saya belum menemukan argumen kuat bahwa memiliki kata kunci await adalah hal yang baik (mengingat jumlah teks, mungkin saja saya merindukannya) karena semuanya menarik bagi otoritas atau pengalaman (namun bukan berarti saya abaikan otoritas atau pengalaman seseorang - itu tidak dapat diterapkan di sini secara penuh).

@tajimaha contoh Python yang Anda berikan sebenarnya memiliki lebih banyak "detail implementasi". Kita dapat menganggap for sebagai async , (int i = 0; i < n; i++) sebagai await , dan i in range(n) sebagai @ dan di sini jelas bahwa Python memperkenalkan kata kunci baru - in (di Java sebenarnya sigil - : ) dan juga memperkenalkan sintaks rentang baru. Itu bisa menggunakan kembali sesuatu yang lebih familiar seperti (i=0, n=0; i<n; i++) daripada memperkenalkan banyak detail implementasi. Namun saat ini, dampak pada pengalaman pengguna hanya positif, sintaksisnya lebih sederhana, dan pengguna benar-benar memedulikannya.

@Pzixel Saya cukup yakin bahwa tidak akan muncul sigil baru. Menerapkan yield sebagai sigil tidak masuk akal karena konstruksi aliran kontrolnya seperti if / match / loop / return . Jelas semua konstruksi aliran kontrol harus menggunakan kata kunci karena menggambarkan logika bisnis dan logika bisnis selalu dalam prioritas. Sebaliknya, @ serta ? adalah pengecualian yang menangani konstruksi dan logika penanganan pengecualian kurang penting, oleh karena itu harus halus.

await bukan merupakan "konstruksi penanganan pengecualian".
Berdasarkan premis yang tidak valid, implikasi Anda juga salah.

penanganan pengecualian adalah aliran kontrol juga, tetapi tidak ada yang berpikir ? adalah hal yang buruk.

Saya belum menemukan argumen kuat bahwa memiliki kata kunci await adalah hal yang baik (mengingat jumlah teks, mungkin saja saya merindukannya) karena semuanya menarik bagi otoritas atau pengalaman (namun bukan berarti saya abaikan otoritas atau pengalaman seseorang - itu tidak bisa diterapkan _di sini_ secara penuh).

Karena Rust tidak memiliki pengalamannya sendiri, jadi hanya bisa melihat apa yang dipikirkan bahasa lain tentangnya, pengalaman tim lain, dan sebagainya. Anda sangat percaya diri dengan sigil, tetapi saya tidak yakin apakah Anda benar-benar mencoba menggunakannya.

Saya tidak ingin membuat argumen tentang sintaks di Rust, tetapi saya telah melihat banyak argumen postfix vs prefix dan mungkin kita bisa mendapatkan yang terbaik dari kedua dunia. Dan orang lain sudah mencoba mengusulkan ini di C ++. Proposal C ++ dan Coroutine TS telah disebutkan beberapa kali di sini, tetapi menurut saya proposal alternatif bernama Core Coroutines perlu mendapat perhatian lebih.

Penulis Core Coroutines mengusulkan untuk mengganti co_await dengan token seperti operator baru.
Dengan, apa yang mereka sebut sintaks operator yang tidak terbungkus , seseorang dapat menggunakan awalan dan notasi postfix (tanpa ambiguitas):

future​<string>​ g​();
string​ s ​=​​ [<-]​ f​();

optional_struct​[->].​optional_sub_struct​[->].​field

Saya pikir ini mungkin menarik, atau setidaknya akan membuat pembahasan lebih lengkap.

Tapi Rust benar-benar berbeda dari C # atau JavaScript (atau bahasa lain). Jadi kita tidak bisa begitu saja meniru apa yang dilakukan bahasa lain.

Harap jangan gunakan "perbedaan" sebagai alasan untuk kecenderungan pribadi Anda terhadap sintaks yang tidak ortodoks.

Ada berapa perbedaan? Ambil bahasa populer apa pun, Java, C, Python, JavaScript, C #, PHP, Ruby, Go, Swift, dll., Statis, dinamis, terkompilasi, ditafsirkan. Mereka sangat berbeda dalam set fitur, tetapi mereka masih memiliki banyak kesamaan untuk sintaksnya. Apakah ada saat Anda merasa salah satu bahasa ini seperti 'brainf * ck'?

Saya pikir kita harus fokus pada memberikan fitur yang berbeda tetapi bermanfaat, bukan sintaks yang aneh.

Setelah membaca utas, saya juga berpikir debat sebagian besar berakhir ketika seseorang membatalkan kebutuhan mendesak untuk merangkai, seseorang membatalkan klaim bahwa orang-orang terganggu oleh terlalu banyak variabel sementara.

IMO, Anda telah kehilangan perdebatan tentang kebutuhan sintaks postfix. Anda hanya dapat menggunakan "perbedaan". Ini adalah percobaan putus asa lainnya.

@tensorduruk Saya merasa bahwa kata-kata Anda terlalu memusuhi pengguna lain. Silakan coba untuk memeriksanya.


Dan sejujurnya, jika ada banyak orang yang menentang sintaks postfix, maka kita harus memutuskan sintaks prefiks untuk saat ini, menunggu untuk melihat bagaimana kode dengan sintaks prefiks ditulis, dan kemudian melakukan analisis untuk melihat berapa banyak kode yang ditulis bisa mendapatkan keuntungan dengan menunggu postfix. Dengan cara itu kami menenangkan setiap orang yang tidak nyaman dengan perubahan inovatif seperti postfix menunggu, sementara juga mendapatkan argumen pragmatis yang baik tentang apakah postfix menunggu atau tidak.

Skenario kasus terburuk dengan ini adalah jika kita melakukan semua itu dan menghasilkan sintaks menunggu postfix yang entah bagaimana benar-benar lebih baik daripada prefiks await. Itu akan menyebabkan banyak kode churn jika orang mau repot-repot beralih ke bentuk menunggu yang lebih baik.

Dan saya pikir semua pembahasan sintaksis ini benar-benar bermuara pada rangkaian. Jika rantai bukanlah suatu hal, maka postfix menunggu akan benar-benar keluar jendela dan akan jauh lebih mudah untuk menyelesaikan hanya dengan menunggu awalan. Namun, merangkai sangat penting di Rust, dan karenanya membuka diskusi ke topik berikut:

if we should have only postfix await:
    what's the best syntax for it that:
         benefits chaining?
         is also ok in non-chaining scenarios
         is readable in both chainable and non-chainable contexts?
else if we should have only prefix await:
    what's the best syntax for it that:
         isn't ambiguous in the sense of order of operation (useful vs obvious)
else if we should have both prefix and postfix await:
    what's the best syntax for it that:
         benefits chaining?
         is also ok in non-chaining scenarios
         is readable in both chainable and non-chainable contexts?
         isn't ambiguous in the sense of order of operation (useful vs obvious)
    should it be a single unified syntax that somehow works for both prefix and postfix?
    would there be clear situations where prefix syntax is favored over postfix?
    would there be a situation where postfix syntax isn't allowed, but prefix is, and vice-versa?

Atau semacam itu. Jika seseorang dapat menemukan pola keputusan yang lebih baik dengan poin yang lebih baik daripada saya, lakukanlah! XD

Jadi pertama-tama, daripada membahas sintaks, kita harus memutuskan apakah kita menginginkan postfix, prefix, atau keduanya postfix dan prefix, dan mengapa kita menginginkan pilihan yang kita miliki. Setelah kami menyelesaikannya, maka kami dapat melanjutkan ke masalah yang lebih kecil tentang pilihan sintaks.

@tensuruk

IMO, Anda telah kehilangan perdebatan tentang kebutuhan sintaks postfix. Anda hanya dapat menggunakan "perbedaan". Ini adalah percobaan putus asa lainnya.

Serius, mengapa tidak menggunakan bahasa yang Anda ucapkan saja alih-alih permusuhan yang tidak beralasan di sini?
Menggunakan logika Anda, rust harus memiliki kelas karena bahasa konvensional lain memilikinya. Rust seharusnya tidak meminjam karena bahasa lain tidak memilikinya.

Tidak menangani rantai, akan menjadi kesempatan yang terlewatkan. i, e Saya tidak ingin membuat binding untuk variabel sementara, jika rangkaian dapat membuatnya anonim.

Namun, saya merasa kita harus tetap menggunakan kata kunci makro saat ini untuk menunggu seperti di malam hari. Miliki makro postfix sebagai fitur malam dan biarkan orang mempermainkannya. Ya, akan ada churn setelah kami menyelesaikannya, tetapi itu dapat ditangani dengan perbaikan karat.

@tensuruk

IMO, Anda telah kehilangan perdebatan tentang kebutuhan sintaks postfix. Anda hanya dapat menggunakan "perbedaan". Ini adalah percobaan putus asa lainnya.

Serius, mengapa tidak menggunakan bahasa yang Anda ucapkan saja alih-alih permusuhan yang tidak beralasan di sini?
Menggunakan logika Anda, rust harus memiliki kelas karena bahasa konvensional lain memilikinya. Rust seharusnya tidak meminjam karena bahasa lain tidak memilikinya.

Tidak menangani rantai, akan menjadi kesempatan yang terlewatkan. i, e Saya tidak ingin membuat binding untuk variabel sementara, jika rangkaian dapat membuatnya anonim.

Namun, saya merasa kita harus tetap menggunakan kata kunci makro saat ini untuk menunggu seperti di malam hari. Miliki makro postfix sebagai fitur malam dan biarkan orang mempermainkannya. Ya, akan ada churn setelah kami menyelesaikannya, tetapi itu dapat ditangani dengan perbaikan karat.

harap baca dengan seksama. saya bilang kita harus menyediakan fitur yang berguna bukan sintaks yang aneh. struct? pinjam? fitur!

tolong pecahkan juga masalah yang benar-benar ada. orang menunjukkan, dengan contoh atau statistik bahwa pengikatan sementara mungkin tidak menjadi masalah. apakah kalian mendukung f await f.await pernah mencoba meyakinkan pihak lain menggunakan bukti?

juga meremehkan saya tidak berguna. agar postfix diterima, Anda perlu berdebat di mana postfix berguna (mungkin jangan mengulang argumen berantai, itu jalan buntu; mungkin masalah yang sering mengganggu orang, dengan beberapa bukti, bukan masalah mainan).

bisakah kita mendapatkan sintaks seperti C # atau JS? sebagian besar developer menggunakannya di dunia, saya tidak suka Rust menggunakan sintaks baru atau tidak konsisten, Rust juga sulit dipelajari oleh orang baru.

Berikut ini akan menjadi lampiran pada posting saya tentang penggunaan postfix @ insead dari await


Sebagai gantinya, kita harus menggunakan pengalaman dari banyak bahasa lain

Dan kami benar-benar menggunakannya. Tapi itu tidak berarti kita harus mengulangi pengalaman yang sama. Juga, ketika berdebat dengan cara ini melawan @ Anda kemungkinan besar tidak akan mencapai hasil yang berguna karena menarik pengalaman sebelumnya tidak meyakinkan :

Catatan genetik dari suatu masalah mungkin benar, dan mereka dapat membantu menjelaskan alasan mengapa masalah tersebut mengambil bentuknya yang sekarang, tetapi mereka tidak meyakinkan dalam menentukan manfaatnya.

Kita harus menggunakan await karena banyak orang menyukainya

Orang-orang itu mungkin menyukai await karena banyak alasan lain, bukan hanya karena await . Alasan ini mungkin juga tidak ada di Rust. Dan berdebat dengan cara ini melawan @ kemungkinan besar tidak akan membawa poin baru ke dalam diskusi karena menarik ke publik tidak meyakinkan :

Argumen ad populum dapat menjadi argumen yang valid dalam logika induktif; misalnya, jajak pendapat dari populasi yang cukup besar mungkin menemukan bahwa 100% lebih menyukai merek produk tertentu daripada yang lain. Argumen yang meyakinkan (kuat) kemudian dapat dibuat bahwa orang berikutnya yang akan dipertimbangkan juga kemungkinan besar akan menyukai merek tersebut (tetapi tidak selalu 100% karena mungkin ada pengecualian), dan jajak pendapat adalah bukti valid dari klaim tersebut. Namun, hal ini tidak cocok sebagai argumen untuk penalaran deduktif sebagai bukti, misalnya untuk mengatakan bahwa jajak pendapat membuktikan bahwa merek yang disukai lebih unggul dari persaingan dalam komposisinya atau bahwa setiap orang lebih memilih merek itu daripada yang lain.

@Tokopedia

Tapi karat adalah hal lain. Kami memiliki ? sini dan kami harus menyebarkan kesalahan. Di C # async / await secara otomatis membungkus pengecualian, melemparkannya ke titik tunggu dan seterusnya. Anda tidak memilikinya di karat. Pertimbangkan setiap kali Anda menulis await di C # Anda harus menulis

var response = (await client.GetAsync("www.google.com")).HandleException();
var json =  (await response.ReadAsStreamAsync()).HandleException();
var somethingElse = (await DoMoreAsyncStuff(json)).HandleException();
...

Sangat membosankan memiliki semua kawat gigi ini.

Di C # Anda menulis ini:

try
{
  var response = await client.GetAsync("www.google.com");
  var json =  await response.ReadAsStreamAsync();
  var somethingElse = await DoMoreAsyncStuff(json);
}
catch (Exception ex)
{
  // handle exception
}

Mengenai argumen propagate-error-chaining, misalnya foo().await? , adakah alasan mengapa ? tidak dapat ditambahkan ke operator await di awalan?

let response = await? getProfile();

Hal lain yang baru saja terpikir oleh saya: bagaimana jika Anda ingin match dengan Future<Result<...>> ? Manakah dari berikut ini yang lebih mudah dibaca?

// Prefix
let userId = match await response {
  Ok(u) => u.id,
  _ => -1
};
// Postfix
let userId = match response {
  Ok(u) => u.id,
  _ => -1
} await;

Selain itu, apakah ekspresi async match menjadi sesuatu? Seperti, apakah Anda ingin badan ekspresi kecocokan menjadi asinkron? Jika demikian, akan ada perbedaan antara match await response dan await match response . Karena match dan await keduanya merupakan operator unary yang efektif, dan match sudah menjadi awalan, akan lebih mudah untuk membedakan jika await juga merupakan awalan. Dengan satu prefiks dan satu postfix, menjadi sulit untuk menentukan apakah Anda sedang menunggu pertandingan atau tanggapannya.

let userId = match response {
  Ok(u) => somethingAsync(u),
  _ => -1
} await; // Are we awaiting match or response here?

Jika Anda harus menunggu kedua hal tersebut, Anda akan melihat sesuatu seperti

// Prefix - yes, double await is weird and ugly but...
let userId = await match await response {
  Ok(u) => somethingAsync(u),
  _ => -1
} await;
// Postfix - ... this is weirder and uglier
let userId = match response {
  Ok(u) => somethingAsync(u),
  _ => -1
} await await;

Meskipun saya rasa itu mungkin

// Postfix - ... this is weirder and uglier
let userId = match response await {
  Ok(u) => somethingAsync(u),
  _ => -1
} await;

(Desain bahasa pemrograman sulit.)

Terlepas dari itu, saya akan menegaskan kembali bahwa Rust memiliki prioritas untuk operator unary yang menjadi awalan dengan match , dan await adalah operator unary.

C# // Postfix - ... this is weirder and uglier let userId = match response await { ... } await;

Kecantikan ada di mata yang melihatnya.

Terlepas dari itu, saya akan menegaskan kembali bahwa Rust memiliki prioritas untuk operator unary yang menjadi awalan dengan match , dan await adalah operator unary.

? di sisi lain adalah unary tetapi postfix.

Terlepas dari itu, saya merasa bahwa diskusi sekarang berputar-putar. Tanpa mengemukakan poin diskusi baru, tidak ada gunanya mengulang posisi yang sama berulang kali.

FWIW, saya senang mendapatkan await dukungan apa pun sintaksnya - meskipun saya memiliki preferensi sendiri, saya tidak merasa ada saran realistis yang terlalu buruk untuk digunakan.

@markrendle Saya tidak yakin Anda menjawab apa

Di C # Anda menulis ini:

Saya tahu bagaimana saya menulis di C #. Saya berkata "bayangkan bagaimana bisa terlihat kita tidak memiliki pengecualian". Karena Rust tidak.

Mengenai argumen propagate-error-chaining, misalnya foo().await? , adakah alasan mengapa ? tidak dapat ditambahkan ke operator await di awalan?

Itu sudah dibahas dua kali atau tiga kali, tolong baca topiknya. Singkatnya: ini adalah konstruksi buatan, yang tidak akan bekerja dengan baik jika kita memiliki tambahan ? . await? sebagai sufiks berfungsi, ketika await? sebagai prefiks membutuhkan dukungan tambahan dalam kompilator. Dan masih membutuhkan kawat gigi untuk merangkai (yang secara pribadi saya tidak suka di sini, tapi orang selalu menyebutnya sebagai hal yang penting), ketika postfix menunggu tidak.

Hal lain yang baru saja terpikir oleh saya: bagaimana jika Anda ingin match dengan Future<Result<...>> ? Manakah dari berikut ini yang lebih mudah dibaca?

// Real postfix
let userId = match response await {
  Ok(u) => u.id,
  _ => -1
};
// Real Postfix 2 - looks fine, except it's better to be
let userId = match response await {
  Ok(u) => somethingAsync(u),
  _ => ok(-1)
} await;
// Real Postfix 2
let userId = match response await {
  Ok(u) => somethingAsync(u) await,
  _ => -1
};

Sebagai pengguna C # yang lain, saya akan mengatakan bahwa sintaks prefiksnya: new , await dan pemeran gaya-C telah membuat intuisi saya tersandung. Saya sangat mendukung opsi operator postfix.

Namun, sintaks apa pun akan lebih baik daripada merangkai futures secara eksplisit, bahkan pseudo-makro. Saya akan menyambut resolusi apapun.

@ Ortodoks Anda mengangkat poin yang sangat bagus. Dalam pekerjaan harian saya, saya kebanyakan menulis Java dan saya membenci operator baru ke tingkat yang semua kelas saya yang membutuhkan instantiasi eksplisit (kejadian yang sangat jarang terjadi ketika Anda menggunakan pola pembangun dan injeksi ketergantungan) memiliki metode pabrik statis hanya sehingga saya dapat menyembunyikan operator .

@Tokopedia

@markrendle Saya tidak yakin Anda menjawab apa

Di C # Anda menulis ini:

Saya tahu bagaimana saya menulis di C #. Saya berkata "bayangkan bagaimana bisa terlihat kita tidak memiliki pengecualian". Karena Rust tidak.

Saya menduga ini mungkin hal kendala bahasa karena sama sekali bukan itu yang Anda katakan, tetapi saya akan menerima mungkin itu yang Anda maksud.

Bagaimanapun, seperti yang dikatakan @rolandsteiner , yang penting adalah kami mendapatkan beberapa bentuk async / menunggu, jadi saya senang menunggu keputusan tim inti, dan semua penggemar postfix dapat menunggu keputusan tim inti. 😛 ❤️ ☮️

@yasammez Datanglah ke C #. Di v8.0 kita bisa menggunakan new () tanpa nama tipe :)

Saya hanya akan memberikan beberapa ide untuk operator postfix.

foo()~; // the pause operator
foo()^^; // the road bumps operator
foo()>>>; // the fast forward operator

Tidak mengatakan apakah operator postfix adalah cara untuk pergi atau tidak, tetapi secara pribadi saya menemukan @ salah satu yang paling berisik dan terlihat aneh dari semua kemungkinan pilihan. ~ dari komentar @phaux tampaknya jauh lebih elegan dan tidak terlalu "sibuk". Juga, jika saya tidak melewatkan apa pun, kami belum menggunakannya untuk apa pun di Rust.

Saya diusulkan ~ sebelum @phaux meskipun saya tidak ingin mengklaim paten; P.

Saya mengusulkan ini karena ini seperti gema yang berbicara:

Hi~~~~~
Where r u~~~~~

Hay~~~~~
I am in another mountain top~~~~~

~ kadang-kadang digunakan setelah kalimat untuk menunjukkan trailing off, yang cocok untuk menunggu!

Saya tidak tahu apakah utas ini telah mencapai puncak kekonyolan atau apakah kita melakukan sesuatu di sini.

Saya pikir ~ tidak mudah untuk menjawab pada beberapa keyboard, terutama beberapa keyboard mekanis yang kecil dan rumit.

Bisa jadi:

let await userId = match response {
  Ok(u) => u.id,
  _ => -1
};
let await userId = match response {
  await Ok(u) => somethingAsync(u),
  _ => ok(-1)
};

Kami dapat memperkenalkan trigraf seperti ... untuk pengguna pada tata letak keyboard di mana ~ tidak nyaman.

Pada awalnya, saya sangat ingin memiliki sintaks awalan pembatas yang diperlukan sebagai await(future) atau await{future} karena sangat tidak ambigu dan mudah untuk diurai secara visual. Namun, saya memahami usulan orang lain bahwa Rust Future tidak seperti kebanyakan Futures lain dari bahasa lain, karena tidak langsung menempatkan tugas pada pelaksana, tetapi lebih merupakan struktur aliran kontrol yang mengubah konteks menjadi apa yang ada. homomorfik ke rantai panggilan monad pada dasarnya.

Itu membuat saya berpikir bahwa agak disayangkan bahwa sekarang ada kebingungan dalam mencoba membandingkannya dengan bahasa lain terkait dengan itu. Analog terdekat sebenarnya adalah notasi monad do di Haskell atau pemahaman for di Scala (yang merupakan satu-satunya yang saya kenal di luar kepala saya). Tiba-tiba, saya menghargai pertimbangan untuk mengusulkan sintaks yang unik, tetapi saya khawatir keberadaan operator ? telah mendorong dan mengecilkan penggunaan sigils lain dengannya. Operator berbasis sigil lainnya di sebelah ? membuatnya terlihat berisik dan membingungkan, seperti future@? , tetapi preseden yang ditetapkan dengan memiliki operator sigil postfix berarti bahwa operator lain tidak terlalu konyol.

Oleh karena itu, saya yakin akan manfaat dari operator sigil postfix. Kelemahan dari ini, adalah bahwa sigil yang paling saya sukai diambil oleh tipe tidak pernah. Saya lebih suka ! karena saya pikir future!? akan membuat saya terkekeh setiap kali saya menulisnya, dan itu paling masuk akal bagi saya untuk melihatnya. Saya kira $ akan menjadi yang berikutnya, kemudian karena secara visual dapat dilihat future$? . Melihat ~ masih mengingatkan saya pada masa-masa awal Rust ketika ~ adalah operator awalan untuk alokasi heap. Itu semua sangat pribadi, jadi saya tidak iri pada pembuat keputusan akhir. Jika saya adalah mereka, saya mungkin hanya akan memilih operator awalan yang tidak ofensif dengan pembatas yang diperlukan.

Namun, saya memahami usulan orang lain bahwa Rust Future tidak seperti kebanyakan Futures lain dari bahasa lain, karena tidak langsung menempatkan tugas pada pelaksana, tetapi lebih merupakan struktur aliran kontrol yang mengubah konteks menjadi apa yang ada. homomorfik ke rantai panggilan monad pada dasarnya.

Saya cenderung tidak setuju. Perilaku yang Anda sebutkan bukanlah properti await , tetapi fungsi atau cakupan sekitar async . Bukan await yang menunda eksekusi kode sebelumnya, tetapi cakupan yang berisi kode tersebut.

Mungkin masalah dengan anehnya mencari simbol @ adalah bahwa menggunakannya dalam konteks itu tidak pernah diharapkan sebelumnya, oleh karena itu di sebagian besar font itu disediakan dengan sangat tidak nyaman bagi kita.

Kemudian memberikan mesin terbang yang lebih baik dan beberapa ligatur untuk font pemrograman populer (atau setidaknya untuk Fira Code Mozilla) mungkin akan sedikit memperbaiki situasi.

Dalam semua kasus lain, bagi saya tampaknya @ tidak begitu aneh untuk menyebabkan masalah nyata saat menulis atau memelihara kode.


Misal, kode berikut menggunakan simbol @ - :


// A
if db.is_trusted_identity(recipient.clone(), message.key.clone())@? {
    info!("recipient: {}", recipient);
}

// B
match db.load(message.key)@? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = client.get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .send()@?
    .error_for_status()?;

// D
let mut res: InboxResponse = client.get(inbox_url)
    .headers(inbox_headers)
    .send()@?
    .error_for_status()?
    .json()@?;

// E
let mut res: Response = client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .send()@?
    .error_for_status()?
    .json()@?;

// F
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.request(url, Method::GET, None, true)@?
        .res.json::<UserResponse>()@?
        .user
        .into();

    Ok(user)
}

Perluas untuk membandingkan tampilannya dengan ANSI @


// A
if db.is_trusted_identity(recipient.clone(), message.key.clone())@? {
    info!("recipient: {}", recipient);
}

// B
match db.load(message.key)@? {
    Some(key) => key,
    None => {
        return Err(/* [...] */);
    }
};

// C
let mut res = client.get(&script_src)
    .header("cookie", self.cookies.read().as_header_value())
    .header("user-agent", USER_AGENT)
    .send()@?
    .error_for_status()?;

// D
let mut res: InboxResponse = client.get(inbox_url)
    .headers(inbox_headers)
    .send()@?
    .error_for_status()?
    .json()@?;

// E
let mut res: Response = client.post(url)
    .multipart(form)
    .headers(headers.clone())
    .send()@?
    .error_for_status()?
    .json()@?;

// F
async fn request_user(self, user_id: String) -> Result<User> {
    let url = format!("users/{}/profile", user_id);
    let user = self.request(url, Method::GET, None, true)@?
        .res.json::<UserResponse>()@?
        .user
        .into();

    Ok(user)
}

@tokopedia

Pada awalnya, saya sangat ingin memiliki sintaks awalan pembatas yang diperlukan sebagai await(future) atau await{future} karena sangat tidak ambigu dan mudah untuk diurai secara visual.

Maka Anda mungkin ingin jelas if { cond } , while { cond } dan match { expr } ...

Namun, saya memahami usulan orang lain bahwa Rust Future tidak seperti kebanyakan Futures lain dari bahasa lain

Itu tidak benar. Ini sebenarnya adalah seperti kebanyakan berjangka lainnya dari bahasa lain. Perbedaan antara "lari sampai menunggu pertama saat muncul" vs "lari sampai menunggu pertama saat disurvei" tidak terlalu besar. Saya tahu karena saya bekerja dengan keduanya. Jika Anda benar-benar berpikir "ketika perbedaan ini datang untuk bermain", Anda hanya akan menemukan casing sudut. misalnya Anda akan mendapatkan kesalahan saat membuat masa depan pada jajak pendapat pertama, bukan saat dibuat.

Mereka bisa saja berbeda secara internal, tetapi mereka cukup sama dari perspektif pengguna sehingga tidak masuk akal bagi saya untuk membuat perbedaan di sini.

@Tokopedia

Perbedaan antara "lari sampai menunggu pertama saat muncul" vs "lari sampai menunggu pertama saat disurvei" tidak terlalu besar.

Namun, bukan itu perbedaan yang sedang kita bicarakan. Kami tidak berbicara tentang malas vs bersemangat untuk menunggu lebih dulu.

Apa yang kita bicarakan adalah bahwa await bergabung dengan Promise (JS) / Task (C #) yang ditunggu-tunggu pada eksekutor dalam bahasa lain, yang sudah diletakkan pada eksekutor pada konstruksi (jadi sudah berjalan di "latar belakang"), tetapi di Rust, Future s adalah mesin keadaan inert sampai await! -drive.

Promise / Task adalah pegangan untuk menjalankan operasi asinkron. Future adalah komputasi asinkron yang ditangguhkan. Orang-orang, termasuk nama-nama terkenal di Rust, telah membuat kesalahan ini sebelumnya, dan contoh-contoh telah ditautkan sebelumnya di tengah-tengah 500+ komentar ini.

Secara pribadi, saya pikir ketidakcocokan semantik ini cukup besar untuk menetralkan keakraban await . Future s kita mencapai tujuan yang sama seperti Promise / Task , tetapi melalui mekanisme yang berbeda.


Secara anekdot, bagi saya ketika saya pertama kali mempelajari async / await dalam JavaScript, async adalah "hanya" sesuatu yang saya tulis untuk mendapatkan kekuatan super await . Dan cara saya diajarkan untuk mendapatkan paralelisme adalah a = fa(); b = fb(); /* later */ await [a, b]; (atau apa pun itu, ini adalah usia sejak saya harus menulis JS). Pendapat saya adalah bahwa pandangan orang lain tentang async sejalan dengan saya, dalam semantik Rust tidak mismatch pada async (memberi Anda await kekuatan super), tetapi pada Future konstruksi dan await! .


Pada titik ini, saya yakin diskusi tentang perbedaan semantik Rust async / Future / await telah berjalan dengan sendirinya, dan tidak ada informasi baru yang disajikan. Kecuali Anda memiliki posisi dan / atau wawasan baru untuk dibawa, itu mungkin yang terbaik untuk utas jika kita meninggalkan diskusi itu di sini. (Saya akan dengan senang hati membawanya ke Internal dan / atau Discord.)

@ CAD97 ya, saya melihat posisi Anda, tetapi menurut saya perbedaannya tidak terlalu besar.

Anda punya saya, saya punya Anda. Jadi biarkan diskusi mengalir.

@ CAD97

Orang-orang, termasuk nama-nama terkenal di Rust, telah membuat kesalahan ini sebelumnya, dan contoh-contoh telah ditautkan sebelumnya di tengah-tengah 500+ komentar ini.

Jika bahkan orang yang sangat akrab dengan Rust membuat kesalahan itu, apakah itu benar-benar kesalahan?

Jadi, kami mengadakan sejumlah diskusi tentang async-menunggu di Rust All Hands. Selama diskusi tersebut, beberapa hal menjadi jelas:

Pertama, belum ada konsensus dalam tim lang tentang sintaks await . Jelas ada banyak kemungkinan dan argumen kuat yang mendukung semuanya. Kami menghabiskan waktu lama untuk mengeksplorasi alternatif dan menghasilkan cukup banyak bolak-balik yang menarik. Langkah langsung berikutnya untuk diskusi ini, menurut saya, adalah mengubah catatan tersebut (bersama dengan komentar lain dari utas ini) menjadi semacam komentar ringkasan yang menjelaskan kasus untuk setiap varian, dan kemudian melanjutkan dari sana. Saya bekerja dengan @withoutboats dan @cramertj untuk itu.

Melangkah mundur dari pertanyaan sintaks, hal lain yang kami rencanakan untuk dilakukan adalah triase keseluruhan dari status implementasi. Ada sejumlah batasan saat ini (misalnya, implementasinya memerlukan TLS di bawah kap saat ini untuk memasukkan informasi tentang waker). Ini mungkin atau mungkin bukan pemblokir untuk stabilisasi , tetapi terlepas dari itu adalah masalah yang pada akhirnya perlu ditangani, dan itu akan membutuhkan beberapa upaya bersama (sebagian dari tim penyusun). Langkah selanjutnya berikutnya adalah melakukan triase ini dan menghasilkan laporan. Saya berharap kami melakukan triase ini minggu depan, dan kami akan mendapatkan pembaruannya nanti.

Sementara itu, saya akan melanjutkan dan mengunci utas ini hingga kami memiliki kesempatan untuk membuat laporan di atas . Saya merasa utas telah memenuhi tujuannya untuk menjelajahi ruang desain yang mungkin secara mendalam dan komentar lebih lanjut tidak akan terlalu membantu karena tahap ini. Setelah kami memiliki laporan yang disebutkan di atas, kami juga akan menjelaskan langkah selanjutnya untuk mencapai keputusan akhir.

(Untuk memperluas sedikit pada paragraf terakhir, saya cukup tertarik untuk mengeksplorasi cara-cara alternatif untuk menjelajahi ruang desain di luar utas diskusi yang panjang. Ini adalah topik yang jauh lebih besar daripada yang dapat saya bahas dalam komentar ini, jadi saya tidak akan membahas detailnya , tetapi cukup untuk mengatakan untuk saat ini bahwa saya cukup tertarik untuk mencoba menemukan cara yang lebih baik untuk menyelesaikan ini - dan masa depan! - debat sintaks.)

Laporan status Async-await:

http://smallcultfollowing.com/babysteps/blog/2019/03/01/async-await-status-report/

Sehubungan dengan komentar saya sebelumnya, ini berisi hasil triase dan beberapa pemikiran tentang sintaks (tetapi belum menulis sintaks lengkap).

Menandai masalah ini sebagai pemblokiran untuk stabilisasi async-await, setidaknya untuk saat ini.

Beberapa waktu yang lalu, Niko berjanji bahwa kami akan menulis ringkasan diskusi di tim bahasa dan komunitas tentang sintaks akhir untuk operator await. Kami mohon maaf atas penantian yang lama. Tulisan status diskusi ditautkan di bawah ini. Sebelumnya, izinkan saya juga memberikan pembaruan tentang di mana posisi pembahasan sekarang dan ke mana kita akan pergi dari sini.

Ringkasan singkat tentang posisi async-await saat ini

Pertama, kami berharap untuk menstabilkan async-await di rilis 1.37 , yang bercabang pada 4 Juli 2019. Karena kami tidak ingin menstabilkan makro await! , kami harus menyelesaikan pertanyaan sintaks sebelum itu. Perhatikan bahwa stabilisasi ini tidak mewakili akhir jalan - lebih pada permulaan. Masih ada pekerjaan fitur yang harus diselesaikan (misalnya, async fn dalam sifat-sifat) dan juga pekerjaan impl (pengoptimalan lanjutan, perbaikan bug, dan sejenisnya). Namun, menstabilkan async / menunggu akan menjadi pencapaian penting!

Sejauh sintaksnya, rencana resolusi adalah sebagai berikut:

  • Untuk memulai, sejauh ini kami menerbitkan
  • Kami ingin agar kompatibel dengan ekstensi masa depan yang jelas untuk sintaks: memproses aliran dengan loop for khususnya (seperti loop JavaScript for await ). Itulah mengapa saya telah mengerjakan serangkaian posting tentang masalah ini ( posting pertama di sini dan lebih banyak lagi di masa mendatang).
  • Pada pertemuan tim-lang mendatang pada tanggal 2 Mei, kami berencana untuk membahas interaksi dengan for loop dan juga untuk menetapkan rencana untuk mencapai keputusan akhir tentang sintaks pada waktunya untuk menstabilkan async / menunggu di 1,37. Kami akan memposting pembaruan setelah pertemuan ke utas internal ini .

Tulisan tersebut

Tulisannya adalah dokumen kertas dropbox, tersedia di sini . Seperti yang akan Anda lihat, ini cukup panjang dan menjabarkan banyak argumen bolak-balik. Kami sangat menghargai umpan balik tentang itu; daripada membuka kembali masalah ini (yang sudah memiliki lebih dari 500 komentar) saya telah membuat utas internal untuk tujuan itu .

Seperti yang saya katakan sebelumnya, kami berencana untuk mencapai keputusan akhir dalam waktu dekat. Kami juga merasa sebagian besar diskusi telah mencapai keadaan stabil: perkirakan beberapa minggu ke depan akan menjadi "periode komentar terakhir" untuk diskusi sintaks ini. Setelah pertemuan, kami berharap kami memiliki jadwal yang lebih rinci untuk dibagikan tentang bagaimana keputusan ini akan dibuat.

Sintaks Async / await mungkin adalah fitur yang paling ditunggu-tunggu yang diperoleh Rust sejak 1.0, dan sintaks untuk await khususnya telah menjadi salah satu keputusan yang paling banyak menerima masukan. Terima kasih kepada semua orang yang telah berpartisipasi dalam diskusi ini selama beberapa bulan terakhir! Ini adalah pilihan yang membuat banyak orang memiliki perasaan yang sangat berbeda; kami ingin meyakinkan semua orang bahwa tanggapan Anda didengarkan dan keputusan akhir akan dicapai setelah pertimbangan yang matang dan cermat.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat