Rust: حل بناء جملة "انتظار"

تم إنشاؤها على ١٥ يناير ٢٠١٩  ·  512تعليقات  ·  مصدر: rust-lang/rust

قبل التعليق في هذا الموضوع ، يرجى مراجعة https://github.com/rust-lang/rust/issues/50547 ومحاولة التحقق من أنك لا تكرر الحجج التي تم تقديمها بالفعل هناك.


ملاحظات من الرعاة:

إذا كنت جديدًا على هذا الموضوع ، ففكر في البدء من https://github.com/rust-lang/rust/issues/57640#issuecomment -456617889 ، والتي أعقبتها ثلاثة تعليقات موجزة رائعة ، كان آخرها https: //github.com/rust-lang/rust/issues/57640#issuecomment -457101180. (شكرا ، ترافيسكروس!)

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

التعليق الأكثر فائدة

اعتقدت أنه قد يكون من المفيد كتابة كيفية تعامل اللغات الأخرى مع بنية انتظار.


كوتلن

val result = task.await()

ج

var result = await task;

F

let! result = task()

سكالا

val result = Await.result(task, timeout)

بايثون

result = await task

جافا سكريبت

let result = await task;

C ++ (Coroutines TR)

auto result = co_await task;

هاك

$result = await task;

سهم

var result = await task;

مع كل ذلك ، لنتذكر أن تعبيرات Rust يمكن أن ينتج عنها عدة طرق متسلسلة. تميل معظم اللغات إلى عدم القيام بذلك.

ال 512 كومينتر

اعتقدت أنه قد يكون من المفيد كتابة كيفية تعامل اللغات الأخرى مع بنية انتظار.


كوتلن

val result = task.await()

ج

var result = await task;

F

let! result = task()

سكالا

val result = Await.result(task, timeout)

بايثون

result = await task

جافا سكريبت

let result = await task;

C ++ (Coroutines TR)

auto result = co_await task;

هاك

$result = await task;

سهم

var result = await task;

مع كل ذلك ، لنتذكر أن تعبيرات Rust يمكن أن ينتج عنها عدة طرق متسلسلة. تميل معظم اللغات إلى عدم القيام بذلك.

مع كل ذلك ، لنتذكر أن تعبيرات Rust يمكن أن ينتج عنها عدة طرق متسلسلة. تميل معظم اللغات إلى عدم القيام بذلك.

أود أن أقول إن اللغات التي تدعم طرق الامتداد تميل إلى امتلاكها. قد تشمل هذه Rust و Kotlin و C # (على سبيل المثال أسلوب - بناء جملة LINQ ومختلف البنائين) و F # ، على الرغم من أن الأخير يستخدم بشدة مشغل الأنابيب لنفس التأثير.

حكاية بحتة من جانبي ، لكنني أعمل بانتظام في أكثر من عشرة تعبيرات متسلسلة بطريقة + في كود Rust في البرية وهي تقرأ وتعمل بشكل جيد. لم أختبر هذا في مكان آخر.

أود أن أرى أنه تمت الإشارة إلى هذه المشكلة في المنشور العلوي رقم 50547 (بجانب مربع الاختيار "بناء الجملة النهائي للانتظار.").

كوتلن

val result = task.await()

بناء جملة Kotlin هو:

val result = doTask()

await هو مجرد suspendable function ، وليس شيئًا من الدرجة الأولى.

شكرا لذكر ذلك. يبدو Kotlin أكثر ضمنية لأن العقود الآجلة حريصة بشكل افتراضي. ومع ذلك ، لا يزال نمطًا شائعًا في الكتلة المؤجلة لاستخدام هذه الطريقة للانتظار على الكتل المؤجلة الأخرى. لقد فعلت ذلك بالتأكيد عدة مرات.

cramertj نظرًا لوجود 276 تعليقًا في https://github.com/rust-lang/rust/issues/50547 ، هل يمكنك تلخيص الحجج المقدمة هناك لتسهيل عدم تكرارها هنا؟ (ربما تضيفهم إلى OP هنا؟)

يبدو Kotlin أكثر ضمنية لأن العقود الآجلة حريصة بشكل افتراضي. ومع ذلك ، لا يزال نمطًا شائعًا في الكتلة المؤجلة لاستخدام هذه الطريقة للانتظار على الكتل المؤجلة الأخرى. لقد فعلت ذلك بالتأكيد عدة مرات.

ربما يجب عليك إضافة حالتي الاستخدام مع قليل من السياق / الوصف.

أيضًا ما الذي يحدث مع اللغات الأخرى التي تستخدم الانتظار الضمني ، مثل go-lang؟

أحد الأسباب التي تجعلك تفضل بناء جملة ما بعد الإصلاح هو أنه من منظور المتصل ، يتصرف الانتظار كثيرًا مثل استدعاء الوظيفة: أنت تتخلى عن التحكم في التدفق وعندما تستعيده ، تنتظر النتيجة على مكدسك. على أي حال ، أنا أفضل بناء جملة يتضمن السلوك الشبيه بالوظيفة من خلال احتواء الارتداد الوظيفي. وهناك أسباب وجيهة لرغبتك في فصل بناء coroutines من أول تنفيذ لها بحيث يكون هذا السلوك متسقًا بين كتل المزامنة وغير المتزامنة.

لكن بينما تمت مناقشة أسلوب coroutine الضمني ، وأنا إلى جانب الصراحة ، فهل يمكن ألا يكون .await!() محاولة أكثر أو أقل للتمييز بين المكالمات العادية ومكالمات coroutine.

لذلك ، بعد تقديم فكرة جديدة إلى حد ما عن سبب تفضيل ما بعد الإصلاح ، اقتراح متواضع للنحو:

  • future(?)
  • أو future(await) الذي يأتي مع المقايضات الخاصة به بالطبع ولكن يبدو أنه مقبول على أنه أقل إرباكًا ، انظر أسفل المنشور.

تكييف مثال شائع إلى حد ما من مؤشر ترابط آخر (بافتراض أن logger.log هو أيضًا coroutine ، لإظهار الشكل الذي يبدو عليه الاتصال الفوري):

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
}

ومع البديل:

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
}

لتجنب الكود غير المقروء وللمساعدة في التحليل ، اترك مسافات بعد علامة الاستفهام فقط ، وليس بينها وبين الباران المفتوح. لذا فإن future(? ) جيد بينما future( ?) لن يكون كذلك. لا تظهر هذه المشكلات في حالة future(await) حيث يمكن استخدام جميع الرموز المميزة الحالية كما في السابق.

التفاعل مع عوامل تشغيل ما بعد الإصلاح الأخرى (مثل المحاولة الحالية ? ) هي أيضًا كما في استدعاءات الوظائف:

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 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)
}

بعض الأسباب لمثل هذا:

  • بالمقارنة مع .await!() فإنه لا يشير إلى عضو يمكن أن يكون له استخدامات أخرى.
  • يتبع الأسبقية الطبيعية للمكالمات ، مثل التسلسل واستخدام ? . هذا يحافظ على عدد فصول الأولوية أقل ويساعد في التعلم. ودائمًا ما كانت استدعاءات الوظائف خاصة إلى حد ما في اللغة (على الرغم من أن لها سمة) ، لذلك لا يوجد توقع أن يكون رمز المستخدم قادرًا على تحديد my_await!() والذي له نفس البنية والتأثير.
  • يمكن أن يعمم هذا على المولدات والجداول ، وكذلك المولدات التي تتوقع تقديم المزيد من الحجج عند الاستئناف. من حيث الجوهر ، يتصرف هذا على أنه FnOnce بينما Streams سيتصرف مثل FnMut . يمكن أيضًا استيعاب الحجج الإضافية بسهولة.
  • بالنسبة لأولئك الذين استخدموا Futures قبل ، فإن هذا يوضح كيف أن ? مع Poll يجب أن يعمل طوال الوقت (تسلسل استقرار مؤسف هنا). وكخطوة تعلم ، فإنها تتوافق أيضًا مع توقع قيام عامل تشغيل قائم على ? بتحويل تدفق التحكم. من ناحية أخرى ، لن يرضي (await) هذا ولكن بعد كل شيء ستتوقع الوظيفة دائمًا استئنافها عند نقطة التباعد.
  • إنها تستخدم صيغة تشبه الوظيفة ، على الرغم من أن هذه الحجة جيدة فقط إذا كنت تتفق معي: ابتسم:

وأسباب عدم إعجابك بهذا:

  • ? وسيطة لكنها لا تطبق حتى على تعبير. أعتقد أن هذا يمكن حله من خلال التدريس ، لأن الرمز المميز الذي يبدو أنه مطبق عليه هو استدعاء الوظيفة نفسه ، وهو المفهوم الصحيح إلى حد ما. هذا يعني أيضًا بشكل إيجابي أن بناء الجملة لا لبس فيه ، كما آمل.
  • قد يكون من الصعب تحليل مزيج أكثر (ومختلف) من الأقواس و ? . خاصة عندما يكون لديك مستقبل يعود نتيجة لمستقبل آخر: construct_future()(?)?(?)? . ولكن يمكنك عمل نفس الوسيطة لتكون قادرًا على الحصول على نتيجة كائن fn ، مما يؤدي إلى السماح بتعبير مثل هذا: foobar()?()?()? . نظرًا لأنني لم أر مطلقًا هذا الاستخدام ولا الشكوى ، يبدو أن الانقسام إلى بيانات منفصلة في مثل هذه الحالات أمر نادر الحدوث. لا توجد هذه المشكلات أيضًا لـ construct_future()(await)?(await)? -
  • future(?) هو أفضل ما لدي في بناء جملة مقتضب ومختصر إلى حد ما. ومع ذلك ، يعتمد منطقه على تفاصيل التنفيذ في coroutines (العودة مؤقتًا والإرسال في السيرة الذاتية) ، مما قد يجعله غير مناسب للتجريد. future(await) بديلاً يمكن أن يكون قابلاً للتفسير بعد أن تم استيعاب await ككلمة رئيسية ولكن موضع الحجة يصعب تقبله قليلاً بالنسبة لي. يمكن أن يكون جيدًا ، وبالتأكيد يكون أكثر قابلية للقراءة عندما يعرض coroutine نتيجة.
  • التداخل مع مقترحات استدعاء الوظيفة الأخرى؟
  • بنفسك؟ لا تحتاج إلى إعجابك ، لقد شعرت وكأنه مضيعة لعدم اقتراح بناء الجملة المقتضب بعد الإصلاح هذا على الأقل.

future(?)

لا يوجد شيء مميز بخصوص Result : يمكن للعقود الآجلة إرجاع أي نوع من الصدأ. يحدث فقط أن بعض العقود المستقبلية ترجع Result

فكيف يمكن أن يعمل ذلك مع العقود الآجلة التي لا تُرجع Result ؟

يبدو أنه لم يكن من الواضح ما قصدته. future(?) هو ما تمت مناقشته سابقًا كـ future.await!() أو ما شابه. التفرع إلى المستقبل الذي يُرجع نتيجة أيضًا سيكون future(?)? (طريقتان مختلفتان لكيفية التخلي عن التحكم في التدفق مبكرًا). هذا يجعل الاقتراع المستقبلي (?) ونتائج اختبار ? متعامد. تحرير: إضافة مثال إضافي لهذا.

التفرع في المستقبل الذي يُرجع نتيجة أيضًا سيكون future(?)?

شكرا للتوضيح. في هذه الحالة أنا بالتأكيد لست من المعجبين بها.

هذا يعني أن استدعاء الدالة التي تُرجع Future<Output = Result<_, _>> ستُكتب مثل foo()(?)?

إنها ثقيلة جدًا في بناء الجملة ، وتستخدم ? لغرضين مختلفين تمامًا.

إذا كان هذا هو التلميح إلى عامل التشغيل ? وهو أمر ثقيل ، فيمكن بالطبع استبداله بالكلمة الأساسية المحجوزة حديثًا. كنت قد اعتبرت في البداية فقط أن هذا يبدو كثيرًا وكأنه حجة فعلية من النوع المحير ، لكن المقايضة يمكن أن تعمل من حيث المساعدة في تحليل العبارة عقليًا. لذا فإن نفس بيان impl Future<Output = Result<_,_>> سيصبح:

  • foo()(await)?

أفضل حجة لماذا ? مناسبة هي أن الآلية الداخلية المستخدمة متشابهة إلى حد ما (وإلا فلن نتمكن من استخدام Poll في المكتبات الحالية) ولكن هذا قد يخطئ الهدف من كونه تجريدًا جيدًا.

إنها ثقيلة جدًا في بناء الجملة

اعتقدت أن هذا هو بيت القصيد من ينتظر صريح؟

تستخدم؟ لغرضين مختلفين تمامًا.

نعم ، لذا فإن بناء الجملة foo()(await) سيكون أجمل كثيرًا.

بناء الجملة هذا يشبه استدعاء دالة تقوم بإرجاع الإغلاق ثم استدعاء هذا الإغلاق في JS.

كانت قراءتي لـ "ثقيل بناء الجملة" أقرب إلى "ثقيل سيجيل" ، ورؤية تسلسل ()(?)? أمر مزعج للغاية. تم طرح هذا في المنشور الأصلي:

قد يكون من الصعب تحليل مزيج أكثر (ومختلف) من الأقواس و ? . خاصة عندما يكون لديك مستقبل يعود نتيجة لمستقبل آخر: construct_future()(?)?(?)?

ولكن يمكنك عمل نفس الوسيطة لتكون قادرًا على الحصول على نتيجة كائن fn ، مما يؤدي إلى السماح بتعبير مثل هذا: foobar()?()?()? . نظرًا لأنني لم أر مطلقًا هذا الاستخدام ولا الشكوى ، يبدو أن الانقسام إلى بيانات منفصلة في مثل هذه الحالات أمر نادر الحدوث.

أعتقد أن الطعن هنا هو: كم مرة رأيت -> impl Fn في البرية (ناهيك عن -> Result<impl Fn() -> Result<impl Fn() -> Result<_, _>, _>, _> )؟ كم مرة تتوقع أن ترى -> impl Future<Output = Result<_, _>> في قاعدة كود غير متزامن؟ إن الاضطرار إلى تسمية قيمة إرجاع نادرة impl Fn لتسهيل قراءة الكود يختلف تمامًا عن الاضطرار إلى تسمية جزء مهم من قيم الإرجاع المؤقتة impl Future .

إن الاضطرار إلى تسمية قيمة إرجاع Fn ضمنية نادرة لتسهيل قراءة الكود يختلف تمامًا عن الاضطرار إلى تسمية جزء مهم من قيم الإرجاع المستقبلية الضمنية.

لا أرى كيف يؤثر اختيار بناء الجملة هذا على عدد المرات التي يتعين عليك فيها تسمية نوع النتيجة بوضوح. لا أعتقد أنه لا يؤثر على نوع الاستدلال يختلف عن await? future .

ومع ذلك ، فقد قدمت جميعًا نقاطًا جيدة جدًا هنا ، وكلما استفدت من المزيد من الأمثلة (لقد قمت بتحرير المنشور الأصلي ليحتوي دائمًا على كلا الإصدارين من بناء الجملة) ، كلما اتجهت نحو future(await) بنفسي. ليس من غير المعقول الكتابة ، ولا يزال يحتفظ بكل وضوح بناء جملة استدعاء الوظيفة الذي كان يهدف إلى استحضاره.

كم مرة تتوقع أن ترى -> ضمني المستقبل> في قاعدة بيانات غير متزامنة؟

أتوقع أن أرى نوعًا مكافئًا لهذا (fn غير متزامن يعيد نتيجة) طوال الوقت ، وربما حتى غالبية جميع fns غير المتزامن ، لأنه إذا كان ما تنتظره هو IO ، فمن المؤكد أنك سترمي أخطاء الإدخال والإخراج إلى الأعلى.


ربط رسالتي السابقة حول قضية التتبع وإضافة بعض الأفكار الأخرى.

أعتقد أن هناك فرصة ضئيلة جدًا لقبول بناء الجملة الذي لا يتضمن سلسلة الأحرف await لبناء الجملة هذا. أعتقد أنه في هذه المرحلة ، بعد عام من العمل على هذه الميزة ، سيكون من الأفضل محاولة الموازنة بين إيجابيات وسلبيات البدائل القابلة للتطبيق المعروفة لمحاولة العثور على الأفضل من اقتراح تراكيب جديدة. أعتقد أن التركيبات قابلة للتطبيق ، بالنظر إلى مشاركاتي السابقة:

  • البادئة في انتظار المحددات الإلزامية. هنا أيضًا قرار بشأن المحددات (إما الأقواس أو الأقواس أو قبول كليهما ؛ كل هذه لها مزاياها وعيوبها). أي ، await(future) أو await { future } . هذا يحل مشاكل الأسبقية تمامًا ، ولكنه صاخب من الناحية النحوية ويمثل كلا الخيارين المحددين مصادر محتملة للارتباك.
  • البادئة في انتظار الأسبقية "المفيدة" بخصوص ? . (وهذا هو ، التي تنتظر ملزمة أشد من؟). قد يفاجئ هذا بعض المستخدمين الذين يقرؤون الكود ، لكنني أعتقد أن الوظائف التي تعيد العقود الآجلة للنتائج ستكون أكثر شيوعًا بشكل كبير من الوظائف التي تعرض نتائج العقود الآجلة.
  • البادئة في انتظار الأسبقية "الواضحة" بخصوص ? . (وهذا يعني أن الربط أقوى من الانتظار). السكر النحوي الإضافي await? لمجموعة انتظار و؟ المشغل أو العامل. أعتقد أن بناء الجملة هذا ضروري لجعل ترتيب الأسبقية هذا قابلاً للتطبيق على الإطلاق ، وإلا فإن الجميع سيكتب (await future)? طوال الوقت ، وهو البديل الأسوأ للخيار الأول الذي عدته.
  • Postfix في انتظار مساحة بناء الجملة في انتظار. هذا يحل مشكلة الأسبقية من خلال وجود ترتيب مرئي واضح بين المشغلين. أشعر بعدم الارتياح تجاه هذا الحل في كثير من النواحي.

يتغير ترتيبي الخاص بين هذه الاختيارات في كل مرة أفحص فيها المشكلة. اعتبارًا من هذه اللحظة ، أعتقد أن استخدام الأسبقية الواضحة مع السكر يبدو أنه أفضل توازن بين بيئة العمل والألفة والفهم. لكنني في الماضي فضلت أيًا من صيغتي البادئة الأخريين.

من أجل المناقشة ، سأقدم هذه الخيارات الأربعة هذه الأسماء:

الاسم | المستقبل | مستقبل النتيجة | نتيجة المستقبل
--- | --- | --- | -
المحددات الإلزامية | await(future) أو await { future } | await(future)? أو await { future }? | await(future?) أو await { future? }
أسبقية مفيدة | await future | await future? | await (future?)
الأسبقية الواضحة مع السكر | await future | await? future أو (await future)? | await future?
كلمة Postfix | future await | future await? | future? await

(لقد استخدمت على وجه التحديد "الكلمة الأساسية لما بعد الإصلاح" لتمييز هذا الخيار عن تراكيب أخرى لما بعد الإصلاح مثل "ماكرو postfix".)

أحد أوجه القصور في "النعمة" await future? في الأسبقية المفيدة ولكن أيضًا الآخرين الذين لا يعملون بعد الإصلاح هو أن الأنماط المعتادة لتحويل التعبيرات يدويًا باستخدام ? قد لا تنطبق ، أو تطلب أن يقوم Future بتكرار أساليب Result بطريقة متوافقة. أجد هذا مفاجئًا. إذا تم تكرارها ، فسيصبح الأمر مربكًا بشكل مفاجئ أي من المجمعات يعمل على مستقبل عائد وأيها حريص. بعبارة أخرى ، سيكون من الصعب تحديد ما يفعله الدمج بالفعل كما هو الحال في حالة الانتظار الضمني. (تحرير: في الواقع ، راجع تعليقين أدناه حيث لدي منظور تقني أكثر ما أعنيه باستبدال مفاجئ لـ ? )

مثال حيث يمكننا التعافي من حالة الخطأ:

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);
}

لا تحدث هذه المشكلة لمشغلي ما بعد الإصلاح الفعلي:

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);

هذه تعبيرات مختلفة ، عامل النقطة له أسبقية أعلى من عامل التشغيل "الأسبقية الواضحة" await ، لذا فإن المكافئ هو:

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

هذا له نفس الغموض تمامًا سواء كان or_recover غير متزامن أم لا. (الذي أجادل به لا يهم ، فأنت تعلم أن التعبير ككل غير متزامن ، ويمكنك إلقاء نظرة على تعريف or_recover إذا كنت لسبب ما بحاجة إلى معرفة ما إذا كان هذا الجزء المحدد غير متزامن).

هذا له نفس الغموض تمامًا عما إذا كان or_recover متزامنًا أم لا.

ليس بالضبط نفس الشيء. يجب أن ينتج unwrap_or_else كوروتين لأنه منتظر ، لذا فإن الغموض هو ما إذا كان get_result عبارة عن coroutine (لذلك تم إنشاء أداة التجميع) أو Result<impl Future, _>Ok يحتوي بالفعل على coroutine ، و Err يبني واحدًا). كلاهما ليس لديه نفس المخاوف المتعلقة بالقدرة على تحديد مكاسب الكفاءة بشكل سريع من خلال نقل نقطة تسلسل await إلى join ، وهو أحد الاهتمامات الرئيسية لـ انتظار ضمني. والسبب هو أنه في أي حال، يجب أن يكون هذا الحساب الوسيط متزامنة ويجب أن يكون طبقت إلى نوع قبل ننتظر ويجب أدت إلى coroutine المنتظر. هناك اهتمام أكبر ببعضنا البعض هنا:

هذه تعبيرات مختلفة ، عامل النقطة له أسبقية أعلى من عامل انتظار "الأسبقية الواضحة" ، لذا فإن المكافئ هو

هذا جزء من الارتباك ، حيث أدى استبدال ? بعملية استرداد إلى تغيير موضع await أساسي. في سياق بناء الجملة ? ، بالنظر إلى التعبير الجزئي expr من النوع T ، أتوقع الدلالات التالية من التحويل (بافتراض وجود T::unwrap_or_else ) :

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

ومع ذلك ، ضمن "أسبقية مفيدة" و await expr? ( await expr تنتج T ) نحصل عليها بدلاً من ذلك

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

بينما في الأسبقية الواضحة ، لم يعد هذا التحول ساريًا على الإطلاق دون مزيد من الارتياب ، ولكن على الأقل لا يزال الحدس يعمل من أجل "نتيجة المستقبل".

وماذا عن الحالة الأكثر إثارة للاهتمام حيث تنتظر عند نقطتين مختلفتين في تسلسل المركب؟ مع أي صيغة بادئة ، أعتقد أن هذا يتطلب أقواس. تحاول بقية لغة Rust تجنب هذا على طول لجعل "التعبيرات تقيم من اليسار إلى اليمين" ، أحد الأمثلة على ذلك هو سحر المرجع التلقائي.

مثال لإظهار أن هذا يزداد سوءًا بالنسبة للسلاسل الأطول التي تحتوي على نقاط انتظار / محاولة / دمج متعددة.

// 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)?;
}

ما أرغب في تجنبه ، هو إنشاء التناظرية Rust من نوع C لتحليل المكان الذي تقفز فيه
الجانب الأيمن والأيسر من التعبير لمُجمعات "المؤشر" و "المصفوفة".

إدخال الجدول بأسلوب withoutboats :

| الاسم | المستقبل | مستقبل النتيجة | نتيجة المستقبل |
| - | - | - | - |
| المحددات الإلزامية | await(future) | await(future)? | await(future?) |
| أسبقية مفيدة | await future | await future? | await (future?) |
| أسبقية واضحة | await future | await? future | await future? |
| استدعاء Postfix | future(await) | future(await)? | future?(await) |

| الاسم | بالسلاسل |
| - | - |
| المحددات الإلزامية | await(await(foo())?.bar())? |
| أسبقية مفيدة | await(await foo()?).bar()? |
| أسبقية واضحة | await? (await? foo()).bar() |
| استدعاء Postfix | foo()(await)?.bar()(await) |

أنا أؤيد بشدة انتظار postfix لأسباب مختلفة ولكني لا أحب المتغير الذي يظهر بواسطة withoutboats ، ويبدو في المقام الأول لنفس الأسباب. على سبيل المثال. foo await.method() محير.

أولاً ، دعنا نلقي نظرة على جدول مشابه ولكن مع إضافة متغيرين آخرين بعد الإصلاح:

| الاسم | المستقبل | مستقبل النتيجة | نتيجة المستقبل |
| ---------------------- | -------------------- | ----- ---------------- | --------------------- |
| المحددات الإلزامية | await { future } | await { future }? | await { future? } |
| أسبقية مفيدة | await future | await future? | await (future?) |
| أسبقية واضحة | await future | await? future | await future? |
| كلمة Postfix | future await | future await? | future? await |
| حقل بوستفيكس | future.await | future.await? | future?.await |
| طريقة Postfix | future.await() | future.await()? | future?.await() |

الآن دعونا نلقي نظرة على تعبير مستقبلي مقيد:

| الاسم | نتائج العقود الآجلة |
| ---------------------- | -------------------------- ----------- |
| المحددات الإلزامية | await { await { foo() }?.bar() }? |
| أسبقية مفيدة | await (await foo()?).bar()? |
| أسبقية واضحة | await? (await? foo()).bar() |
| كلمة Postfix | foo() await?.bar() await? |
| حقل بوستفيكس | foo().await?.bar().await? |
| طريقة Postfix | foo().await()?.bar().await()? |

والآن للحصول على مثال حقيقي ، بدءًا من reqwests ، حيث قد ترغب في انتظار مستقبل متسلسل من النتائج (باستخدام نموذج الانتظار المفضل لدي).

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

في الواقع أعتقد أن كل فاصل يبدو جيدًا لبناء جملة postfix ، على سبيل المثال:
let res: MyResponse = client.get("https://my_api").send()/await?.json()/await?;
لكن ليس لدي رأي قوي حول أيهما أستخدم.

هل يمكن أن يظل ماكرو postfix (مثل future.await!() ) خيارًا؟ إنه واضح وموجز ولا لبس فيه:

| المستقبل | مستقبل النتيجة | نتيجة المستقبل |
| --- | --- | --- |
| Future.await! () | Future.await! ()؟ | المستقبل؟ .await! () |

يتطلب ماكرو postfix أيضًا جهدًا أقل ليتم تنفيذه ، وهو سهل الفهم والاستخدام.

يتطلب ماكرو postfix أيضًا جهدًا أقل ليتم تنفيذه ، وهو سهل الفهم والاستخدام.

كما أنها تستخدم ميزة lang الشائعة (أو على الأقل ستبدو مثل ماكرو postfix عادي).

سيكون ماكرو postfix لطيفًا لأنه يجمع بين الإيجاز وقابلية التسلسل لما بعد الإصلاح مع الخصائص غير السحرية والوجود الواضح لوحدات الماكرو ، ويتناسب جيدًا مع وحدات الماكرو الخاصة بمستخدم الطرف الثالث ، مثل بعض .await_debug!() ، .await_log!(WARN) أو .await_trace!()

سيكون ماكرو postfix رائعًا لأنه يجمع بين [...] الخصائص غير السحرية [...] لوحدات الماكرو

novacrazy ، المشكلة في هذه الحجة هي أن أي ماكرو await! سيكون سحريًا ، إنه يؤدي عملية غير ممكنة في التعليمات البرمجية التي كتبها المستخدم (حاليًا يتم الكشف عن التنفيذ المستند إلى المولد الأساسي إلى حد ما ، لكن ما أفهمه هو ذلك قبل التثبيت ، سيكون هذا مخفيًا تمامًا (ويتطلب التفاعل معه في الوقت الحالي استخدام بعض الميزات الداخلية الليلية على أي حال rustc ).

@ Nemo157 هممم . لم أكن أعلم أنه كان من المفترض أن يكون شديد الغموض.

هل فات الأوان لإعادة النظر في استخدام ماكرو إجرائي مثل #[async] لإجراء التحويل من وظيفة "غير متزامن" إلى وظيفة المولد ، بدلاً من استخدام كلمة رئيسية سحرية؟ يجب كتابة ثلاثة أحرف إضافية ، ويمكن تمييزها في المستندات بنفس طريقة كتابة #[must_use] أو #[repr(C)] .

أنا حقا لا أحب فكرة إخفاء الكثير من طبقات التجريد التي تتحكم بشكل مباشر في تدفق التنفيذ. إنه شعور مناقض لما هو عليه Rust. يجب أن يكون المستخدم قادرًا على التتبع الكامل من خلال الكود ومعرفة كيفية عمل كل شيء وأين يذهب التنفيذ. يجب تشجيعهم على اختراق الأشياء وخداع الأنظمة ، وإذا استخدموا الصدأ الآمن ، فيجب أن يكون آمنًا. هذا لا يحسن أي شيء إذا فقدنا السيطرة على المستوى المنخفض ، وقد ألتزم أيضًا بالعقود الآجلة.

أعتقد اعتقادًا راسخًا أن Rust ، اللغة (ليست std / core ) ، يجب أن تقدم أفكارًا تجريدية وبناء جملة فقط إذا كان من المستحيل (أو غير عملي للغاية) القيام بها من قبل المستخدمين أو std . لقد خرج هذا الشيء غير المتزامن بأكمله عن السيطرة في هذا الصدد. هل نحتاج حقًا إلى أي شيء أكثر من واجهة برمجة تطبيقات الدبوس والمولدات في rustc ؟

novacrazy أنا أتفق بشكل عام مع المشاعر ولكن ليس مع الاستنتاج.

يجب أن تقدم أفكارًا مجردة ونحوية فقط إذا كان من المستحيل (أو غير عملي للغاية) القيام بها من قبل المستخدمين أو الأمراض المنقولة جنسياً.

ما سبب وجود حلقات for في اللغة بينما يمكن أن تكون أيضًا ماكروًا يتحول إلى loop مع فواصل. ما سبب || closure عندما يمكن أن تكون سمة مخصصة ومنشئات كائن. لماذا قدمنا ? لدينا بالفعل try!() . سبب اختلافي مع هذه الأسئلة واستنتاجاتك هو الاتساق. الهدف من هذه الأفكار التجريدية ليس فقط السلوك الذي تغلفه ولكن أيضًا إمكانية الوصول إليه. for -الاستبدال ينقسم إلى قابلية التغيير ومسار الكود الأساسي وسهولة القراءة || ينقسم الاستبدال في الإسهاب في الإعلان - على غرار Futures حاليًا. try!() ينقسم بالترتيب المتوقع للتعبيرات والتركيب.

ضع في اعتبارك أن async ليس فقط المصمم على وظيفة ، ولكن هناك أفكار أخرى لتوفير أنماط إضافية من خلال aync-blocks و async || . نظرًا لأنه ينطبق على عناصر لغة مختلفة ، فإن قابلية استخدام الماكرو تبدو دون المستوى الأمثل. لا تفكر حتى في التنفيذ إذا كان يجب أن يكون مرئيًا للمستخدم في ذلك الوقت.

يجب أن يكون المستخدم قادرًا على التتبع الكامل من خلال الكود ومعرفة كيفية عمل كل شيء وأين يذهب التنفيذ. يجب تشجيعهم على اختراق الأشياء وخداع الأنظمة ، وإذا استخدموا الصدأ الآمن ، فيجب أن يكون آمنًا.

لا أعتقد أن هذه الحجة تنطبق لأن تنفيذ coroutines باستخدام std api من المحتمل أن يعتمد بشدة على unsafe . ثم هناك حجة العكسي لأنه في حين أنها قابلة للتنفيذ والتي لن تتوقف حتى لو كان هناك طريقة النحوية والدلالية في اللغة للقيام بذلك، أي تغيير سيكون بشكل كبير لخطر كسر الافتراضات في unsafe -code. أزعم أن Rust لا ينبغي أن يجعلها تبدو وكأنها تحاول تقديم واجهة قياسية لتنفيذ البتات التي لا تنوي استقرارها قريبًا ، بما في ذلك الأجزاء الداخلية من Coroutines. سيكون التناظرية لهذا هو extern "rust-call" وهو بمثابة السحر الحالي لتوضيح أن استدعاءات الوظائف ليس لها أي ضمان من هذا القبيل. ونحن قد تريد أن يكون لها أبدا في الواقع إلى return ، على الرغم من أن مصير coroutines stackful لم يتم بعد البت في. قد نرغب في ربط التحسين بشكل أعمق في المترجم.

بعيدًا: عند الحديث عن أي فكرة ، من الناحية النظرية ، ليست فكرة جادة تمامًا ، يمكن الإشارة إلى coroutine المنتظر على أنها extern "await-call" fn () -> T افتراضي؟ إذا كان الأمر كذلك ، فإن هذا سيسمح في المقدمة أ

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

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

الملقب ب. future.await() في عناصر مساحة المستخدم الموثقة. أو لهذه المسألة ، قد يكون بناء جملة المشغل الآخر ممكنًا أيضًا.

تضمين التغريدة

لماذا قدمنا ? لدينا بالفعل try!()

لكي نكون منصفين ، كنت ضد هذا أيضًا ، على الرغم من أنه قد نما علي. سيكون أكثر قبولًا إذا كان Try قد استقر يومًا ما ، لكن هذا موضوع آخر.

المشكلة مع أمثلة "السكر" التي قدمتها هي أنها سكر رقيق جدًا جدًا. حتى impl MyStruct هو سكر أكثر أو أقل مقابل impl <anonymous trait> for MyStruct . هذه هي سكريات جودة الحياة التي تضيف صفرًا من النفقات العامة على الإطلاق.

على النقيض من ذلك ، تضيف المولدات والوظائف غير المتزامنة نفقات غير مهمة تمامًا ونفقات عقلية كبيرة. من الصعب جدًا تنفيذ المولدات على وجه التحديد كمستخدم ، ويمكن استخدامها بشكل أكثر فاعلية وسهولة كجزء من اللغة نفسها ، بينما يمكن تنفيذ غير المتزامن فوق ذلك بسهولة نسبيًا.

على الرغم من ذلك ، فإن النقطة المتعلقة بالكتل غير المتزامنة أو عمليات الإغلاق مثيرة للاهتمام ، وأعترف بأن الكلمة الرئيسية ستكون أكثر فائدة هناك ، لكنني ما زلت أعارض عدم القدرة على الوصول إلى العناصر ذات المستوى الأدنى إذا لزم الأمر.

من الناحية المثالية، سيكون من الرائع أن دعم async الكلمة و #[async] مولد السمة / ماكرو الإجرائي، مع السماح بالوصول السابق على مستوى منخفض للولدت (لا يقصد التوريه). في الوقت نفسه ، يجب عدم السماح باستخدام yield في الكتل أو الوظائف باستخدام async ككلمة رئيسية. أنا متأكد من أنه يمكنهم مشاركة رمز التنفيذ.

أما بالنسبة لل await ، إذا كان كل ما سبق ممكن، ونحن يمكن أن تفعل شيئا من هذا القبيل، والحد من الكلمات الرئيسية await إلى async الكلمة / وظائف بنات، وتستخدم نوعا من await!() ماكرو في #[async] وظائف.

كود مزيف:

// 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);
}

أفضل ما في العالمين.

يبدو هذا وكأنه تقدم طبيعي أكثر في التعقيد لتقديمه للمستخدم ، ويمكن استخدامه بشكل أكثر فعالية لمزيد من التطبيقات.

يرجى تذكر أن هذه المشكلة مخصصة لمناقشة بنية await . المحادثات الأخرى حول كيفية تنفيذ وظائف وكتل async خارج النطاق ، باستثناء أغراض تذكير الأشخاص بأن await! ليس شيئًا يمكنك أو ستتمكن من كتابته في Rust's لغة السطح.

أود أن أثقل على وجه التحديد إيجابيات وسلبيات جميع مقترحات بناء الجملة بعد الإصلاح. إذا تميزت إحدى الصيغ النحوية بقدر ضئيل من السلبيات ، فربما يجب علينا أن نتبعها. إذا لم يكن هناك أي شيء ، فسيكون من الأفضل دعم بناء الجملة المحدد ببادئة البنية والذي يتم إعادة توجيه التوافق إلى إصلاح لاحق لم يتم تحديده بعد إذا دعت الحاجة. نظرًا لأن Postfix يبدو أنه أكثر إيجازًا بالنسبة لعدد قليل من الأعضاء ، يبدو أنه من العملي تقييمها بقوة أولاً قبل الانتقال إلى الآخرين.

ستكون المقارنة syntax ، example (يبدو reqwest من concerns ، و resolution اختياري await سيشعر على الأرجح بأنه غريب عن الوافدين الجدد والمستخدمين ذوي الخبرة على حد سواء ، لكن جميع القواعد المدرجة حاليًا تشملها.

مثال في بناء جملة بادئة للإشارة فقط ، لا تقم بركوب الدراجة في هذا الجزء من فضلك:

let sent = (await client.get("https://my_api").send())?;
let res: MyResponse = (await sent.json())?;
  • الكلمة الأساسية Postfix foo() await?

    • مثال: client.get("https://my_api").send() await?.json() await?

    • | قلق | القرار |

      | - | - |

      | قد يكون التسلسل بدون ? مربكًا أو غير مسموح به | |

  • حقل Postfix foo().await?

    • مثال: client.get("https://my_api").send().await?.json().await?

    • | قلق | القرار |

      | - | - |

      | يبدو وكأنه حقل | |

  • طريقة Postfix foo().await()?

    • مثال: client.get("https://my_api").send().await()?.json().await()?

    • | قلق | القرار |

      | - | - |

      | تبدو وكأنها طريقة أو سمة | قد يتم توثيقه على أنه ops:: سمة؟ |

      | ليس استدعاء وظيفة | |

  • استدعاء Postfix foo()(await)?

    • مثال: client.get("https://my_api").send()(await)?.json()(await)?

    • | قلق | القرار |

      | - | - |

      | يمكن الخلط بينه وبين الحجة الفعلية | كلمة رئيسية + تمييز + غير متداخلة |

  • ماكرو Postfix foo().await!()?

    • مثال: client.get("https://my_api").send().await!()?.json().await!()?

    • | قلق | القرار |

      | - | - |

      | لن يكون ماكرو في الواقع… | |

      | … أو ، await لم تعد كلمة أساسية | |

فكرة إضافية حول البادئة اللاحقة للإصلاح من وجهة نظر إمكانية دمج المولدات: النظر في القيم ، yield و await تحتل نوعين متعارضين من العبارات. السابق يعطي قيمة من وظيفتك إلى الخارج ، والأخير يقبل قيمة.

ملاحظة جانبية: حسنًا ، لدى Python مولدات تفاعلية حيث يمكن لـ yield إرجاع قيمة. بشكل متماثل ، تحتاج المكالمات إلى مثل هذا المولد أو التدفق إلى وسيطات إضافية في إعداد كتابة قوي. دعونا لا نحاول التعميم أكثر من اللازم ، وسنرى أن الحجة من المحتمل أن تنتقل في كلتا الحالتين.

بعد ذلك ، أزعم أنه من غير الطبيعي أن تكون هذه العبارات متشابهة. هنا ، على غرار تعبيرات التخصيص ، ربما يجب علينا الانحراف عن المعيار الذي حددته اللغات الأخرى عندما يكون هذا المعيار أقل اتساقًا وأقل إيجازًا بالنسبة إلى Rust. كما تم التعبير عنه في مكان آخر ، طالما أننا نقوم بتضمين await والتشابه مع التعبيرات الأخرى بنفس ترتيب الوسيطة ، فلا ينبغي أن يكون هناك عائق كبير للانتقال حتى من نموذج آخر.

منذ يبدو ضمنيًا خارج الطاولة.

من استخدام غير متزامن / انتظار في لغات أخرى والنظر في الخيارات هنا ، لم أجد أبدًا أنه من الجيد من الناحية التركيبية سلسلة العقود الآجلة.

هل البديل غير قادر على السلسلة على الطاولة؟

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

أنا أحب هذا نوعًا ما حيث يمكنك تقديم الحجة القائلة بأنه جزء من النمط.

المشكلة في جعل انتظار الربط هي أن قصة الخطأ أقل من لطيفة.

// 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?;

نحتاج إلى أن نضع في اعتبارنا أن جميع العقود الآجلة المتاحة في المجتمع للانتظار تقريبًا قابلة للخطأ ويجب دمجها مع ? .

إذا كنا حقًا بحاجة إلى السكر التركيبي:

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

يجب إضافة كل من await و await? ككلمات رئيسية أو نوسع ذلك إلى let أيضًا ، أي let? result = 1.divide(0);

بالنظر إلى عدد المرات التي يتم فيها استخدام التسلسل في كود Rust ، أتفق تمامًا على أنه من المهم أن تكون فترات الانتظار المقيدة واضحة قدر الإمكان للقارئ. في حالة متغير postfix انتظار:

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

هذا بشكل عام يتصرف بشكل مشابه للطريقة التي أتوقع أن يتصرف بها كود Rust. لدي مشكلة مع حقيقة أن await() في هذا السياق يبدو وكأنه استدعاء دالة ، لكن لديه سلوك سحري (لا يشبه الوظيفة) في سياق التعبير.

ستجعل نسخة الماكرو postfix هذا أكثر وضوحا. اعتاد الناس على استخدام علامات التعجب في الصدأ بمعنى "يوجد سحر هنا" وأنا بالتأكيد أفضل هذا الإصدار لهذا السبب.

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

مع ذلك ، يجدر التفكير في أن لدينا بالفعل try!(expr) في اللغة وكان هذا هو سلفنا مقابل ? . ستكون إضافة ماكرو await!(expr) الآن متسقة تمامًا مع كيفية تقديم try!(expr) و ? للغة.

مع الإصدار await!(expr) من الانتظار ، لدينا خيار إما الانتقال إلى ماكرو postfix لاحقًا ، أو إضافة عامل تشغيل جديد على غرار ? بحيث يصبح التسلسل أمرًا سهلاً. مثال مشابه لـ ? لكن في انتظار:

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

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

أعتقد أنه يجب علينا استخدام await!(expr) أو await!{expr} في الوقت الحالي حيث إنه معقول جدًا وعملي. يمكننا بعد ذلك التخطيط للانتقال إلى إصدار postfix من الانتظار (على سبيل المثال .await! أو .await!() ) لاحقًا إذا أصبحت وحدات ماكرو postfix شيئًا ما. (أو في النهاية الذهاب إلى طريق إضافة عامل نمط إضافي ? ... بعد الكثير من التعرّف على الموضوع: P)

لمعلوماتك ، فإن بناء جملة Scala ليس Await.result لأن هذه مكالمة حظر. العقود الآجلة لـ Scala هي monads ، وبالتالي استخدم استدعاءات الطريقة العادية أو for monad comprehension:

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

نتيجة لهذا الترميز الفظيع ، تم إنشاء مكتبة تسمى scala-async باستخدام الصيغة التي أؤيدها بشدة ، وهي كما يلي:

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
}

هذا يعكس بقوة ما أريد أن يبدو عليه كود الصدأ ، مع استخدام المحددات الإلزامية ، وعلى هذا النحو ، أود أن أتفق مع الآخرين على البقاء مع الصيغة الحالية await!() . كان الصدأ المبكر رمزًا ثقيلًا ، وتم إبعاده لسبب وجيه ، على ما أفترض. استخدام السكر النحوي في شكل عامل postfix (أو ما لديك) هو ، كما هو الحال دائمًا ، متوافقًا مع الإصدارات السابقة ، ووضوح await!(future) لا لبس فيه. كما أنه يعكس التقدم الذي أحرزناه مع try! ، كما ذكرنا سابقًا.

من فوائد الاحتفاظ بها كوحدة ماكرو أنه من الواضح بشكل فوري في لمحة أنها ميزة لغة وليست استدعاء وظيفة عادية. بدون إضافة ! ، فإن إبراز بناء الجملة للمحرر / العارض سيكون أفضل طريقة لتكون قادرًا على اكتشاف المكالمات ، وأعتقد أن الاعتماد على هذه التطبيقات هو خيار أضعف.

سنتان (ليس مساهمًا منتظمًا ، fwiw) أنا متحيز للغاية لنسخ نموذج try! . لقد تم إجراؤها مرة واحدة من قبل ، وقد نجحت بشكل جيد وبعد أن أصبحت شائعة جدًا ، كان هناك عدد كافٍ من المستخدمين للنظر في عامل postfix.

لذا فإن تصويتي سيكون: الاستقرار مع await!(...) و punt على عامل postfix للتسلسل اللطيف بناءً على استطلاع لمطوري Rust. انتظار كلمة رئيسية ، ولكن ! يشير إلى أن هذا شيء "سحري" بالنسبة لي والأقواس تجعله واضحًا.

أيضا مقارنة:

| بوستفيكس | التعبير |
| --- | --- |
| .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()$? |

سنتي الثالث هو أنني أحب @ (لـ "انتظار") و # (لتمثيل خيوط المعالجة المتعددة / التزامن).

أنا أحب postfix @ أيضًا! أعتقد أنه ليس خيارًا سيئًا في الواقع ، على الرغم من أنه يبدو أن هناك بعض المشاعر بأنه غير قابل للتطبيق.

  • _ @ لانتظار_ هو ذاكري لطيف وسهل التذكر
  • ? و @ متشابهين جدًا ، لذا لا ينبغي أن يكون تعلم @ بعد تعلم ? قفزة كبيرة
  • يساعد في مسح سلسلة من التعبيرات من اليسار إلى اليمين ، دون الحاجة إلى المسح للأمام للعثور على محدد إغلاق من أجل فهم التعبير

أنا أؤيد بشدة بناء الجملة await? foo ، وأعتقد أنه مشابه لبعض القواعد النحوية في الرياضيات ، حيث على سبيل المثال. يمكن استخدام sin² x لتعني (sin x) ². يبدو الأمر محرجًا بعض الشيء في البداية ، لكنني أعتقد أنه من السهل جدًا التعود عليه.

كما ذكرنا سابقًا ، أنا مؤيد لإضافة await!() كماكرو ، تمامًا مثل try!() ، في الوقت الحالي ، وفي النهاية أقرر كيفية إصلاحه. إذا استطعنا أن نضع في اعتبارنا دعم الإصلاح الذي يحول تلقائيًا مكالمات await!() إلى postfix في انتظار لم يتم تحديده بعد ، بل إنه أفضل.

خيار الكلمات الرئيسية postfix هو الفائز الواضح بالنسبة لي.

  • لا توجد مشكلة في الترتيب / الأسبقية ، ومع ذلك لا يزال من الممكن جعل الترتيب واضحًا باستخدام الأقواس. ولكن في الغالب لا حاجة إلى التداخل المفرط (حجة مماثلة لتفضيل postfix "؟" كبديل لـ "try ()!").

  • يبدو جيدًا مع التسلسل متعدد الخطوط (انظر التعليق السابق بواسطةearthengine) ، ومرة ​​أخرى ، لا يوجد أي لبس فيما يتعلق بالطلب أو ما يتم انتظاره. ولا توجد أقواس متداخلة / إضافية للتعبيرات ذات الاستخدامات المتعددة للانتظار:

let x = x.do_something() await
         .do_another_thing() await;
let x = x.foo(|| ...)
         .bar(|| ...)
         .baz() await;
  • يفسح المجال لانتظار بسيط! () الماكرو (انظر التعليق السابق من قبلnovacrazy):
macro_rules! await {
    ($e:expr) => {{$e await}}
}
  • حتى سطر واحد ، عارية (بدون "؟") ، postfix في انتظار تسلسل الكلمات الرئيسية لا يزعجني لأنه يقرأ من اليسار إلى اليمين ونحن ننتظر عودة القيمة التي تعمل عليها الطريقة اللاحقة (على الرغم من أنني سأفعل ذلك فقط تفضل رمز rustfmt'ed متعدد الأسطر). الفضاء يكسر الخط ويكفي لمؤشر / إشارة بصرية تنتظر:
client.get("https://my_api").send() await.unwrap().json() await.unwrap()

لاقتراح مرشح آخر لم أره مقدمًا بعد (ربما لأنه لن يكون قابلاً للتحليل) ، فماذا عن عامل ما بعد الإصلاح ".." مرح مزدوج النقاط؟ يذكرني أننا ننتظر شيئًا ما (النتيجة!) ...

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

أنا أحب postfix @ أيضًا! أعتقد أنه ليس خيارًا سيئًا في الواقع ، على الرغم من أنه يبدو أن هناك بعض المشاعر بأنه غير قابل للتطبيق.

  • _ @ لانتظار_ هو ذاكري لطيف وسهل التذكر
  • ? و @ متشابهين جدًا ، لذا لا ينبغي أن يكون تعلم @ بعد تعلم ? قفزة كبيرة
  • يساعد في مسح سلسلة من التعبيرات من اليسار إلى اليمين ، دون الحاجة إلى المسح للأمام للعثور على محدد إغلاق من أجل فهم التعبير

لست من محبي استخدام @ للانتظار. من المحرج الكتابة على لوحة مفاتيح ذات تخطيط زعنفة / سوي حيث يتعين علي الضغط على alt-gr بإبهامي الأيمن ثم الضغط على المفتاح 2 في صف الأرقام. أيضًا ، @ لها معنى راسخ (at) لذلك لا أرى سببًا لدمج معناها.

أفضل أن تكتب ببساطة await ، إنها أسرع لأنها لا تتطلب أي حركات بهلوانية في لوحة المفاتيح.

هذا هو تقييمي الشخصي للغاية. لقد أضفت أيضًا future@await ، وهو ما يبدو مثيرًا للاهتمام بالنسبة لي.

| بناء الجملة | ملاحظات |
| --- | --- |
| await { f } | قوي:

  • واضحة جدا
  • المتوازيات for ، loop ، async إلخ.
ضعيف:
  • مطول جدًا (5 أحرف ، قوسان ، 3 اختياري ، لكن من المحتمل مسافات ممزقة)
  • نتائج التسلسل في العديد من الأقواس المتداخلة ( await { await { foo() }?.bar() }? )
|
| await f | قوي:
  • يوازي بناء الجملة await من Python و JS و C # و Dart
  • مباشر وقصير
  • يتصرف كل من الأسبقية المفيدة في مقابل الأسبقية الواضحة بشكل جيد مع ? ( await fut? مقابل await? fut )
ضعيف:
  • غامض: يجب تعلم الأسبقية المفيدة مقابل الأسبقية الواضحة
  • التسلسل أيضًا مرهق جدًا ( await (await foo()?).bar()? مقابل await? (await? foo()).bar() )
|
| fut.await
fut.await()
fut.await!() | قوي:
  • يسمح بتسلسل سهل للغاية
  • قصيرة
  • إكمال كود لطيف
ضعيف:
  • يخدع المستخدمين ليعتقدوا أنه حقل / وظيفة / ماكرو محدد في مكان ما. تحرير: أتفق مع jplatte على أن await!() هو الأقل سحريًا
|
| fut(await) | قوي:
  • يسمح بتسلسل سهل للغاية
  • قصيرة
ضعيف:
  • يخدع المستخدمين في التفكير في وجود متغير await محدد في مكان ما وأن العقود الآجلة يمكن تسميتها كدالة
|
| f await | قوي:
  • يسمح بتسلسل سهل للغاية
  • قصيرة
ضعيف:
  • لا يوازي أي شيء في بناء جملة Rust ، ليس واضحًا
  • يقوم عقلي بتجميع client.get("https://my_api").send() await.unwrap().json() await.unwrap() في client.get("https://my_api").send() ، await.unwrap().json() و await.unwrap() (مجمعة حسب أولاً ، ثم . ) وهذا ليس صحيحا
  • بالنسبة لـ Haskellers: يشبه الكاري ولكنه ليس كذلك
|
| f@ | قوي:
  • يسمح بتسلسل سهل للغاية
  • قصير جدا
ضعيف:
  • يبدو محرجًا بعض الشيء (على الأقل في البداية)
  • يستهلك @ والذي قد يكون أكثر ملاءمة لشيء آخر
  • قد يكون من السهل التغاضي عنه ، خاصة في التعبيرات الكبيرة
  • يستخدم @ بطريقة مختلفة عن كل اللغات الأخرى
|
| f@await | قوي:
  • يسمح بتسلسل سهل للغاية
  • قصيرة
  • إكمال كود لطيف
  • لا يلزم أن يصبح await كلمة رئيسية
  • متوافق مع التوجيه: يسمح بإضافة عوامل تشغيل postfix جديدة بالشكل @operator . على سبيل المثال ، كان من الممكن تنفيذ ? كـ @try .
  • يقوم عقلي بتجميع client.get("https://my_api").send()@await.unwrap().json()@await.unwrap() في المجموعات الصحيحة (مجمعة حسب . أولاً ، ثم @ )
ضعيف:
  • يستخدم @ بطريقة مختلفة عن كل اللغات الأخرى
  • قد تحفز على إضافة عدد كبير جدًا من عوامل التشغيل اللاحقة غير الضرورية
|

نقاطي:

  • الألفة (fam): ما مدى قرب بناء الجملة هذا من التركيبات المعروفة (Rust وغيرها ، مثل Python و JS و C #)
  • الوضوح (obv): إذا قرأت هذا في كود شخص آخر لأول مرة ، فهل ستتمكن من تخمين المعنى والأسبقية وما إلى ذلك؟
  • الإسهاب (vrb): عدد الأحرف اللازمة للكتابة
  • الرؤية (الرؤية): ما مدى سهولة اكتشافه (مقابل التغاضي عنه) في الكود
  • التسلسل (cha): ما مدى سهولة ربطها بـ . و await s الأخرى
  • التجميع (GRP): ما إذا كان عقلي يقوم بتجميع الكود في الأجزاء الصحيحة
  • التوافق الأمامي (fwd): ما إذا كان هذا يسمح بتعديله لاحقًا بطريقة غير منقطعة

| بناء الجملة | فام | obv | vrb | تجاه | تشا | 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 |

يبدو لي أنه يجب علينا عكس بناء جملة try!() في المقطع الأول والحصول على بعض الاستخدام الحقيقي من استخدام await!(expr) قبل تقديم بعض الصيغ الأخرى.

ومع ذلك ، إذا / عندما نقوم ببناء بناء جملة بديل ..

أعتقد أن @ يبدو قبيحًا ، و "at" لـ "async" لا يبدو ذلك بديهيًا بالنسبة لي ، والرمز مستخدم بالفعل لمطابقة الأنماط.

تؤدي البادئة async بدون أقواس إلى أسبقية غير واضحة أو أقواس محيطة عند استخدامها مع ? (والتي غالبًا ما تكون).

سلاسل Postfix .await!() بشكل جيد ، تبدو واضحة إلى حد ما في معناها ، تتضمن ! لتخبرني أنها ستعمل السحر ، وأقل ابتكارًا من الناحية التركيبية ، لذا فإن أسلوب "القطع التالي" شخصيا سوف يفضل هذا. ومع ذلك ، بالنسبة لي ، يبقى أن نرى إلى أي مدى سيحسن الكود الحقيقي خلال أول عملية قطع await! (expr) .

أفضل عامل تشغيل البادئة للحالات البسيطة:
let result = await task;
يبدو الأمر أكثر طبيعية مع الأخذ في الاعتبار أننا لا نكتب نوع النتيجة ، لذا فإن الانتظار يساعد عقليًا عند القراءة من اليسار إلى اليمين لفهم هذه النتيجة هي مهمة الانتظار.
تخيلها على هذا النحو:
let result = somehowkindoflongtask await;
حتى لا تصل إلى نهاية المهمة ، لا تدرك أنه يجب انتظار النوع الذي ستعود إليه. ضع في اعتبارك أيضًا (على الرغم من أن هذا عرضة للتغيير وليس مرتبطًا بشكل مباشر بمستقبل اللغة) أن IDEs مثل Intellij مضمنة في النوع (دون أي تخصيص ، إذا كان ذلك ممكنًا) بين الاسم والمتساوي.
تخيلها على هذا النحو:
6voler6ykj

هذا لا يعني أن رأيي هو مائة بالمائة نحو البادئة. أنا أفضل إصدار ما بعد الإصلاح للمستقبل عند تضمين النتائج ، حيث يبدو ذلك أكثر طبيعية. بدون أي سياق يمكنني بسهولة تحديد أي من هذه الوسائل يعني ماذا:
future await?
future? await
بدلاً من ذلك ، انظر إلى هذا ، أيهما صحيح ، من وجهة نظر مبتدئ:
await future? === await (future?)
await future? === (await future)?

أنا أؤيد الكلمة الأساسية البادئة: await future .

إنها اللغة المستخدمة من قبل معظم لغات البرمجة التي لها عدم تزامن / انتظار ، وبالتالي فهي مألوفة على الفور للأشخاص الذين يعرفون إحداها.

بالنسبة إلى أسبقية await future? ما هي الحالة الشائعة؟

  • دالة تقوم بإرجاع Result<Future> يجب انتظارها.
  • المستقبل الذي يجب انتظاره والذي يعيد Result : Future<Result> .

أعتقد أن الحالة الثانية أكثر شيوعًا عند التعامل مع السيناريوهات النموذجية ، حيث قد تفشل عمليات الإدخال / الإخراج. وبالتالي:

await future? <=> (await future)?

في الحالة الأولى الأقل شيوعًا ، من المقبول وجود أقواس: await (future?) . قد يكون هذا استخدامًا جيدًا للماكرو try! إذا لم يتم إهماله: await try!(future) . بهذه الطريقة ، لا يكون عامل التشغيل await وعلامة الاستفهام على جوانب مختلفة من المستقبل.

لماذا لا تأخذ await كأول معلمة دالة 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);

هنا future.run(await) بديل لـ await future .
يمكن أن تكون مجرد وظيفة عادية async تأخذ المستقبل وتعمل ببساطة await!() الماكرو عليها.

C ++ (التزامن TR)

auto result = co_await task;

هذا في Coroutines TS ، وليس التزامن.

قد يكون الخيار الآخر هو استخدام الكلمة الرئيسية become بدلاً من 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 كلمة رئيسية لتكلفة إجمالية مضمونة للملكية بالرغم من ذلك

شكرا EyeOfPython على ذلك [نظرة عامة]. هذا مفيد بشكل خاص للأشخاص الذين ينضمون الآن إلى سقيفة الدراجات.

أنا شخصياً آمل أن نبتعد عن f await ، لمجرد أنه بناء غير ريفي للغاية ويجعله يبدو مميزًا وسحريًا. سيصبح أحد هذه الأشياء التي سيشوش المستخدمون الجدد على Rust بشأنها كثيرًا وأشعر أنه لا يضيف الكثير من الوضوح ، حتى للمحاربين القدامى في Rust ، ليكونوا يستحقون ذلك.

novacrazy ، المشكلة في هذه الوسيطة هي أن أي ماكرو await! سيكون سحريًا ، فهو يؤدي عملية غير ممكنة في التعليمات البرمجية التي كتبها المستخدم

@ Nemo157 أوافق على أن الماكرو await! سيكون سحريًا ، لكنني أزعم أن هذه ليست مشكلة. هناك العديد من وحدات الماكرو في std بالفعل ، على سبيل المثال compile_error! ولم أر أي شخص يشكو منها. أعتقد أنه من الطبيعي استخدام الماكرو فقط لفهم ما يفعله ، وليس كيف يفعله.

أتفق مع المعلقين السابقين على أن postfix سيكون الأكثر راحة ، لكنني أفضل أن أبدأ بالماكرو البادئة await!(expr) وربما الانتقال إلى postfix-macro بمجرد أن يكون هذا شيئًا بدلاً من أن يكون لديك expr.await (حقل مدمج في المترجم السحري) أو expr.await() (طريقة تضمين مترجم سحري). كلاهما سيقدم صياغة جديدة تمامًا لهذه الميزة فقط ، و IMO الذي يجعل اللغة تشعر بعدم الاتساق.

EyeOfPython هل تمانع إضافة future(await) إلى قوائمك وجدولك ؟ يبدو أن جميع إيجابيات تقييمك لـ future.await() تنتقل دون ضعف

نظرًا لأن البعض جادل بأنه على الرغم من تمييز بناء الجملة ، فإن foo.await يشبه إلى حد كبير الوصول إلى الحقل ، يمكننا تغيير الرمز المميز . إلى # وبدلاً من ذلك كتابة foo#await . فمثلا:

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

لتوضيح كيفية عرض GitHub لهذا مع تمييز بناء الجملة ، دعنا نستبدل await بـ match لأنهما متساويان في الطول:

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

يبدو هذا واضحًا ومريحًا.

الأساس المنطقي لـ # بدلاً من بعض الرموز الأخرى ليس محددًا ، لكن الرمز المميز مرئي تمامًا مما يساعد.

إذن ، مفهوم آخر: إذا كان المستقبل مرجعية :

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;

سيكون هذا قبيحًا حقًا وغير متسق. دعنا على الأقل نزيل غموض هذا النحو:

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;

أفضل ، لكنه لا يزال قبيحًا (والأسوأ من ذلك ، فإنه يجعل إبراز بناء جملة جيثب). ومع ذلك ، للتعامل مع ذلك ، دعنا نقدم القدرة على تأخير عامل البادئة :

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;

هذا بالضبط ما أريد! ليس فقط للانتظار ( $ ) ولكن أيضًا لـ deref ( * ) و negate ( ! ).

بعض المحاذير في هذا النحو:

  1. أسبقية عامل التشغيل: بالنسبة لي هذا واضح ولكن للمستخدمين الآخرين؟
  2. إمكانية التشغيل المتداخل لوحدات الماكرو: هل يتسبب الرمز $ حدوث مشكلات هنا؟
  3. التعبير التناقض: المشغل الرائد بادئة ينطبق على التعبير كله في حين ينطبق عامل بادئة تأخر فقط ل . تعبير محدد ( await_chain FN تثبت ذلك)، غير أن الخلط؟
  4. التحليل والتنفيذ: هل هذا النحو صحيح على الإطلاق؟

تضمين التغريدة
IMO # قريب جدًا من البنية الحرفية الخام r#"A String with "quotes""#

IMO # قريب جدًا من البنية الحرفية الخام r#"A String with "quotes""#

يبدو واضحًا تمامًا من السياق ما هو الاختلاف في هذه الحالة.

تضمين التغريدة
إنه كذلك ، لكن بناء الجملة غريب أيضًا على النمط الذي يستخدمه Rust IMHO. لا يشبه أي بناء جملة مع وظيفة مماثلة.

Laaas لم يفعل أيضًا ? عندما تم تقديمه. سأكون سعيدا للذهاب مع .await ؛ لكن يبدو أن الآخرين غير راضين عن ذلك ، لذا فأنا أحاول العثور على شيء آخر يعمل (أي أنه واضح ، ومريح ، وسهل الطباعة بدرجة كافية ، وقابل للتسلسل ، وله أسبقية جيدة) ويبدو أن foo#await يرضي كل ذلك.

? على مجموعة من النقاط الجيدة الأخرى ، بينما يبدو #await عشوائيًا إلى حد ما. على أي حال ، إذا كنت تريد شيئًا مثل .await ، فلماذا لا .await! ؟

Centril كان هذا هو الأساس المنطقي وراء future(await) ، لتجنب الوصول إلى الحقل مع عدم الحاجة إلى إضافة أي عوامل تشغيل إضافية أو بناء جملة خارجي.

بينما #await يبدو تعسفيا نوعا ما.

# تعسفي ، نعم - هل هذا ناجح أم لا؟ عندما يتم اختراع بناء جملة جديد في مرحلة ما ، يجب أن يكون تعسفيًا لأن شخصًا ما اعتقد أنه يبدو جيدًا أو منطقيًا.

لماذا لا .await! ؟

هذا تعسفي بنفس القدر لكن ليس لدي معارضة له.

Centril كان هذا هو الأساس المنطقي وراء future(await) ، لتجنب الوصول إلى الحقل دون الحاجة إلى إضافة أي عوامل تشغيل إضافية أو بناء جملة خارجي.

بدلاً من ذلك ، يبدو مثل تطبيق الوظيفة حيث تقوم بتمرير await إلى future ؛ التي تبدو لي محيرة أكثر من "الوصول الميداني".

بدلا من ذلك يبدو وكأنه تطبيق وظيفة حيث تمر في انتظار المستقبل ؛ التي تبدو لي محيرة أكثر من "الوصول الميداني".

بالنسبة لي كان هذا جزءًا من الإيجاز. انتظار في العديد من الجوانب مثل استدعاء الوظيفة (يتم تنفيذ المكالمة بينك وبين النتيجة) ، وقد أوضح تمرير كلمة رئيسية إلى هذه الوظيفة أن هذا نوع مختلف من الاستدعاء. لكني أرى كيف يمكن أن يكون محيرًا أن تكون الكلمة الرئيسية مشابهة لأي حجة أخرى. (ناقص إبراز بناء الجملة ورسائل خطأ المترجم التي تحاول تعزيز هذه الرسالة. لا يمكن استدعاء العقود المستقبلية بأي طريقة أخرى غير الانتظار والعكس صحيح).

تضمين التغريدة

كونك تعسفيًا ليس شيئًا سلبيًا ، لكن التشابه مع بناء جملة موجود هو أمر إيجابي. .await! ليس تعسفيًا ، لأنه ليس بناء جملة جديدًا تمامًا ؛ بعد كل شيء ، إنه مجرد ماكرو بعد الإصلاح يسمى await .

لتوضيح / إضافة إلى النقطة المتعلقة بالصياغة الجديدة على وجه التحديد لـ await ، ما أعنيه بذلك هو تقديم بناء جملة جديد يختلف عن الصيغة الحالية. يوجد الكثير من الكلمات الرئيسية البادئة ، والتي تمت إضافة بعضها بعد Rust 1.0 (على سبيل المثال union ) ، لكن AFAIK ليست كلمة رئيسية واحدة بعد الإصلاح (بغض النظر عما إذا كانت مفصولة بمسافة أو نقطة أو أي شيء آخر) . لا يمكنني أيضًا التفكير في أي لغة أخرى تحتوي على كلمات مفتاحية postfix.

IMHO هو بناء جملة postfix الوحيد الذي لا يزيد بشكل كبير من غرابة Rust هو الماكرو postfix ، لأنه يعكس استدعاءات الطريقة ولكن يمكن تحديده بوضوح على أنه ماكرو من قبل أي شخص رأى ماكرو Rust من قبل.

لقد أحببت أيضًا الماكرو القياسي await!() . انها مجرد واضحة وبسيطة.
أنا أفضل وصول يشبه الحقل (أو أي postfix آخر) للتواجد معًا كـ awaited .

  • انتظر تشعر أنك ستنتظر ما يأتي بعد ذلك / على حق. في انتظار ما جاء قبل / يسار.
  • منسجم مع طريقة cloned() على التكرارات.

وأحببت أيضًا @? مثل ? مثل السكر.
هذا أمر شخصي ، لكنني في الواقع أفضل مجموعة &? ، لأن @ يميل إلى أن يكون طويلًا جدًا ولا يصلح الخط ، وهو أمر مشتت للغاية. & هو أيضًا طويل القامة (جيد) ولكنه لا يفشل (جيد أيضًا). لسوء الحظ ، فإن & له معنى بالفعل ، على الرغم من أنه سيتبعه دائمًا ? .. أعتقد؟

على سبيل المثال.

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

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

بالنسبة لي ، فإن @ يبدو وكأنه شخصية منتفخة ، ويشتت انتباه تدفق القراءة. من ناحية أخرى ، يشعر &? بالسعادة ، ولا يشتت انتباهي عن القراءة ولديه مساحة علوية لطيفة بين & و ? .

أنا شخصياً أعتقد أنه يجب استخدام ماكرو بسيط await! . إذا دخلت وحدات الماكرو بعد الإصلاح في اللغة ، فيمكن ببساطة توسيع الماكرو ، أليس كذلك؟

Laaas بقدر ما أود أن يكون هناك ، لا توجد وحدات ماكرو postfix في Rust حتى الآن ، لذا فهي بناء جملة جديدة. لاحظ أيضًا أن foo.await!.bar و foo.await!().bar ليسا نفس التركيبة السطحية. في الحالة الأخيرة ، سيكون هناك ما بعد الإصلاح الفعلي والماكرو المدمج (والذي يستلزم التخلي عن await ككلمة رئيسية).

تضمين التغريدة

يوجد الكثير من الكلمات الرئيسية البادئة ، والتي تمت إضافة بعضها بعد Rust 1.0 (على سبيل المثال union ) ،

union ليس عامل تعبير أحادي لذا فهو غير ذي صلة في هذه المقارنة. يوجد بالضبط 3 عوامل تشغيل لبادئة أحادية ثابتة في Rust ( return ، break ، continue ) وكلهم مكتوبون على ! .

حسنًا ... مع @ يبدو اقتراحي السابق أفضل قليلاً:

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 هذه هي وجهة نظري ، نظرًا لأنه ليس لدينا وحدات ماكرو ما بعد الإصلاح حتى الآن ، يجب أن تكون ببساطة await!() . كما قصدت .await!() لمعلوماتك. لا أعتقد أن await يجب أن يكون كلمة رئيسية ، على الرغم من أنه يمكنك حجزها إذا وجدت أنها مشكلة.

union ليس عامل تعبير أحادي لذا فهو غير ذي صلة في هذه المقارنة. يوجد بالضبط 3 معاملات بادئة أحادية ثابتة في Rust ( return ، break ، و continue ) وجميعهم مكتوبون على ! .

لا يزال هذا يعني أن متغير الكلمة الأساسية البادئة يلائم مجموعة عوامل التعبير الأحادي ، حتى لو تمت كتابته بشكل مختلف. إن متغير الكلمة الأساسية postfix هو اختلاف أكبر بكثير عن الصيغة الحالية ، وهو اختلاف كبير جدًا بالنسبة إلى أهمية await في اللغة في رأيي.

فيما يتعلق بادئة الماكرو وإمكانية الانتقال إلى الإصلاح اللاحق: await يحتاج إلى أن يظل كلمة أساسية حتى يعمل هذا. سيكون الماكرو بعد ذلك عنصرًا خاصًا من lang حيث يُسمح باستخدام كلمة رئيسية كاسم بدون r# إضافي ، و r#await!() يمكن لكن ربما لا يجب أن يستدعي نفس الماكرو. بخلاف ذلك ، يبدو أنه الحل الأكثر واقعية لجعله متاحًا.

أي ، احتفظ بـ await ككلمة رئيسية ولكن اجعل await! يتحول إلى ماكرو lang-item.

HeroicKatora لماذا يجب أن تظل كلمة أساسية حتى تعمل؟

Laaas لأنه إذا أردنا الاحتفاظ بإمكانية الانتقال مفتوحة ، فنحن بحاجة إلى أن نظل منفتحين لبناء جملة ما بعد الإصلاح في المستقبل يستخدمها ككلمة رئيسية. لذلك ، نحتاج إلى الاحتفاظ بحجز الكلمات الرئيسية حتى لا نحتاج إلى فاصل إصدار للانتقال.

تضمين التغريدة

لاحظ أيضًا أن foo.await! .bar و foo.await! (). bar ليسا نفس بناء جملة السطح. في الحالة الأخيرة ، سيكون هناك ماكرو postfix وماكرو مدمج (والذي يستلزم التخلي عن الانتظار ككلمة رئيسية).

ألا يمكن حل ذلك بجعل الكلمة الأساسية await مجتمعة مع ! تتوصل إلى ماكرو داخلي (لا يمكن تحديده من خلال الوسائل العادية)؟ ثم تظل كلمة أساسية ولكنها تتحول إلى ماكرو في بناء جملة الماكرو.

HeroicKatora لماذا يكون await في x.await!() كلمة رئيسية محجوزة؟

لن يكون الأمر كذلك ، ولكن إذا أبقينا على الإصلاح اللاحق دون حل ، فلا داعي أن يكون هذا هو الحل الذي نتوصل إليه في المناقشات اللاحقة. إذا كان هذا هو أفضل الاحتمال الفريد المتفق عليه ، فعلينا اعتماد بناء الجملة الدقيق بعد الإصلاح في المقام الأول.

هذه مرة أخرى نواجه فيها شيئًا يعمل بشكل أفضل كمشغل لما بعد الإصلاح. والمثال الكبير على ذلك هو try! والذي وضعناه في النهاية ? . ومع ذلك ، أعتقد أن هذه ليست المرة الأخيرة التي يكون فيها عامل ما بعد الإصلاح هو الأفضل ولا يمكننا إعطاء كل شيء حرفًا خاصًا به. لذلك أعتقد أننا يجب ألا نبدأ على الأقل بـ @ . سيكون من الأفضل كثيرًا أن يكون لدينا طريقة للقيام بهذا النوع من الأشياء. هذا هو السبب في أنني أؤيد نمط الماكرو postfix .await!() .

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

ولكن لكي يكون هذا منطقيًا ، يجب تقديم وحدات ماكرو postfix لهم. لذلك أعتقد أنه من الأفضل البدء بتركيب ماكرو عادي await!(foo) . يمكننا لاحقًا توسيع هذا إما إلى foo.await!() أو حتى foo@ إذا شعرنا حقًا أن هذا مهم بما يكفي لتبرير رمزه الخاص.
إن احتياج هذا الماكرو إلى القليل من السحر ليس أمرًا جديدًا بالنسبة للأمراض المنقولة جنسيًا وليست مشكلة كبيرة بالنسبة لي.
كما قال jplatte :

@ Nemo157 أوافق على أن الماكرو await! سيكون سحريًا ، لكنني أزعم أن هذه ليست مشكلة. هناك العديد من وحدات الماكرو في std بالفعل ، على سبيل المثال compile_error! ولم أر أي شخص يشكو منها. أعتقد أنه من الطبيعي استخدام الماكرو فقط لفهم ما يفعله ، وليس كيف يفعله.

هناك مشكلة متكررة أراها تتم مناقشتها هنا تتعلق بالتسلسل وكيفية الاستخدام
انتظر في تسلسل التعبيرات. ربما يمكن أن يكون هناك حل آخر؟

إذا أردنا عدم استخدام الانتظار للتسلسل واستخدام الانتظار فقط
المهام ، يمكن أن يكون لدينا شيء مثل استبدال let بـ wait:
await foo = future

ثم للتسلسل يمكننا تخيل نوع من العمليات مثل await res = fut1 -> fut2 أو await res = fut1 >>= fut2 .

الحالة الوحيدة المفقودة هي انتظار وإرجاع النتيجة ، بعض الاختصار
مقابل await res = fut; res .
يمكن القيام بذلك بسهولة باستخدام await fut عادي

لا أعتقد أنني رأيت اقتراحًا آخر مثل هذا (على الأقل لـ
التسلسل) ، لذا فإن إسقاط ذلك هنا كما أعتقد أنه سيكون من الجيد استخدامه.

HeroicKatora لقد أضفت fut(await) إلى القائمة وقمت بترتيبها وفقًا لرأيي.

إذا شعر أي شخص أن درجاتي معطلة ، من فضلك قل لي!

من الناحية التركيبية ، أعتقد أن .await!() نظيف ويسمح بالتسلسل مع عدم إضافة أي شذوذ إلى بناء الجملة.

ومع ذلك ، إذا حصلنا على وحدات ماكرو postfix "حقيقية" ، فسيكون ذلك غريبًا بعض الشيء ، لأنه من المحتمل أن يكون .r#await!() مظللًا ، بينما .await!() لا يمكنه ذلك.

أنا أعارض بشدة خيار "postfix keyword" (مفصولاً بمسافة مثل: foo() await?.bar() await? ) ، لأنني وجدت حقيقة أن await مرتبط بالجزء التالي من التعبير ، وليس الجزء الذي تعمل عليه. أنا أفضل إلى حد كبير أي رمز آخر غير المسافة البيضاء هنا ، وحتى أفضل بناء جملة البادئة على هذا على الرغم من عيوبه مع السلاسل الطويلة.

أعتقد أن "الأسبقية الواضحة" (مع await? sugar) هي أفضل خيار للبادئة بوضوح ، حيث أن المحددات الإلزامية هي ألم للحالة الشائعة جدًا المتمثلة في انتظار عبارة واحدة ، و "الأسبقية المفيدة" ليست على الإطلاق بديهية ، وبالتالي مربكة.

من الناحية التركيبية ، أعتقد أن .await!() نظيف ويسمح بالتسلسل مع عدم إضافة أي شذوذ إلى بناء الجملة.

ومع ذلك ، إذا حصلنا على وحدات ماكرو postfix "حقيقية" ، فسيكون ذلك غريبًا بعض الشيء ، لأنه من المحتمل أن يكون .r#await!() مظللًا ، بينما .await!() لا يمكنه ذلك.

إذا حصلنا على وحدات ماكرو postfix واستخدمنا .await!() فيمكننا إلغاء الاحتفاظ بـ await ككلمة رئيسية وجعلها ماكرو postfix. لا يزال تنفيذ هذا الماكرو يتطلب القليل من سحر العدالة ولكنه سيكون ماكروًا حقيقيًا تمامًا مثل compiler_error! اليوم.

EyeOfPython هل يمكنك أن تشرح بالتفصيل التغييرات التي تراها في forward-compatibility ؟ لست متأكدًا من الطريقة التي سيكون بها سعر fut@await أعلى من fut.await!() و await { future } أقل من await!(future) . يبدو العمود verbosity أيضًا غريبًا بعض الشيء ، فبعض التعبيرات قصيرة ولكن لها تصنيف أقل ، هل تعتبر عبارات متسلسلة ، إلخ. يبدو أن كل شيء آخر متوازن ويشبه التقييم الشامل الأكثر تكثيفًا حتى الآن.

بعد قراءة هذه المناقشة ، يبدو أن الكثير من الأشخاص يريدون إضافة ماكرو عادي await!() واكتشاف إصدار postfix لاحقًا. هذا بافتراض أننا نريد بالفعل نسخة postfix ، والتي سأعتبرها صحيحة لبقية هذا التعليق.

لذا أود فقط أن أستطلع رأي الجميع هنا: هل يجب أن نتفق جميعًا على بناء جملة await!(future) لـ NOW؟ المحترفون هم أنه لا يوجد غموض تحليلي في بناء الجملة هذا ، وهو أيضًا ماكرو ، لذلك لا تغيير في بنية اللغة لدعم هذا التغيير. العيوب هي أنه سيبدو قبيحًا للتسلسل ، لكن هذا لا يهم لأن بناء الجملة هذا يمكن استبداله بسهولة بإصدار postfix تلقائيًا.

بمجرد تسوية هذا الأمر وتطبيقه في اللغة ، يمكننا بعد ذلك متابعة المناقشة الخاصة بـ postfix في انتظار بناء الجملة مع الأمل بتجارب وأفكار أكثر نضجًا وربما ميزات أخرى للمترجم.

HeroicKatora لقد صنفته على أنه أعلى لأنه يمكن أن يحرر await ليتم استخدامه كمعرف عادي بشكل طبيعي وسيسمح بإضافة عوامل postfix أخرى ، بينما أعتقد أن fut.await!() سيكون أفضل إذا await تم حجز fut.await!() يمكن أن يكون أعلى.

بالنسبة إلى await { future } مقابل await!(future) ، فإن الخيار الأخير يبقي الخيار مفتوحًا للتغيير إلى أي من الخيارات الأخرى تقريبًا ، في حين أن الخيار الأول لا يسمح إلا بالفعل بأي من المتغيرات await future كما هو موضح في إدخال مدونة withoutboats ). لذلك أعتقد أنه يجب أن يكون بالتأكيد fwd(await { future }) < fwd(await!(future)) .

أما فيما يتعلق بالإسهاب ، فأنت على صواب ، بعد تقسيم الحالات إلى مجموعات فرعية ، لم أعد تقييم الإسهاب ، والذي ينبغي أن يكون الأكثر موضوعية على الإطلاق.

سأقوم بتحريره لأخذ تعليقاتك في الاعتبار ، شكرًا لك!

الاستقرار في await!(future) هو أسوأ خيار ممكن أتخيله:

  1. هذا يعني أنه يتعين علينا إلغاء الاحتفاظ بـ await مما يعني أيضًا أن تصميم اللغة المستقبلية يصبح أكثر صعوبة.
  2. إنه عن قصد يسلك نفس المسار كما هو الحال مع try!(result) الذي تم إيقافه (والذي يتطلب كتابة r#try!(result) في Rust 2018).

    • إذا علمنا أن بناء الجملة await!(future) هو بناء سيء فإننا نعني في النهاية التخلص منه ، فهذا يؤدي عمداً إلى إنشاء ديون فنية.

    • علاوة على ذلك ، يتم تعريف try!(..) في Rust بينما await!(future) لا يمكن أن يكون وسيصبح بدلاً من ذلك سحر مترجم.

    • لم يكن التخلص من try!(..) أمرًا سهلاً وأحدث خسائر اجتماعية. لا يبدو أن المرور بهذه المحنة مرة أخرى أمر جذاب.

  3. هذا من شأنه أن يستخدم بناء الجملة الكلي لأحد الأجزاء المركزية والمهمة من اللغة ؛ من الواضح أن هذا ليس من الدرجة الأولى.
  4. await!(future) صاخب ؛ على عكس await future تحتاج إلى كتابة !( ... ) .
  5. تتمحور واجهات برمجة تطبيقات Rust ، وخاصة المكتبة القياسية ، حول بناء جملة استدعاء الطريقة. على سبيل المثال ، من الشائع استخدام سلاسل عند التعامل مع Iterator s. على غرار await { future } و await future ، فإن بناء الجملة await!(future) سيجعل تسلسل الطريقة صعبًا ويؤدي إلى ارتباطات مؤقتة let . هذا أمر سيء لبيئة العمل وفي رأيي لسهولة القراءة.

Centril أود الموافقة ولكن هناك بعض الأسئلة المفتوحة. هل أنت متأكد من حوالي 1. ؟ إذا جعلناها "سحرية" ، فلن نجعلها أكثر سحرًا من خلال السماح لها بالإشارة إلى ماكرو دون إسقاطها ككلمة رئيسية؟

انضممت إلى Rust بعد فوات الأوان لتقييم المنظور الاجتماعي لـ 2. . تكرار الخطأ لا يبدو جذابة ولكن كما بعض الرموز لا يزال لم تحويلها من try! يجب أن يكون واضحا أنه كان الحل. هذا يثير السؤال عما إذا كنا نعتزم الحصول على async/await كحافز للانتقال إلى الإصدار 2018 أو بالأحرى التحلي بالصبر وعدم تكرار ذلك.

ميزتان مركزيتان أخريان تشبهان إلى حد كبير 3. : vec![] و format! / println! . الأول يرجع إلى حد كبير إلى عدم وجود بنية مربعة ثابتة afaik ، والأخيرة بسبب تكوين سلسلة التنسيق وعدم وجود تعبيرات مكتوبة بشكل مستقل. أعتقد أن هذه المقارنات أيضًا وضعت جزئيًا 4. في منظور آخر.

أنا أعارض أي بناء جملة لا يشبه إلى حد ما اللغة الإنجليزية. IE ، "انتظار x" يقرأ شيئًا مثل اللغة الإنجليزية. "x # !! @! &" لا. يقرأ "x.await" بشكل مثير للإعجاب مثل اللغة الإنجليزية ، لكنه لن يحدث عندما تكون x سطرًا غير تافه ، مثل استدعاء وظيفة العضو بأسماء طويلة ، أو مجموعة من طرق التكرار المتسلسلة ، إلخ.

وبشكل أكثر تحديدًا ، أنا أؤيد "الكلمة الرئيسية" ، حيث ربما تكون الكلمة الرئيسية await . لقد جئت من استخدام كل من coroutines TS C ++ و coroutines c # للوحدة ، وكلاهما يستخدم بناء جملة مشابه جدًا لذلك. وبعد سنوات من استخدامها في كود الإنتاج ، أعتقد أن معرفة مكان نقاط العائد الخاصة بك ، في لمحة ، أمر بالغ الأهمية . عندما تقوم بالقشط أسفل خط المسافة البادئة إذا كانت وظيفتك ، يمكنك انتقاء كل co_await / yield return في دالة مكونة من 200 سطر في غضون ثوانٍ ، دون تحميل معرفي.

نفس الشيء لا ينطبق على العامل النقطي مع "انتظار" بعد ذلك ، أو بعض صيغ "كومة الرموز" الأخرى اللاحقة للإصلاح.

أعتقد أن await هي عملية أساسية للتحكم في التدفق. يجب منحه نفس مستوى الاحترام مثل 'if و while و match و return . تخيل لو كان أي من هؤلاء مشغلين لما بعد الإصلاح - قراءة كود Rust ستكون كابوسًا. كما هو الحال مع حجتي في الانتظار ، حيث يمكنك تخطي خط المسافة البادئة لأي وظيفة Rust واختيار كل تدفق التحكم على الفور. هناك استثناءات ، لكنها استثناءات ، وليست شيئًا يجب أن نسعى إليه.

أنا أتفق معejmahler. يجب ألا ننسى الجانب الآخر من التطوير - مراجعة الكود. غالبًا ما يتم قراءة الملف ذي الكود المصدري ثم كتابته ، ومن ثم أعتقد أنه من الأسهل قراءته وفهمه ثم الكتابة. العثور على نقاط العائد مهم حقًا في مراجعة الكود. وأنا شخصياً سأصوت لـ Useful precedence .
أعتقد أن هذا:

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

أسهل في الفهم بدلاً من هذا:

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

تضمين التغريدة

Centril أود الموافقة ولكن هناك بعض الأسئلة المفتوحة. هل أنت متأكد من حوالي 1. ؟ إذا جعلناها "سحرية" ، فلن نجعلها أكثر سحرًا من خلال السماح لها بالإشارة إلى ماكرو دون إسقاطها ككلمة رئيسية؟

من الناحية الفنية؟ ربما. ومع ذلك ، يجب أن يكون هناك مبرر قوي للحالات الخاصة وفي هذه الحالة لا يبدو أن وجود السحر على السحر أمر غير مبرر.

هذا يثير السؤال عما إذا كنا نعتزم الحصول على async/await كحافز للانتقال إلى الإصدار 2018 أو بالأحرى التحلي بالصبر وعدم تكرار ذلك.

لا أعرف ما إذا كنا قد قلنا من قبل أننا نعتزم إنشاء نظام وحدة جديدة ، async / await ، و try { .. } ليكون حوافز ؛ لكن بغض النظر عن نيتنا هم كذلك ، وأعتقد أن هذا أمر جيد. نريد أن يبدأ الناس في النهاية في استخدام ميزات لغة جديدة لكتابة مكتبات اصطلاحية أفضل وأكثر.

هناك ميزتان مركزيتان أخريان تشبهان إلى حد كبير 3. : vec![] و format! / println! . السابق كثيرًا لأنه لا يوجد بناء محاصر مستقر ،

الأول موجود ، ومكتوب vec![1, 2, 3, ..] ، لتقليد التعبيرات الحرفية للصفيف ، على سبيل المثال [1, 2, 3, ..] .

تضمين التغريدة

يقرأ "x.await" بشكل مثير للإعجاب مثل اللغة الإنجليزية ، لكنه لن يحدث عندما تكون x سطرًا غير تافه ، مثل استدعاء وظيفة العضو بأسماء طويلة ، أو مجموعة من طرق التكرار المتسلسلة ، إلخ.

ما الخطأ في مجموعة من طرق التكرار المقيدة؟ هذا هو الصدأ الاصطلاحي الواضح.
ستعمل الأداة rustfmt أيضًا على تنسيق سلاسل الطريقة على أسطر مختلفة بحيث تحصل (مرة أخرى باستخدام match لإظهار تمييز بناء الجملة):

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

إذا كنت تقرأ .await كـ "ثم تنتظر" فإنها ستقرأ تمامًا ، على الأقل بالنسبة لي.

وبعد سنوات من استخدامها في كود الإنتاج ، أعتقد أن معرفة مكان نقاط العائد الخاصة بك ، في لمحة ، أمر بالغ الأهمية .

لا أرى كيف تنفي postfix await ذلك ، خاصة في تنسيق rustfmt أعلاه. علاوة على ذلك ، يمكنك كتابة:

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();

إذا كنت تتوهم ذلك.

في وظيفة مكونة من 200 سطر في غضون ثوانٍ ، بدون حمل معرفي.

بدون معرفة الكثير عن الوظيفة المحددة ، يبدو لي أن 200 LOC ربما ينتهك مبدأ المسؤولية الفردية ويفعل الكثير. الحل هو جعلها تعمل بشكل أقل وتقسيمها. في الواقع ، أعتقد أن هذا هو أهم شيء لقابلية الصيانة والقراءة.

أعتقد أن await هي عملية أساسية للتحكم في التدفق.

هكذا هو ? . في الواقع ، فإن كلا من await و ? كلاهما عمليتا تدفق تحكم فعالتان تقولان "استخراج القيمة خارج السياق". بمعنى آخر ، في السياق المحلي ، يمكنك تخيل أن هؤلاء المشغلين لديهم النوع await : impl Future<Output = T> -> T و ? : impl Try<Ok = T> -> T .

هناك استثناءات ، لكنها استثناءات ، وليست شيئًا يجب أن نسعى إليه.

والاستثناء هنا هو ? ؟

تضمين التغريدة

أنا أتفق معejmahler. يجب ألا ننسى الجانب الآخر من التطوير - مراجعة الكود. غالبًا ما يتم قراءة الملف ذي الكود المصدري ثم كتابته ، ومن ثم أعتقد أنه من الأسهل قراءته وفهمه ثم الكتابة.

يدور الخلاف حول ما هو الأفضل لسهولة القراءة وبيئة العمل.

أسهل في الفهم بدلاً من هذا:

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

هذه ليست الطريقة التي سيتم تنسيقها. تشغيله من خلال rustfmt تحصل على:

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

ejmahlerandreytkachenko وأنا أتفق معCentril هنا، فإن التغيير الأكبر (البعض قد يقول تحسن، وأنا لن) تكسب من بادئة بناء الجملة التي يتم تحفيز المستخدمين لتقسيم بياناتهم عبر خطوط متعددة لأن كل شيء آخر هو غير قابل للقراءة. هذا ليس Rust-y وقواعد التنسيق المعتادة تعوض عن ذلك في بناء جملة ما بعد الإصلاح. أنا أعتبر أيضًا أن نقطة العائد أكثر غموضًا في بناء جملة البادئة لأن await لا يتم وضعه فعليًا في نقطة الكود التي تعطيها ، بدلاً من ذلك.

إذا ذهبت بهذه الطريقة ، فلنجرّب من await كبديل لـ let بروح فكرة Keruspe لفرض ذلك حقًا. بدون ملحقات بناء الجملة الأخرى لأنها تبدو ممتدة.

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

لكن بالنسبة لأي من هؤلاء ، لا أرى فائدة كافية لشرح فقدان قابليتها للتركيب والمضاعفات في القواعد.

حسنًا ... هل من المستحسن أن يكون لديك شكل البادئة واللاحقة في الانتظار؟ أو مجرد شكل لاحقة؟

طريقة تسلسل المكالمات معا هي اصطلاحية على وجه التحديد عند الحديث عنها
التكرارات والخيارات إلى حد أقل بكثير ، ولكن بصرف النظر عن ذلك ، الصدأ
لغة حتمية أولا ولغة وظيفية ثانيا.

أنا لا أجادل في أن postfix غير مفهوم حرفيًا ، فأنا أجعله
حجة الحمل المعرفي التي تخفي عملية تدفق تحكم من الدرجة الأولى ،
تحمل نفس أهمية "العودة" ، يزيد العبء المعرفي عندما
مقارنة بوضعه بالقرب من بداية السطر - وأنا كذلك
جعل هذه الحجة على أساس سنوات من الخبرة في الإنتاج.

يوم السبت 19 يناير 2019 الساعة 11:59 صباحًا Mazdak Farrokhzad [email protected]
كتب:

HeroicKatora https://github.com/HeroicKatora

Centril https://github.com/Centril أود أن أوافق ولكن هناك
بعض الأسئلة المفتوحة. هل أنت متأكد من 1.؟ إذا جعلناها "سحرية" يمكن
نحن لا نجعلها أكثر سحرًا من خلال السماح لهذا بالإشارة إلى ماكرو بدون
إسقاطها ككلمة رئيسية؟

من الناحية الفنية؟ ربما. ومع ذلك ، يجب أن يكون هناك مبرر قوي ل
حالات خاصة وفي هذه الحالة لا يبدو أن السحر على السحر
مبرر.

هذا يطرح السؤال عما إذا كنا نعتزم أن يكون لدينا عدم التزامن / انتظار
حافزًا للانتقال إلى إصدار 2018 أو بالأحرى التحلي بالصبر وليس
كرر هذا.

لا أعرف ما إذا قلنا من قبل أننا نعتزم إنشاء نظام وحدة نمطية جديد ، غير متزامن /
انتظر ، وحاول {..} أن تكون حوافز ؛ ولكن بغض النظر عن نيتنا
هم كذلك ، وأعتقد أن هذا أمر جيد. نريد من الناس في النهاية
ابدأ في استخدام ميزات لغة جديدة لكتابة أفضل وأكثر اصطلاحات
المكتبات.

سمتان مركزيتان أخريان (imho) تشبهان إلى حد كبير 3 .: vec! [] والشكل! /
println !. السابق كثيرًا لأنه لا يوجد مستقر محاصر
أفيك البناء

السابق موجود ، ومكتوب vec! [1، 2، 3، ..] لتقليد المصفوفة
التعبيرات الحرفية ، مثل [1 ، 2 ، 3 ، ..].

Ejmahler https://github.com/ejmahler

يقرأ "x.await" بشكل مثير للإعجاب مثل اللغة الإنجليزية ، لكنه لن يحدث عندما يكون x هو a
خط غير تافه ، مثل استدعاء وظيفة عضو بأسماء طويلة ، أو مجموعة
طرق التكرار المقيدة ، إلخ.

ما الخطأ في مجموعة من طرق التكرار المقيدة؟ هذا واضح
اصطلاحي الصدأ.
ستعمل أداة rustfmt أيضًا على تنسيق سلاسل الطريقة على أسطر مختلفة لذلك أنت
الحصول على (مرة أخرى باستخدام المطابقة لإظهار تمييز بناء الجملة):

دع foo = alpha (). match؟ // أو alpha() match? ، alpha()#match? ، alpha().match!()?
.beta
.some_other_stuff (). تطابق؟
.even_more_stuff (). تطابق
.stuff_and_stuff () ،

إذا قرأت. انتظر كـ "ثم انتظر" فإنه يقرأ تمامًا ، على الأقل بالنسبة لي.

وبعد سنوات من استخدامها في كود الإنتاج ، أعتقد أن المعرفةحيث نقاط العائد الخاصة بك ، في لمحة ، أمر بالغ الأهمية .

لا أرى كيف أن انتظار postfix ينفي ذلك ، خاصةً في rustfmt
التنسيق أعلاه. علاوة على ذلك ، يمكنك كتابة:

دعونا foo = alpha (). match؟؛ let bar = foo.beta.some_other_stuff (). match؟؛ let baz = bar..even_more_stuff (). match؛ let quux = baz.stuff_and_stuff ()؛

إذا كنت تتوهم ذلك.

في وظيفة مكونة من 200 سطر في غضون ثوانٍ ، بدون حمل معرفي.

من دون معرفة الكثير عن وظيفة معينة ، يبدو لي
أن 200 LOC ربما ينتهك مبدأ المسؤولية الفردية ويفعل
كثير جدا. الحل هو جعلها تعمل بشكل أقل وتقسيمها. في الحقيقة، أنا
أعتقد أن هذا هو أهم شيء لقابلية الصيانة والقراءة.

أعتقد أن الانتظار هو عملية أساسية للتحكم في التدفق.

إذن؟ في الواقع ، انتظر و؟ كلاهما عمليات تدفق تحكم فعالة
التي تقول "استخراج قيمة من سياقها". بعبارة أخرى ، على المستوى المحلي
السياق ، يمكنك أن تتخيل أن هذه العوامل لها النوع انتظار: impl
مستقبل-> T و؟ : ضمني حاول-> T.

هناك استثناءات ، لكنها استثناءات ، ولا ينبغي علينا ذلك
يسعى من اجل.

والاستثناء هنا؟ ؟

andreytkachenko https://github.com/andreytkachenko

أنا أتفق مع ejmahler https://github.com/ejmahler . لا ينبغي لنا
ننسى الجانب الآخر من التطوير - مراجعة الكود. الملف مع شفرة المصدر هو
في كثير من الأحيان يتم قراءتها ثم كتابتها ، وبالتالي أعتقد أنه يجب أن يكون أسهل
اقرأ وفهم ثم اكتب.

يدور الخلاف حول ما هو الأفضل لسهولة القراءة و
بيئة العمل.

أسهل في الفهم بدلاً من هذا:

... اسمح للجسم: MyResponse = client.get ("https: // my_api") .send (). wait؟ .into_json (). wait ؟؛

هذه ليست الطريقة التي سيتم تنسيقها. تنسيق rustfmt الاصطلاحي
يكون:

اسمحوا الجسم: MyResponse = العميل
.get ("https: // my_api")
.إرسال()
.مباراة؟
.into_json ()
.مباراة؟؛

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/57640#issuecomment-455810497 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABGmeocFJpPKaypvQHo9LpAniGOUFrmzks5vE3kXgaJpZM4aBlba
.

ejmahler نحن نختلف إعادة. "إخفاء"؛ قدمت نفس الحجج WRT.

عامل التشغيل ? قصير جدًا ، وقد تم انتقاده في الماضي "لإخفاء" العائد. يتم قراءة رمز معظم الأوقات وليس كتابته. إعادة تسميته إلى ?! ستجعله أطول بمرتين وبالتالي يصعب التغاضي عنه.

ومع ذلك ، فقد استقرنا في النهاية على ? ومنذ ذلك الحين أعتقد أن النبوءة لم تتحقق.
بالنسبة لي ، فإن postfix await يتبع ترتيب القراءة الطبيعي (على الأقل لمتحدثي اللغات من اليسار إلى اليمين). على وجه الخصوص ، يتبع ترتيب تدفق البيانات.

ناهيك عن إبراز بناء الجملة: يمكن تمييز أي شيء متعلق بالانتظار بلون ساطع ، بحيث يمكن العثور عليه في لمحة. لذلك ، حتى لو كان لدينا رمز بدلاً من الكلمة الفعلية await ، فسيظل قابلاً للقراءة ويمكن العثور عليه في التعليمات البرمجية المميزة. مع ذلك ، ما زلت أفضل استخدام الكلمة await فقط لأسباب تتعلق بالإمساك - من الأسهل كتابة الكود لأي شيء ينتظرنا إذا استخدمنا الكلمة await بدلاً من الرمز مثل @ أو # ، التي يعتمد معناها على القواعد.

أنتم جميعا هذا ليس علم الصواريخ

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

postfix ... سهل القراءة للغاية ، يصعب تفويته في لمحة وبديهية للغاية نظرًا لأنك تقرأه بشكل طبيعي على أنه نوع الكود اللاحق أثناء انتظار نتيجة المستقبل لتصبح متاحة. لا توجد أسبقية / خدع ماكرو ضرورية ولا ضوضاء خطية إضافية من سيجيلات غير مألوفة ، لأن الجميع قد رأوا علامات الحذف من قبل.

(نعتذر إلىsolson)

@ ben0x539 هل هذا يعني أنه يمكنني الوصول إلى أحد أعضاء future()....start ؟ أو تنتظر نتيجة النطاق مثل range()..... ؟ وكيف تقصد بالضبط no precedence/macro shenanigans necessary and حيث أن علامة القطع حاليًا .. تتطلب أن تكون عامل تشغيل ثنائي أو أقواس على اليمين وهذا قريب جدًا في لمحة.

نعم ،؟ المشغل موجود. لقد أقرت بالفعل أن هناك
استثناءات. لكنه استثناء. الغالبية العظمى من تدفق التحكم في أي
يحدث برنامج الصدأ عن طريق الكلمات الأساسية البادئة.

يوم السبت ، 19 يناير 2019 الساعة 1:51 مساءً Benjamin Herr [email protected]
كتب:

أنتم جميعا هذا ليس علم الصواريخ

دع الجسم: MyResponse = client.get ("https: // my_api") .send () ...؟. into_json () ...؟؛

postfix ... سهل القراءة للغاية ، يصعب تفويته في لمحة وفائقة
بديهية نظرًا لأنك تقرأها بشكل طبيعي على أنها نوع رمز لاحق
بينما تنتظر نتيجة المستقبل لتصبح متاحة. لا
الأسبقية / الخدع الكلية ضرورية ولا ضوضاء خط إضافية من
سيجيلات غير مألوفة ، حيث أن الجميع قد رأوا علامات الحذف من قبل.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/57640#issuecomment-455818177 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABGmen354fhk7snYsANTfp5oOuDb4OLYks5vE5NSgaJpZM4aBlba
.

HeroicKatora يبدو ذلك مصطنعًا بعض الشيء لكن نعم بالتأكيد. قصدت أنه نظرًا لأنها عملية postfix ، مثل حلول postfix الأخرى المقترحة ، فإنها تتجنب الحاجة إلى أسبقية غير بديهية لـ await x? ، وهي ليست ماكرو.

تضمين التغريدة

نعم ،؟ المشغل موجود. لقد أقرت بالفعل بوجود استثناءات. لكنه استثناء.

هناك نوعان من أشكال التعبير يلائمان keyword expr ، وهما return expr و break expr . الأول أكثر شيوعًا من الأخير. لا يتم احتساب النموذج continue 'label منذ ذلك الحين ، في حين أنه تعبير ، فهو ليس بالصيغة keyword expr . إذن لديك الآن شكلين من أشكال التعبير الأحادي للكلمات الرئيسية البادئة الكاملة وصيغة تعبير أحادية لاحقة. قبل أن نأخذ في الاعتبار أن ? و await أكثر تشابهًا من await و return ، بالكاد أتصل بـ return/break expr قاعدة لـ ? لتكون استثناءً من.

تحدث الغالبية العظمى من تدفق التحكم في أي برنامج Rust عبر الكلمات الأساسية البادئة.

كما ذكرنا سابقًا ، break expr ليس كل هذا الشائع ( break; أكثر نموذجية و return; أكثر نموذجية وليست نماذج تعبير أحادية). ما تبقى هو return expr; s ولا يبدو واضحًا على الإطلاق أن هذا أكثر شيوعًا من match ، ? ، متداخل فقط if let حلقات else s و for . بمجرد أن يستقر try { .. } ، أتوقع أن يتم استخدام ? أكثر.

@ ben0x539 أعتقد أنه يجب علينا ... للأدوية المتغيرة ، بمجرد أن نكون مستعدين للحصول عليها

تعجبني حقًا فكرة الابتكار باستخدام بنية postfix هنا. يبدو الأمر أكثر منطقية مع التدفق وأتذكر مدى تحول الكود الأفضل عندما انتقلنا من البادئة try! إلى postfix ? . أعتقد أن هناك الكثير من الأشخاص الذين صنعوا التجربة في مجتمع Rust عن مقدار التحسينات التي تم إجراؤها على الكود.

إذا لم تعجبنا فكرة .await فأنا متأكد من أنه يمكن عمل بعض الإبداع للعثور على عامل postfix فعلي. أحد الأمثلة على ذلك هو استخدام ++ أو @ للانتظار.

:( لا أريد الانتظار بعد الآن.

الجميع مرتاح مع بناء الجملة الكلي ، يبدو أن معظم الأشخاص في هذا الموضوع الذين يبدأون بآراء أخرى ينتهي بهم الأمر إلى تفضيل بناء الجملة الكلي.

من المؤكد أنه سيكون "ماكروًا سحريًا" ولكن المستخدمين نادرًا ما يهتمون بالشكل الذي يبدو عليه توسيع الماكرو وبالنسبة لأولئك الذين يفعلون ذلك ، فمن السهل بما يكفي شرح الفروق الدقيقة في المستندات.

بناء جملة الماكرو المنتظم يشبه نوعًا ما فطيرة التفاح ، فهو الخيار المفضل الثاني للجميع ولكن نتيجة لذلك هو الخيار المفضل للعائلة [0]. الأهم من ذلك ، مثل مع المحاولة! يمكننا دائمًا تغييره لاحقًا. ولكن الأهم من ذلك ، أنه كلما أسرعنا في الاتفاق ، كلما بدأنا جميعًا في استخدامه فعليًا ونكون منتجين.

[0] (تمت الإشارة إليه في الدقيقة الأولى) https://www.ted.com/talks/kenneth_cukier_big_data_is_better_data/transcript؟language=ar

تطابق ، إذا ، إذا سمح ، بينما ، بينما اسمح ، ولأجل كلها تدفق تحكم شامل
التي تستخدم البادئات. التظاهر بالكسر والاستمرار هما تدفق التحكم الوحيد
الكلمات الرئيسية مضللة بشكل محبط.

يوم السبت 19 يناير 2019 الساعة 3:37 مساءً Yazad Daruvala [email protected]
كتب:

:( لا أريد الانتظار بعد الآن.

الجميع مرتاح مع بناء جملة الماكرو ، ومعظم الناس في هذا الموضوع ذلك
تبدأ بآراء أخرى ويبدو أن ينتهي بها الأمر لصالح بناء الجملة الكلي.

من المؤكد أنه سيكون "ماكروًا سحريًا" ولكن المستخدمين نادرًا ما يهتمون بماهية الماكرو
يبدو أن التوسيع وبالنسبة لأولئك الذين يفعلون ذلك ، فمن السهل بما يكفي شرح
فارق بسيط في المستندات.

بناء جملة الماكرو المنتظم هو نوع ما مثل فطيرة التفاح ، فهو الثاني للجميع
الخيار المفضل ولكن نتيجة الخيار المفضل للعائلة [0].
الأهم من ذلك ، مثل مع المحاولة! يمكننا دائمًا تغييره لاحقًا. لكن معظم
والأهم من ذلك ، أنه كلما أسرعنا في الاتفاق ، كلما بدأنا جميعًا
استخدمه بالفعل وكن منتجًا!

[0] (تمت الإشارة إليه في الدقيقة الأولى)
https://www.ted.com/talks/kenneth_cukier_big_data_is_better_data/transcript؟language=ar

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/57640#issuecomment-455824275 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABGmesz5_LfDKdcKn6zMO5uuSJs9lFiYks5vE6wygaJpZM4aBlba
.

mitsuhiko أوافق! يبدو Postfix أكثر ريفيًا بسبب السلاسل. أعتقد أن بناء الجملة fut@await الذي اقترحته هو خيار آخر مثير للاهتمام لا يبدو أنه يحتوي على العديد من الجوانب السلبية مثل المقترحات الأخرى. لست متأكدًا مما إذا كان الأمر بعيدًا جدًا وسيكون من الأفضل إصدار نسخة أكثر واقعية.

تضمين التغريدة

تطابق ، إذا ، إذا سمحت ، بينما ، بينما let ، و for كلها تدفق تحكم شامل يستخدم البادئات. إن التظاهر بالكسر والاستمرار هي الكلمات الرئيسية الوحيدة للتحكم في التدفق وهي مضللة بشكل محبط.

إنه ليس مضللاً على الإطلاق. القواعد ذات الصلة بهذه التركيبات هي تقريبًا:

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 ","?;

هنا ، النماذج هي if/while expr block ، for pat in expr block ، و match expr { pat0 => expr0, .., patn => exprn } . هناك كلمة أساسية تسبق ما يلي في كل هذه الأشكال. أعتقد أن هذا ما تعنيه ب "استخدام البادئات". ومع ذلك ، فهذه كلها نماذج كتلة وليست عوامل بادئة أحادية. لذلك فإن المقارنة مع await expr مضللة نظرًا لعدم وجود تناسق أو قاعدة يمكن التحدث عنها. إذا كنت تريد التوافق مع نماذج الحظر ، فقم بمقارنتها بـ await block ، وليس await expr .

mitsuhiko أوافق! يبدو Postfix أكثر ريفيًا بسبب السلاسل.

الصدأ ثنائي. وهو يدعم كل من النهج الحتمية والوظيفية. وأعتقد أنه جيد ، لأنه في حالات مختلفة ، يمكن أن يكون كل واحد منهم أكثر ملاءمة.

لا أدري، لا أعرف. أشعر أنه سيكون من الرائع أن يكون لديك كلاهما:

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

بعد أن استخدمت Scala لفترة من الوقت ، كنت مغرمًا جدًا بالعديد من الأشياء التي تعمل من هذا القبيل أيضًا. سيكون من الجيد وجود match و if في مواقع postfix في Rust.

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

ملخص حتى الآن

مصفوفات الخيار:

مصفوفة ملخص للخيارات (باستخدام @ كعنصر سيجيل ، ولكن يمكن أن يكون في الغالب أي شيء):

| الاسم | Future<T> | Future<Result<T, E>> | Result<Future<T>, E> |
| --- | --- | --- | --- |
| الإعداد المسبق | - | - | - |
| ماكرو الكلمات الرئيسية | await!(fut) | await!(fut)? | await!(fut?) |
| وظيفة الكلمات الرئيسية | await(fut) | await(fut)? | await(fut?) |
| أسبقية مفيدة | await fut | await fut? | await (fut?) |
| أسبقية واضحة | await fut | await? fut | await fut? |
| بوستفيكس | - | - | - |
| الجبهة الوطنية مع الكلمات الرئيسية | fut(await) | fut(await)? | fut?(await) |
| حقل الكلمات الرئيسية | fut.await | fut.await? | fut?.await |
| طريقة الكلمات الرئيسية | fut.await() | fut.await()? | fut?.await() |
| ماكرو الكلمات الرئيسية Postfix | fut.await!() | fut.await!()? | fut?.await!() |
| مسافة الكلمات الرئيسية | fut await | fut await? | fut? await |
| كلمة سيجيل | fut@await | fut@await? | fut?@await |
| سيجيل | fut@ | fut@? | fut?@ |

لا يمكن أن تكون علامة Sigil الخاصة بـ "Sigil Keyword" # ، إذ لا يمكنك فعل ذلك بمستقبل يسمى r . ... حيث لن يضطر sigil مثل أول ما يقلقني.

المزيد من الاستخدام الواقعي (PM me other _real_ use case with Multiple await على urlo وسأضيفها ):

| الاسم | (reqwest) Client |> Client::get |> RequestBuilder::send |> await |> ? |> Response::json | > ? |
| --- | --- |
| الإعداد المسبق | - |
| ماكرو الكلمات الرئيسية | await!(client.get("url").send())?.json()? |
| وظيفة الكلمات الرئيسية | await(client.get("url").send())?.json()? |
| أسبقية مفيدة | (await client.get("url").send()?).json()? |
| أسبقية واضحة | (await? client.get("url").send()).json()? |
| بوستفيكس | - |
| الجبهة الوطنية مع الكلمات الرئيسية | client.get("url").send()(await)?.json()? |
| حقل الكلمات الرئيسية | client.get("url").send().await?.json()? |
| طريقة الكلمات الرئيسية | client.get("url").send().await()?.json()? |
| ماكرو الكلمات الرئيسية Postfix | client.get("url").send().await!()?.json()? |
| مسافة الكلمات الرئيسية | client.get("url").send() await?.json()? |
| كلمة سيجيل | client.get("url").send()@await?.json()? |
| سيجيل | client.get("url").send()@?.json()? |

ملاحظة التحرير: لقد أُشير إلي أنه قد يكون من المنطقي أن تقوم Response::json بإرجاع Future ، حيث ينتظر send أمر الإدخال / الإخراج الصادر و json (أو تفسير آخر للنتيجة) ينتظر الإدخال / الإخراج الوارد. سأترك هذا المثال كما هو ، على الرغم من أنني أعتقد أنه من المفيد إظهار أن مشكلة التسلسل تنطبق حتى مع وجود IO واحد فقط في انتظار نقطة في التعبير.

يبدو أن هناك إجماعًا تقريبيًا على أن خيارات البادئة ، الأسبقية الواضحة (إلى جانب السكر await? ) هي الأكثر تفضيلاً. ومع ذلك ، فقد تحدث العديد من الأشخاص لصالح حل postfix ، من أجل جعل التسلسل على النحو الوارد أعلاه أسهل. على الرغم من أن اختيار البادئة يحظى بإجماع تقريبي ، يبدو أنه لا يوجد إجماع حول أفضل حل لما بعد الإصلاح. تؤدي جميع الخيارات المقترحة إلى ارتباك سهل (يتم تخفيفه عن طريق تمييز الكلمات الرئيسية):

  • Fn باستخدام Keyword => استدعاء fn باستخدام وسيطة تسمى await
  • حقل الكلمات الرئيسية => حقل الوصول
  • طريقة الكلمات الرئيسية => استدعاء الأسلوب
  • ماكرو (بادئة أو postfix) => هل await كلمة رئيسية أم لا؟
  • مسافة الكلمات الرئيسية => يكسر التجميع في سطر واحد (أفضل من عدة أسطر؟)
  • Sigil => يضيف sigils جديدًا إلى لغة يُنظر إليها بالفعل على أنها ثقيلة sigil

اقتراحات أخرى أكثر جذرية:

  • اسمح لكل من البادئة (الأسبقية الواضحة) و "الحقل" بعد الإصلاح (يمكن تطبيق هذا على المزيد من الكلمات الرئيسية مثل match ، if ، إلخ في المستقبل لجعل هذا نمطًا عامًا ، ولكنه غير ضروري إضافة لهذا النقاش) [[مرجع] (https://github.com/rust-lang/rust/issues/57640#issuecomment-455827164)]
  • أنماط await لحل العقود الآجلة (بدون تسلسل على الإطلاق) [[مرجع] (https://github.com/rust-lang/rust/issues/57640)]
  • استخدم عامل تشغيل البادئة مع السماح بتأخيرها [[المرجع] (https://github.com/rust-lang/rust/issues/57640#issuecomment-455782394)]

الاستقرار باستخدام ماكرو كلمة رئيسية await!(fut) هو بالطبع متوافق مع المستقبل بشكل أساسي مع كل ما سبق ، على الرغم من أن هذا يتطلب جعل الماكرو يستخدم كلمة رئيسية بدلاً من معرف عادي.

إذا كان لدى شخص ما مثال في الغالب من الواقعية يستخدم await في سلسلة واحدة ، فأنا أحب أن أراه ؛ لم يشارك أحد حتى الآن. ومع ذلك ، فإن postfix await مفيد أيضًا حتى إذا لم تكن بحاجة إلى await أكثر من مرة في سلسلة ، كما هو موضح في مثال reqwest.

إذا فاتني شيئًا ملحوظًا فوق هذا التعليق الملخص ، فقم برؤيتي على urlo وسأحاول إضافته. (على الرغم من أنني

أنا شخصياً أؤيد من الناحية التاريخية الكلمات الأساسية البادئة ذات الأسبقية الواضحة. ما زلت أعتقد أن الاستقرار باستخدام ماكرو كلمة رئيسية await!(fut) سيكون مفيدًا لجمع معلومات من العالم الحقيقي حول مكان حدوث الانتظار في حالات استخدام العالم الحقيقي ، وسيظل يسمح لنا بإضافة بادئة غير ماكرو أو خيار postfix لاحقًا .

ومع ذلك ، أثناء كتابة الملخص أعلاه ، بدأت في الإعجاب بـ "حقل الكلمات الرئيسية". تبدو "الكلمة الرئيسية للمسافة" رائعة عند تقسيمها على عدة أسطر:

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

ولكن في سطر واحد ، يؤدي ذلك إلى حدوث فاصل محرج يقوم بتجميع التعبير بشكل سيئ: client.get("url").send() await?.json()? . ومع ذلك ، مع حقل الكلمات الرئيسية ، تبدو جيدة في كلا الشكلين: client.get("url").send().await?.json()?

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

على الرغم من أنني أفترض أن "طريقة الكلمات الرئيسية" ستتدفق بشكل أفضل ، لأنها إجراء. يمكننا أن نجعلها طريقة "حقيقية" على Future إذا أردنا:

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

(يعني extern "rust-await" بالطبع كل السحر المطلوب للقيام بالانتظار بالفعل ولن يكون في الواقع fn حقيقيًا ، سيكون موجودًا بشكل أساسي لأن البنية تبدو وكأنها طريقة ، إذا كانت كلمة رئيسية الطريقة المستخدمة.)

السماح بكل من البادئة (الأسبقية الواضحة) و "الحقل" بعد الإصلاح ...

إذا تم تحديد أي بناء جملة postfix (بغض النظر عما إذا كان معًا أو بدلاً من البادئة الأولى) ، فسيكون ذلك بالتأكيد حجة للمناقشة المستقبلية: لدينا الآن كلمات رئيسية تعمل في كل من تدوين البادئة و postfix ، على وجه التحديد لأنه في بعض الأحيان يكون واحد أفضل من والآخر ، لذلك ربما يمكننا السماح بكلتا الحالتين حيثما يكون ذلك منطقيًا ، وزيادة مرونة بناء الجملة ، مع توحيد القواعد. ربما تكون فكرة سيئة ، ربما سيتم رفضها ، لكنها بالتأكيد مناقشة ستجري في المستقبل ، إذا تم استخدام تدوين postix لـ await .

أعتقد أن هناك فرصة ضئيلة جدًا لأن يتم قبول بناء الجملة الذي لا يتضمن سلسلة الأحرف المنتظرة في بناء الجملة هذا.

: +1:


فكرة عشوائية راودتني بعد رؤية مجموعة من الأمثلة هنا ( .await هي أنه من الصعب جدًا رؤيتها † ، ولكن بالنظر إلى هذه الأشياء المنتظرة عادة عرضة للخطأ، والحقيقة أنه في كثير من الأحيان .await? يساعد على لفت الانتباه اضافية لذلك على أي حال.

† إذا كنت تستخدم شيئًا لا يبرز الكلمات الرئيسية


تضمين التغريدة

أنا أعارض أي بناء جملة لا يشبه إلى حد ما اللغة الإنجليزية

شيء مثل request.get().await يقرأ تمامًا بالإضافة إلى شيء مثل body.lines().collect() . في "مجموعة من طرق التكرار المقيدة" ، أعتقد أن قراءة _prefix_ في الواقع أسوأ ، حيث يجب أن تتذكر أنهم قالوا "انتظر" في البداية ، ولا تعرف أبدًا متى تسمع شيئًا ما إذا كان سيحدث الانتظار ، كيندا مثل جملة مسار الحديقة .

وبعد سنوات من استخدامها في كود الإنتاج ، أعتقد أن معرفة مكان نقاط العائد الخاصة بك ، في لمحة ، أمر بالغ الأهمية. عندما تقوم بالقشط لأسفل على خط المسافة البادئة إذا كانت وظيفتك ، يمكنك انتقاء كل عائد co_await / العائد في وظيفة مكونة من 200 سطر في غضون ثوانٍ ، دون تحميل معرفي.

هذا يعني أنه لا يوجد أبدًا أي تعبير داخلي ، وهو قيد لن أؤيده مطلقًا ، نظرًا لطبيعة Rust الموجهة للتعبير. وعلى الأقل مع await لـ C # ، فمن المعقول تمامًا أن يكون لديك CallSomething(argument, await whatever.Foo() .

نظرًا لأن async _will_ يظهر في منتصف التعبيرات ، فأنا لا أفهم سبب سهولة رؤيته في البادئة مقارنةً بـ postfix.

يجب أن يُمنح نفس المستوى من الاحترام مثل "if ، while ، match ، and return. تخيل لو كان أي من هؤلاء مشغلين لما بعد الإصلاح - قراءة كود Rust ستكون كابوسًا.

returncontinue و break ) و while تُعتبر عديمة الجدوى للتسلسل ، حيث أنها تُرجع دائمًا ! و () . وبينما حذفت لسبب ما for ، رأينا رمزًا مكتوبًا على ما يرام باستخدام .for_each() بدون تأثيرات سيئة ، خاصة في الحرير الصناعي .

ربما نحتاج إلى التصالح مع حقيقة أن async/await ستكون ميزة لغة رئيسية. سيظهر في جميع أنواع التعليمات البرمجية. سوف ينتشر في النظام البيئي - في بعض الأماكن سيكون شائعًا مثل ? . سيتعين على الناس تعلمها.

وبالتالي ، قد نرغب في التركيز بوعي على كيفية شعور اختيار بناء الجملة بعد استخدامه لفترة طويلة بدلاً من الشعور به في البداية.

نحتاج أيضًا إلى فهم أنه بقدر ما تذهب بنيات التحكم في التدفق ، فإن await هو نوع مختلف من الحيوانات. يمكن فهم التركيبات مثل return و break و continue وحتى yield بشكل حدسي بدلالة jmp . عندما نرى هذه ، فإن أعيننا ترتد عبر الشاشة لأن تدفق التحكم الذي نهتم به يتحرك في مكان آخر. ومع ذلك ، بينما يؤثر await على تدفق التحكم في الجهاز ، فإنه لا يحرك تدفق التحكم المهم لأعيننا وفهمنا البديهي للكود.

لا نميل إلى ربط المكالمات غير المشروطة بـ return أو break لأن ذلك لا معنى له. لأسباب مماثلة ، لا نميل إلى تغيير قواعد الأسبقية لدينا لاستيعابها. هذه العوامل لها أسبقية منخفضة. يأخذون كل شيء إلى اليمين ويعيدونه في مكان ما ، وينهون التنفيذ داخل تلك الوظيفة أو الكتلة. ومع ذلك ، يريد عامل التشغيل await أن يتم تقييده. إنه جزء لا يتجزأ من تعبير وليس نهايته.

بعد أن نظرت في المناقشة والأمثلة في هذا الموضوع ، تركت لدي إحساس قضم أننا سنعيش لنأسف لقواعد الأسبقية المفاجئة.

يبدو أن مرشح الحصان المطارد يذهب بـ await!(expr) في الوقت الحالي ويأمل أن يتم عمل شيء أفضل لاحقًا قبل قراءة ملاحظات Centril ، ربما كنت ? ، والتي تحظى بشعبية كبيرة وناجحة على نطاق واسع. إن استخدام صيغة نعلم أنها ستخيب آمالنا هو في الواقع مجرد إضافة ديون تقنية.

في وقت مبكر من هذا الموضوع ، أشار withoutboats إلى أن أربعة خيارات موجودة فقط تبدو قابلة للتطبيق. من بين هؤلاء ، من المرجح أن تجعلنا صيغة postfix expr await سعداء على المدى الطويل. هذا التركيب اللغوي لا يخلق مفاجآت غريبة. لا يجبرنا على إنشاء نسخة بادئة من عامل التشغيل ? . إنه يعمل بشكل جيد مع طريقة التسلسل ولا يفكك تدفق التحكم من اليسار إلى اليمين. عاملنا الناجح ? بمثابة سابقة لمشغل postfix ، و await يشبه ? في الممارسة أكثر مما يشبه return ، break أو yield . على الرغم من أن عامل التشغيل غير الرمزي postfix قد يكون جديدًا في Rust ، فإن استخدام async/await سيكون واسع الانتشار بما يكفي لجعله مألوفًا بسرعة.

في حين أن جميع الخيارات الخاصة ببنية ما بعد الإصلاح تبدو قابلة للتطبيق ، فإن expr await لها بعض المزايا. يوضح هذا التركيب أن await كلمة رئيسية ، مما يساعد على التأكيد على تدفق التحكم السحري. مقارنة بـ expr.await ، expr.await() expr.await! ، expr.await!() ، وما إلى ذلك ، يتجنب هذا الاضطرار إلى توضيح أن هذا يبدو وكأنه حقل / طريقة / ماكرو ، ولكن في الحقيقة ليس في هذه الحالة الخاصة. سنعتاد جميعًا على فاصل المسافات هنا.

تهجئة await كـ @ أو استخدام رمز آخر لا يسبب مشاكل في التحليل أمر جذاب. إنها بالتأكيد عامل مهم بما يكفي لتبرير ذلك. ولكن إذا كان لابد من تهجئته في النهاية await ، فسيكون ذلك جيدًا. طالما أنه في وضع postfix.

كما ذكر أحدهم أمثلة _real_ ... أحتفظ (وفقًا لـ tokei) بقاعدة كود 23858 لصدأ خط غير متزامن بشكل كبير ويستخدم العقود الآجلة 0.1 await (أعلم أنه تجريبي للغاية). دعنا نذهب (منقح) في استكشاف الكهوف (لاحظ أن كل شيء تم تشغيله من خلال 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)
}

الآن دعونا نحول هذا إلى البديل الأكثر شيوعًا للبادئة ، الأسبقية الواضحة مع السكر. لأسباب واضحة ، لم يتم تشغيل هذا من خلال 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)
}

أخيرًا ، دعنا نحول هذا إلى متغير postfix المفضل لدي ، "postfix field".

// 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)
}

بعد هذا التمرين ، أجد عددًا من الأشياء.

  • أنا الآن بقوة ضد await? foo . يبدو جيدًا بالنسبة للتعبيرات البسيطة ، لكن ? يشعر بالضياع في التعبيرات المعقدة. إذا كان يجب علينا عمل بادئة ، فأنا أفضل الحصول على أسبقية "مفيدة".

  • يقودني استخدام تدوين postfix إلى ضم العبارات وتقليل ارتباطات السماح غير الضرورية.

  • يقودني استخدام ترميز postfix _field_ إلى تفضيل .await? بشدة للظهور في سطر الشيء الذي ينتظره بدلاً من السطر الخاص به في لغة rustfmt.

أنا أقدر تدوين الحذف بعد الإصلاح "..." أعلاه ، لإيجازه ورمزيًا في اللغة الإنجليزية التي تمثل وقفة تحسباً لشيء آخر. (مثل كيفية عمل السلوك غير المتزامن!) ، فهو أيضًا يترابط معًا جيدًا.

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

أشك في أن أي خيارات أخرى ستكون موجزة وذات مغزى بصريًا.

أ

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

ب

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

هل يجب اعتبار أن يكون لديك سلاسل طويلة من الانتظار هو شكل جيد؟

لماذا لا تستخدم أدوات التجميع العادية Future ؟

في الواقع ، لا تُترجم بعض التعبيرات جيدًا إلى سلاسل انتظار إذا كنت ترغب في الحصول على سلوكيات احتياطية عند الفشل.

أنا شخصياً أعتقد أن هذا:

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

value.unwrap()

يقرأ بشكل طبيعي أكثر بكثير من هذا:

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()

لا يزال لدينا القوة الكاملة للعقود الآجلة بدون تكلفة في أيدينا ، بعد كل شيء.

اعتبر ذلك.

EyeOfPython أود أن @ في future@await . يمكننا كتابة future~await ، حيث يعمل ~ كواصلة شبه ، ويمكن أن يعمل مع أي عوامل لاحقة محتملة.

تم استخدام الواصلة - بالفعل كعامل ناقص وعامل سالب. لم تعد جيدة. ولكن تم استخدام ~ للإشارة إلى كائنات الكومة في Rust ، وبخلاف ذلك لم يتم استخدامه في أي لغات برمجة. من شأنه أن يعطي ارتباكات أقل للأشخاص من لغات أخرى.

earthengine فكرة جيدة ، ولكن ربما استخدم فقط future~ مما يعني انتظار المستقبل ، حيث تعمل ~ مثل الكلمة الرئيسية await . (مثل ? الرمز

المستقبل | مستقبل النتيجة | نتيجة المستقبل
- | - | -
المستقبل ~ | المستقبل ~؟ | المستقبل؟ ~

العقود الآجلة المقيدة أيضًا مثل:

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

ألقيت نظرة على كيفية تنفيذ Go للبرمجة غير المتزامنة ووجدت شيئًا مثيرًا للاهتمام هناك. أقرب بديل للعقود الآجلة في Go هي القنوات. وبدلاً من await أو بناء جملة صراخ آخر لانتظار القيم ، توفر قنوات Go فقط عامل تشغيل <- لهذا الغرض. بالنسبة لي يبدو نظيفًا ومباشرًا. في السابق ، غالبًا ما رأيت كيف يثني الناس على Go من أجل بناء الجملة البسيط والمرافق غير المتزامنة الجيدة ، لذلك من المؤكد أن تعلم شيئًا ما من تجربته فكرة جيدة.

للأسف ، لا يمكننا الحصول على نفس البنية بالضبط لأن هناك الكثير من الأقواس الزاوية في شفرة مصدر Rust أكثر من Go ، غالبًا بسبب الأدوية الجنيسة. هذا يجعل المشغل <- دقيقًا حقًا وليس من الجيد العمل معه. عيب آخر هو أنه يمكن أن ينظر إليه على أنه معاكس لـ -> في توقيع الوظيفة وليس هناك سبب لاعتباره من هذا القبيل. وهناك عيب آخر وهو أن <- sigil كان يُقصد به أن يتم تنفيذه كـ placement new ، لذلك قد يخطئ الناس في تفسيره.

لذلك ، بعد بعض التجارب مع بناء الجملة توقفت عند <-- sigil:

let output = <-- future;

في async Context <-- بسيط جدًا ، على الرغم من أنه أقل من <- . ولكنها بدلاً من ذلك توفر ميزة كبيرة على <- بالإضافة إلى البادئة await - إنها تلعب بشكل جيد مع المسافة البادئة.

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);
}

بالنسبة لي ، يبدو هذا العامل أكثر تميزًا وأسهل في تحديده من await (والذي يشبه إلى حد كبير أي كلمة رئيسية أخرى أو متغير في سياق الكود). لسوء الحظ ، يبدو على Github أنحف مما هو عليه في محرر الكود الخاص بي ، لكنني أعتقد أن هذا ليس قاتلاً ويمكننا التعايش مع ذلك. حتى إذا شعر شخص ما بعدم الارتياح ، فإن تمييز البنية المختلفة أو الخط الأفضل (خاصة مع الأحرف المزدوجة) سيحل جميع المشكلات.

سبب آخر للإعجاب بهذا النحو هو أنه يمكن التعبير عنه من الناحية المفاهيمية على أنه "شيء لم يسبق له مثيل". اتجاه السهم من اليمين إلى اليسار هو عكس الاتجاه الذي نقرأ به النص ، مما يتيح لنا وصفه بأنه "شيء يأتي من المستقبل". يشير الشكل الطويل لعامل التشغيل <-- أيضًا إلى أن "بعض العمليات الدائمة تبدأ". يمكن أن يرمز قوس الزاوية والشرطتان الموجهتان من future إلى "الاقتراع المستمر". وما زلنا قادرين على قراءته على أنه "انتظار" كما كان من قبل.
ليس نوعا من التنوير ، ولكن قد يكون ممتعا.


أهم شيء في هذا الاقتراح هو أن طريقة التسلسل المريحة ستكون ممكنة أيضًا. فكرة مشغل البادئة المتأخرة التي اقترحتها سابقًا مناسبة جدًا هنا. وبهذه الطريقة سيكون لدينا أفضل ما في عالم البادئة و postfix await بناء الجملة. آمل حقًا أن يتم تقديم بعض الإضافات المفيدة التي أردتها شخصيًا في مناسبات عديدة من قبل: تأخر إلغاء الإشارة وتأخير بناء جملة النفي .

من خلال ، لست متأكدًا مما إذا كانت الكلمة المتأخرة مناسبة هنا ، فربما يجب علينا تسميتها بشكل مختلف.

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

let not_empty = some_vec.!is_empty();

let deref = value.*as_ref();

تبدو أسبقية عامل التشغيل واضحة جدًا: من اليسار إلى اليمين.

آمل أن يقلل بناء الجملة هذا من الحاجة إلى كتابة الدالات is_not_* التي يكون الغرض منها فقط نفي وإرجاع خاصية bool . وستكون التعبيرات المنطقية / dereference في بعض الحالات أكثر نظافة عند استخدامها.


أخيرًا ، قمت بتطبيقه على أمثلة من العالم الحقيقي تم نشرها بواسطة mehcode وأحب كيف <-- بشكل مناسب على وظيفة async داخل سلاسل استدعاء الطريقة. على العكس من ذلك ، فإن postfix 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)
}

بعد كل شيء: هذا هو النحو الذي أريد استخدامه.

تضمين التغريدة

هل يجب اعتبار أن يكون لديك سلاسل طويلة من الانتظار هو شكل جيد؟ لماذا لا تستخدم أدوات دمج المستقبل العادية؟

لست متأكدًا مما إذا كنت لم أسيء فهمك ولكن هذه ليست حصرية ، فلا يزال بإمكانك استخدام أدوات الدمج أيضًا

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

ولكن لكي يعمل هذا ، يجب كتابة جملة and_then على FnOnce(T) -> impl Future<_> ، وليس فقط على FnOnce(T) -> U . القيام combinators بالسلاسل على المستقبل والنتيجة يعمل فقط نظيفة دون paranthesis في لواحق:

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

في هذا المنشور ، سأركز على مسألة أسبقية المشغل في حالة ما بعد الإصلاح. بقدر ما أستطيع أن أقول ، لدينا ثلاثة بدائل قابلة للتطبيق والتي تعمل على الأقل إلى حد ما ، كل منها يمثله تعبير واحد بعد الإصلاح له هذه الأسبقية في البنية الحالية. أشعر بقوة أنه لا ينبغي لأي من المقترحات تغيير أسبقية المشغل الحالي.

  • طريقة استدعاء ( future.await() )
  • تعبير الحقل ( future.await )
  • استدعاء الوظيفة ( future(await) )

الاختلافات في الوظائف صغيرة نوعًا ما ولكنها موجودة. لاحظ أنني سأقبل كل هذه الأمور ، هذا في الغالب هو ضبط دقيق. لإظهارهم جميعًا ، نحتاج إلى بعض الأنواع. من فضلك لا تعلق على اختراع المثال ، فهذه هي النسخة الأكثر ضغطًا والتي تظهر جميع الاختلافات مرة واحدة.

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)
  • طريقة استدعاء: foo.member.await()(42)
    يرتبط بشدة ، لذلك لا يوجد ارتباط على الإطلاق
  • العضو: (foo.member.await)(42)
    يحتاج إلى ربط حول النتيجة المنتظرة عندما يكون هذا قابلاً للاستدعاء ، وهذا يتفق مع وجود عنصر قابل للاستدعاء كعضو ، وإلا فإن الخلط مع استدعاء وظيفة العضو. يشير هذا أيضًا إلى أنه يمكن للمرء التدمير باستخدام الأنماط: let … { await: value } = foo.member; value(42) بطريقة ما؟
  • استدعاء الوظيفة: (foo.member)(await)(42)
    يحتاج إلى الربط من أجل التدمير (ننقل العضو) لأنه يتصرف مثل استدعاء الوظيفة.

تبدو جميعها متشابهة عندما لا ندمر بنية إدخال من خلال حركة عضو ، ولا نسمي النتيجة بأنها قابلة للاستدعاء لأن فئات الأسبقية الثلاثة هذه تأتي مباشرة بعد بعضها البعض. كيف نريد أن تتصرف العقود الآجلة؟

أفضل طريقة موازية لاستدعاء الأسلوب يجب أن تكون مجرد استدعاء طريقة أخرى يأخذ self .

أفضل مواز للعضو هو الهيكل الذي يحتوي فقط على العضو الضمني await وبالتالي يتم تدميره بالانتقال من هذا ، وهذه الحركة تنتظر المستقبل ضمنيًا. هذا واحد يشعر وكأنه أقل نعي.

التوازي للوظيفة هو استدعاء هو سلوك الإغلاق. أنا أفضل هذا الحل باعتباره أنظف إضافة إلى مجموعة اللغة (كما هو الحال في بناء الجملة ، فإن أفضل ما يوازي إمكانيات النوع) ولكن هناك بعض النقاط الإيجابية لاستدعاء الطريقة و .await ليست أطول من غيرها.

HeroicKatora يمكننا وضع حالة خاصة .await في libsyntax للسماح لـ foo.await(42) لكن هذا سيكون غير متسق / مخصص. ومع ذلك ، يبدو (foo.await)(42) صالحًا للخدمة لأنه بينما توجد عمليات إغلاق مخرجات العقود الآجلة ، فمن المحتمل ألا تكون شائعة. وبالتالي ، إذا قمنا بالتحسين للحالة الشائعة ، فإن عدم الحاجة إلى إضافة () إلى .await المحتمل أن يفوز بالميزان.

@ Centril أوافق ، الاتساق مهم. عندما أرى نوعين من السلوكيات المتشابهة ، أود أن أستنتج الآخرين من خلال التوليف. لكن هنا يبدو .await محرجًا ، لا سيما مع المثال أعلاه الذي يوضح بوضوح أنه يوازي التدمير الضمني (إلا إذا كان بإمكانك العثور على صيغة مختلفة حيث تحدث هذه التأثيرات؟). عندما أرى التدمير ، أتساءل على الفور عما إذا كان بإمكاني استخدام هذا مع روابط السماح وما إلى ذلك ، ومع ذلك ، لن يكون هذا ممكنًا لأننا قمنا بتدمير النوع الأصلي الذي لا يحتوي على مثل هذا العضو أو خاصة عندما يكون النوع هو impl Future<Output=F> (ملاحظة جانبية غير ذات صلة في الغالب: إن القيام بهذا العمل سيعيدنا إلى بادئة بديلة await _ = بدلاً من let _ = ، funnily¹).

هذا لا يمنعنا من استخدام بناء الجملة في حد ذاته ، أعتقد أنه يمكنني التعامل مع تعلمه وإذا اتضح أنه آخر ما سأستخدمه بقوة ، لكن يبدو أنه نقطة ضعف واضحة.


¹ قد يكون هذا متسقًا مع السماح بـ ? خلال السماح بـ ? خلف الأسماء في النمط

  • await value? = failing_future();

لمطابقة الجزء Ok من Result . يبدو هذا مثيرًا للاستكشاف في سياقات أخرى أيضًا ولكن خارج الموضوع. سيؤدي أيضًا إلى مطابقة صياغة البادئة واللاحقة لـ await في نفس الوقت.

هذا لا يمنعنا من استخدام بناء الجملة في حد ذاته ، أعتقد أنه يمكنني التعامل مع تعلمه وإذا اتضح أنه آخر ما سأستخدمه بقوة ، لكن يبدو أنه نقطة ضعف واضحة.

أعتقد أن كل حل سيكون له بعض العيوب في بعض الأبعاد أو الحالة. التناسق ، بيئة العمل ، قابلية التسلسل ، سهولة القراءة ، ... هذا يجعل الأمر مسألة تتعلق بالدرجة ، أهمية الحالات ، الملاءمة لرمز الصدأ وواجهات برمجة التطبيقات النموذجية ، إلخ.

في حالة كتابة المستخدم foo.await(42) ...

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

    foo.closure(42);
}

... نحن نقدم بالفعل تشخيصات جيدة:

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

يبدو أن التغيير والتبديل في هذا لملاءمة foo.await(42) ممكن التحقيق. في الواقع ، بقدر ما أستطيع أن أرى ، نحن نعلم أن المستخدم ينوي (foo.await)(42) عند كتابة foo.await(42) لذلك يمكن أن يكون cargo fix ed في MachineApplicable بطريقة. في الواقع ، إذا استقرنا على foo.await لكن لا نسمح بـ foo.await(42) أعتقد أنه يمكننا حتى تغيير الأسبقية لاحقًا إذا احتجنا إلى ذلك لأن foo.await(42) لن يكون قانونيًا في البداية.

ستعمل عمليات تداخل أخرى (على سبيل المثال ، مستقبل نتيجة الإغلاق - وليس أن هذا سيكون شائعًا):
`` صدأ
هيكلة الإغلاق fn _foo () -> النتيجة <()، ()> {
let foo: HasClosure <_ i = "27"> = HasClosure {اغلاق: Ok (| x | {})}؛

foo.closure?(42);

Ok(())

}

على سبيل المثال ، مستقبل نتيجة الإغلاق - لا يعني أن هذا سيكون شائعًا

اللاحقة الإضافية ? عامل التشغيل تجعل هذا الأمر واضحًا بدون أي تعديلات على البنية - في أي من أمثلة ما بعد الإصلاح. لا حاجة للتغيير والتبديل . المشاكل فقط لكون .member حقلاً بشكل واضح والحاجة إلى إتلاف الحركة للتطبيق أولاً. وأنا لا أريد حقًا أن أقول إنه سيكون من الصعب الكتابة. أريد في الغالب أن أقول إن هذا يبدو غير متسق مع الاستخدامات الأخرى .member والتي على سبيل المثال يمكن تحويلها إلى مطابقة. كان المنشور الأصلي يزن الإيجابيات والسلبيات في هذا الصدد.

تحرير: التغيير والتبديل لملاءمة future.await(42) ينطوي على مخاطر إضافية ، من المحتمل أن تكون غير مقصودة ، تجعل ذلك أ) غير متسق مع عمليات الإغلاق حيث لا يكون هذا هو الحال بسبب الأساليب التي تحمل نفس اسم العضو المسموح به ؛ ب) منع التطورات المستقبلية حيث نرغب في تقديم الحجج لـ await . ولكن ، كما ذكرت سابقًا ، لا ينبغي أن يكون التغيير والتبديل لـ Future إعادة الإغلاق هو المشكلة الأكثر إلحاحًا.

novacrazy لماذا لا تستخدم مجرد

لست متأكدًا من مقدار خبرتك مع Futures 0.3 ، ولكن التوقع العام هو أنه لن يتم استخدام الدمج كثيرًا ، والاستخدام الأساسي / الاصطلاحي سيكون غير متزامن / ينتظر.

يتميز Async / wait بالعديد من المزايا التي تتفوق على أدوات الدمج ، مثل أنه يدعم الاقتراض عبر نقاط العائد.

كانت أدوات التجميع موجودة قبل فترة طويلة من عدم التزامن / الانتظار ، ولكن تم اختراع عدم التزامن / الانتظار على أي حال ، ولسبب وجيه!

Async / wait موجود لتبقى ، مما يعني أنه يجب أن يكون مريحًا (بما في ذلك مع سلاسل الأسلوب).

بالطبع للناس الحرية في استخدام أدوات الدمج إذا رغبوا في ذلك ، ولكن لا ينبغي أن تكون ضرورية للحصول على بيئة عمل جيدة.

كما قال cramertj ، دعونا نحاول إبقاء المناقشة مركزة على عدم التزامن / انتظار ، وليس بدائل غير متزامن / انتظار.

في الواقع ، لا تُترجم بعض التعبيرات جيدًا إلى سلاسل انتظار إذا كنت ترغب في الحصول على سلوكيات احتياطية عند الفشل.

يمكن تبسيط المثال الخاص بك بشكل كبير:

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()

يوضح هذا الأجزاء التي تتعامل مع الخطأ والأجزاء التي تسير على المسار الطبيعي السعيد.

هذا أحد الأشياء الرائعة حول عدم التزامن / انتظار: فهو يعمل جيدًا مع أجزاء أخرى من اللغة ، بما في ذلك الحلقات ، والفروع ، match ، ? ، try ، إلخ.

في الواقع ، بصرف النظر عن await ، هذا هو نفس الكود الذي ستكتبه إذا لم تكن تستخدم Futures.

طريقة أخرى لكتابتها ، إذا كنت تفضل استخدام المركب 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()

وأفضل ما في الأمر هو نقل الكود العادي إلى وظيفة منفصلة ، مما يجعل رمز معالجة الخطأ أكثر وضوحًا:

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()

(هذا رد على تعليق joshtriplett ).

لكي أكون واضحًا ، ليس عليك وضع الأقواس ، فقد ذكرتها لأن بعض الناس قالوا إنه من الصعب جدًا القراءة بدون الأقواس. لذا فإن الأقواس هي اختيار أسلوبي اختياري (مفيد فقط للخطوط المفردة المعقدة).

كل من صالح جمل من الأقواس في بعض الحالات، فإن أيا من جمل مثالية، انها مسألة أي من الحالات نريد أن الأمثل ل.

أيضًا ، بعد إعادة قراءة تعليقك ، ربما كنت تعتقد أنني كنت أدافع عن البادئة await ؟ لم أكن ، المثال الخاص بي كان يستخدم postfix await . أنا بشكل عام أحب postfix await ، على الرغم من أنني أحب بعض الصيغ الأخرى أيضًا.

بدأت أشعر بالراحة مع fut.await ، أعتقد أن رد الفعل الأولي للناس سيكون "انتظر ، هذا ما تنتظره؟ غريب." لكن لاحقًا سيحبونه للراحة. بالطبع ، ينطبق الشيء نفسه على @await ، والذي يبرز أكثر بكثير من .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()

يوضح هذا أيضًا ما يتم فكه بـ ? ، مقابل await some_op()? ، ليس من الواضح ما إذا كان some_op() فكه أو النتيجة المنتظرة.

تضمين التغريدة

لا أحاول تحويل التركيز بعيدًا عن الموضوع هنا ، أحاول أن أشير إلى أنه غير موجود في الفقاعة. علينا أن نفكر في كيفية عمل الأشياء معًا .

حتى لو تم اختيار الصيغة المثالية ، ما زلت أرغب في استخدام العقود الآجلة المخصصة والجمعيات في بعض المواقف. فكرة أن هؤلاء يمكن أن يكونوا مهملين بشكل ناعم يجعلني أتساءل عن الاتجاه الكامل لـ Rust.

لا تزال الأمثلة التي تعطيها تبدو رهيبة مقارنة بالمُجمِّعات ، ومع حمل المولد من المحتمل أن تكون أبطأ قليلاً وستنتج المزيد من كود الآلة.

بقدر ما ينتظر ، كل هذه البادئة / postfix sigil / bikeshedding للكلمة الرئيسية رائعة ، ولكن ربما يجب أن نكون عمليين ونذهب مع أبسط خيار مألوف أكثر للمستخدمين القادمين إلى Rust. أي: بادئة الكلمة

هذا العام سوف يمر بشكل أسرع مما نعتقد. حتى شهر يناير يتم في الغالب. إذا اتضح أن المستخدمين ليسوا سعداء بكلمة رئيسية بادئة ، فيمكن تغييرها في إصدار 2019/2020. يمكننا حتى أن نجعل نكتة "الإدراك المتأخر هو عام 2020".

تضمين التغريدة

الإجماع العام الذي رأيته هو أن _أقدم _ نرغب في إصدار ثالث هو عام 2022. نحن بالتأكيد لا نريد التخطيط لطبعة أخرى ؛ كانت نسخة 2018 رائعة ولكن ليس بدون تكاليف. (وإحدى نقاط إصدار 2018 هي جعل عدم المزامنة / الانتظار ممكنًا ، تخيل استرجاع ذلك وقول "لا ، أنت بحاجة للترقية إلى إصدار 2020 الآن!")

على أي حال ، لا أعتقد أن الكلمة الأساسية البادئة -> انتقال الكلمات الرئيسية بعد الإصلاح ممكنة في إصدار ، حتى لو كان ذلك مرغوبًا فيه. القاعدة حول الإصدارات هي أنه يجب أن تكون هناك طريقة لكتابة كود اصطلاحي في الإصدار X بحيث يتم تجميعه بدون تحذيرات ويعمل بنفس الطريقة في الإصدار X + 1.

إنه نفس السبب الذي يجعلنا نفضل عدم الاستقرار مع ماكرو الكلمات الرئيسية إذا كان بإمكاننا تحقيق الإجماع على حل مختلف ؛ إن الاستقرار المتعمد لحل نعلم أنه غير مرغوب فيه هو بحد ذاته مشكلة.

أعتقد أننا أظهرنا أن حل ما بعد الإصلاح هو الحل الأمثل ، حتى بالنسبة للتعبيرات التي تحتوي على نقطة انتظار واحدة فقط. لكنني أشك في أن أيًا من حلول ما بعد الإصلاح المقترحة أفضل بشكل واضح من جميع الحلول الأخرى.

فقط سنتان (أنا لست أحدًا ، لكني أتابع المناقشة لفترة طويلة جدًا). الحل المفضل لدي هو إصدار postfix @await . ربما يمكنك التفكير في postfix !await ، مثل بعض بناء جملة ماكرو postfix جديد؟

مثال:

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

بعد عدد قليل من التكرارات اللغوية ، ستكون القدرة على تنفيذ وحدات ماكرو postfix الخاصة بنا أمرًا رائعًا.

... كل مقترحات sygils الجديدة

الصدأ هو بالفعل بناء الجملة / sygil ثقيل ، لدينا await كلمة رئيسية محجوزة ، stuff@await (أو أي sygil أخرى) تبدو غريبة / قبيحة (شخصية ، أعلم) ، وهي مجرد بنية مخصصة لا يتكامل مع أي شيء آخر في اللغة وهو علم أحمر كبير.

لقد ألقيت نظرة على كيفية تنفيذ Go
... <- ... اقتراح

@ I60R : يحتوي Go على بنية رهيبة مليئة بالحلول المخصصة ، وهو أمر ضروري تمامًا ، على عكس Rust. هذا الاقتراح هو مرة أخرى sygil / بناء الجملة ثقيلًا ومخصصًا تمامًا لهذه الميزة بالذات.

@ I60R : Go له تركيب رهيب

دعونا نمتنع من فضلك عن تقريع اللغات الأخرى هنا. "X له تركيب رهيب" لا يؤدي إلى التنوير والتوافق.

بصفتي مستخدم Python / JavaScript / Rust وطالب علوم كمبيوتر ، فأنا شخصياً أفضل البادئة await + f.await() لتكون كلاهما في اللغة.

  1. لكل من Python و JavaScript بادئة await . أتوقع ظهور await في البداية. إذا اضطررت إلى القراءة بعمق لأدرك أن هذا رمز غير متزامن ، أشعر بعدم الارتياح الشديد. مع قدرة Rust's WASM ، قد تجذب العديد من مطوري JS. أعتقد أن الألفة والراحة أمران مهمان حقًا ، مع الأخذ في الاعتبار أن لدى Rust بالفعل الكثير من المفاهيم الجديدة الأخرى.

  2. يبدو Postfix await مناسبًا في إعدادات التسلسل. ومع ذلك ، أنا لا أحب الحلول مثل .await ، @await ، f await لأنها تبدو كحل مخصص لبناء جملة await بينما من المنطقي التفكير .await() لاستدعاء طريقة على future .

يعتبر Rust بالفعل خروجًا عن جافا سكريبت ولا يعد انتظار وظيفة الاستدعاء (أي لا يمكن محاكاة الوظيفة عبر دالة) باستخدام الدوال للإشارة إلى الانتظار ، مما يجعل الأمر مربكًا لأجهزة ضبط الوقت الأولى التي يتم تقديمها إلى الانتظار غير المتزامن. ومن ثم أعتقد أن بناء الجملة يجب أن يكون مختلفا.

لقد أقنعت نفسي أن .await() ربما يكون مرغوبًا فيه أكثر من .await ، على الرغم من أن بقية هذا المنشور يحوط هذا الوضع قليلاً.

والسبب في ذلك هو أن await!(fut) يجب أن يستهلك fut بالقيمة_. جعله يبدو وكأنه وصول إلى الحقل هو _bad_ ، لأن ذلك لا يتضمن دلالة على نقل الطريقة التي تعمل بها الكلمة الأساسية البادئة ، أو إمكانية التحرك مثل الماكرو أو استدعاء الأسلوب.

ومن المثير للاهتمام ، أن بنية طريقة الكلمات الرئيسية تجعلها تبدو وكأنها تصميم انتظار ضمني . لسوء الحظ ، لا يمكن استخدام "Explicit Async، Implicit Await" لـ Rust (لذا _please_ لا تقاضي الأمر مرة أخرى في سلسلة الرسائل هذه) لأننا نريد استخدام async fn() -> T بشكل مماثل لـ fn() -> Future<T> ، بدلاً من تفعيل سلوك "انتظار ضمني".

حقيقة أن بناء جملة .await() _ يبدو_ وكأنه نظام انتظار ضمني (مثل استخدامات Kotlin) يمكن أن يكون منتقدًا ، ومن شأنه أن يعطي إحساسًا بـ "الانتظار الضمني المتزامن ، الانتظار الضمني" بسبب السحر المحيط بـ not- حقا طريقة استدعاء .await() بناء الجملة. هل ستكون قادرًا على استخدام ذلك كـ await(fut) مع UFCS؟ هل ستكون Future::await(fut) لـ UFCS؟ يثير أي بناء جملة يشبه بُعدًا آخر للغة مشاكل ما لم يتم توحيده إلى حد ما معه على الأقل من الناحية التركيبية ، حتى وإن لم يكن وظيفيًا.

ما زلت متشككًا إذا كانت فوائد أي حل من حلول postfix تفوق عيوب ذلك الحل نفسه ، على الرغم من أن مفهوم حل postfix مرغوب فيه أكثر من البادئة بشكل عام.

أنا مندهش قليلاً من أن هذا الموضوع مليء بالاقتراحات التي يبدو أنها قدمت لأنها ممكنة - وليس لأنها يبدو أنها تحقق أي فوائد كبيرة على الاقتراحات الأولية.
هل يمكننا التوقف عن الحديث عن $ ، # ، @ ، ! ، ~ وما إلى ذلك ، دون ذكر حجة مهمة ما الخطأ باستخدام await ، وهو أمر مفهوم جيدًا وقد أثبت نفسه في العديد من لغات البرمجة الأخرى؟

أعتقد أن المنشور من https://github.com/rust-lang/rust/issues/57640#issuecomment -455361619 مدرج بالفعل جميع الخيارات الجيدة.

من اولاءك:

  • المحددات الإلزامية تبدو جيدة ، على الأقل من الواضح ما هي الأسبقية ويمكننا قراءة كود الآخرين دون مزيد من الوضوح. وكتابة قوسين ليس بهذا السوء. ربما يكون الجانب السلبي الوحيد هو أنه يبدو وكأنه استدعاء دالة ، على الرغم من أنه عملية تدفق مختلفة للتحكم.
  • قد تكون الأسبقية المفيدة هي الخيار المفضل. يبدو أن هذا هو الطريق الذي سلكته معظم اللغات الأخرى ، لذلك فهو مألوف ومثبت.
  • أنا شخصياً أعتقد أن الكلمة المفتاحية postfix ذات المسافات البيضاء تبدو غريبة مع عدة مرات انتظار في بيان واحد:
    client.get("url").send() await?.json()? . تلك المسافة البيضاء بينهما تبدو في غير محلها. باستخدام الأقواس ، سيكون الأمر أكثر منطقية بالنسبة لي: (client.get("url").send() await)?.json()?
    لكني أجد أن تتبع تدفق التحكم لا يزال أصعب من متغير البادئة.
  • أنا لا أحب مجال postfix. await بعملية معقدة للغاية - لا يحتوي Rust على خصائص قابلة للحساب ويكون الوصول إلى الحقل بخلاف ذلك عملية بسيطة للغاية. لذلك يبدو أنه يعطي انطباعًا خاطئًا عن مدى تعقيد هذه العملية.
  • يمكن أن تكون طريقة Postfix جيدة. قد يشجع بعض الأشخاص على كتابة عبارات طويلة جدًا مع فترات انتظار متعددة فيها ، مما قد يخفي نقاط العائد أكثر. كما أنه يجعل الأشياء تبدو وكأنها استدعاء طريقة مرة أخرى ، على الرغم من اختلافها.

لهذه الأسباب أفضل "الأولوية المفيدة" متبوعة بـ "المحددات الإلزامية"

يحتوي Go على بنية رهيبة مليئة بالحلول المخصصة ، وهو أمر ضروري تمامًا ، على عكس Rust. هذا الاقتراح هو مرة أخرى sygil / بناء الجملة ثقيلًا ومخصصًا تمامًا لهذه الميزة بالذات.

dpc ، إذا قرأت اقتراح <-- بالكامل ، فسترى أن بناء الجملة هذا مستوحى فقط من Go ، ومع ذلك فهو مختلف تمامًا وقابل للاستخدام سواء في سياق الأمر وفي سياق تسلسل الوظائف. أخفق أيضًا في معرفة كيف أن بناء الجملة await ليس حلًا مخصصًا ، فبالنسبة لي فهو أكثر تحديدًا وأكثر خرقاء من <-- . يشبه أن يكون لديك deref reference / reference.deref / إلخ بدلاً من *reference ، أو لديك try result / result.try / إلخ بدلاً من result? . إما أنني لا أرى أي ميزة لاستخدام الكلمة الرئيسية await بخلاف الإلمام بـ JS / Python / إلخ ، والتي يجب أن تكون على أي حال أقل أهمية من وجود بنية متسقة وقابلة للتكوين. ولا أرى أي عيب في الحصول على <-- sigil بخلاف أنه ليس await وهو على أي حال ليس بهذه البساطة مثل اللغة الإنجليزية البسيطة ويجب على المستخدمين فهم ما يفعله أولاً.

تحرير: قد يكون هذا أيضًا إجابة جيدة لمشاركة @ Matthias247 ، نظرًا لأنه يوفر بعض الحجج ضد await ويقترح بديلاً محتملاً لا يتأثر بنفس المشكلات


من المثير للاهتمام حقًا أن أقرأ النقد الموجه إلى بناء جملة <-- ، خالٍ من الحجج التي تناشد الأسباب التاريخية والمتحيزة.

دعنا نذكر التفاصيل الفعلية حول الأسبقية:

مخطط الأسبقية كما هو اليوم :

عامل / تعبير | الترابطية
- | -
المسارات |
استدعاءات الأسلوب |
تعابير الحقول | من اليسار إلى اليمين
استدعاءات الوظائف ، فهرسة الصفيف |
? |
Unary - * ! & &mut |
as | من اليسار إلى اليمين
* / % | من اليسار إلى اليمين
+ - | من اليسار إلى اليمين
<< >> | من اليسار إلى اليمين
& | من اليسار إلى اليمين
^ | من اليسار إلى اليمين
\| | من اليسار إلى اليمين
== != < > <= >= | تتطلب الأقواس
&& | من اليسار إلى اليمين
\|\| | من اليسار إلى اليمين
.. ..= | تتطلب الأقواس
= += -= *= /= %= &= \|= ^= <<= >>= | من اليمين الى اليسار
إغلاق |. return break إغلاق |

الأسبقية المفيدة تضع await قبل ? بحيث ترتبط بإحكام أكبر من ? . وبالتالي ، فإن A ? في السلسلة يربط `` انتظار كل شيء قبله.

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

نعم ، مع أسبقية مفيدة ، هذا "يعمل فقط". هل تعرف ماذا يفعل ذلك في لمحة؟ هل هذا أسلوب سيء (ربما)؟ إذا كان الأمر كذلك ، فهل يمكن لـ rustfmt إصلاح ذلك تلقائيًا؟

الأسبقية الواضحة تضع await _ في مكان ما_ أقل من ? . لست متأكدًا من المكان بالضبط ، على الرغم من أن هذه التفاصيل ربما لا تهم كثيرًا.

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

هل تعرف ماذا يفعل ذلك في لمحة؟ هل يمكن لـ rustfmt وضعه في أسلوب مفيد لا يضيع كثيرًا من المساحة الرأسية والأفقية تلقائيًا؟


أين تقع كلمات postfix في هذا؟ ربما تكون طريقة الكلمات الرئيسية مع استدعاءات الأسلوب وحقل الكلمات الرئيسية مع تعبيرات الحقول ، لكنني لست متأكدًا من كيفية ربط الآخرين. ما هي الخيارات التي تؤدي إلى أقل التكوينات الممكنة حيث يتلقى await "وسيطة" مفاجئة؟

بالنسبة لهذه المقارنة ، أظن أن "المحددات الإلزامية" (ما أسميته دالة الكلمات الرئيسية في الملخص ) تربح بسهولة ، لأنها ستكون مكافئة لاستدعاء دالة عادية.

@ CAD97 لتوضيح الأمر ، تذكر أن .json() هو أيضًا المستقبل (على الأقل في reqwests ).

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

كلما لعبت أكثر بتحويل تعبيرات الصدأ المعقدة (حتى تلك التي تحتاج فقط إلى 1 في الانتظار ، ومع ذلك ، لاحظ أنه في أكثر من 20000 رمز مستقبلي ، يكون كل تعبير غير متزامن في انتظار ، متبوعًا مباشرة بانتظار آخر) ، كلما كرهت أكثر بادئة الصدأ.

هذا _all_ بسبب عامل التشغيل ? . لا توجد لغة أخرى لديها مشغل تدفق للتحكم بعد الإصلاح _and_ await يتم إقرانها دائمًا بشكل أساسي في رمز حقيقي.


لا يزال تفضيلي هو حقل postfix . بصفتي عامل تحكم بعد الإصلاح ، أشعر أنه يحتاج إلى التجميع المرئي الضيق الذي يوفره future.await أكثر من future await . وبالمقارنة مع .await()? ، فإنني أفضل كيف يبدو .await? _ غريب_ لذا سوف يتم ملاحظته ولن يفترض المستخدمون أنها وظيفة بسيطة (وبالتالي لن يسألوا لماذا لا تعمل UFCS) .


كنقطة بيانات أخرى لصالح postfix ، عندما يستقر هذا ، فإن rustfix للانتقال من await!(...) إلى ما نقرره سيكون موضع تقدير كبير. لا أرى كيف يمكن ترجمة أي شيء ما عدا بناء الجملة بعد الإصلاح بشكل لا لبس فيه دون تغليف الأشياء في ( ... ) داع.

أعتقد أنه يجب علينا أولاً الإجابة على السؤال "هل نريد تشجيع استخدام await في تسلسل السياقات؟". أعتقد أن الإجابة السائدة هي "نعم" ، لذلك تصبح حجة قوية لمتغيرات postfix. في حين أن await!(..) سيكون أسهل إضافة ، أعتقد أنه لا ينبغي لنا تكرار قصة try!(..) . كما أنني شخصياً لا أتفق مع الحجة القائلة بأن "التسلسل يخفي عملية محتملة مكلفة" ، فلدينا بالفعل الكثير من طرق التسلسل التي يمكن أن تكون ثقيلة للغاية ، لذا فإن التسلسل لا يستلزم الكسل.

في حين أن البادئة await الكلمة الرئيسية ستكون الأكثر شيوعًا للمستخدمين القادمين من لغات أخرى ، لا أعتقد أننا يجب أن نتخذ قرارنا بناءً عليها وبدلاً من ذلك يجب أن نركز على المدى الطويل ، أي سهولة الاستخدام والراحة وسهولة القراءة . تحدثت withoutboats عن "ميزانية الألفة" ، لكنني أعتقد بقوة أنه لا ينبغي لنا تقديم حلول دون المستوى الأمثل فقط من أجل الإلمام.

ربما لا نريد الآن طريقتين لعمل نفس الشيء ، لذا لا يجب أن نقدم كلا من متغيري postfix والبادئة. لنفترض أننا قمنا بتضييق خياراتنا على متغيرات postfix.

لنبدأ أولاً بـ fut await ، أنا لا أحب بشدة هذا المتغير ، لأنه سوف يعبث بشكل خطير بكيفية تحليل البشر للكود وسيكون مصدر ارتباك دائم أثناء قراءة الكود. (لا تنس أن هذا الرمز مخصص للقراءة في الغالب)

التالي fut.await و fut.await() و fut.await!() . أعتقد أن البديل الأكثر اتساقًا والأقل إرباكًا هو ما بعد الإصلاح الكلي. لا أعتقد أنه من المفيد تقديم "وظيفة الكلمات الرئيسية" الجديدة أو كيان "طريقة الكلمات الرئيسية" فقط لحفظ بضعة أحرف.

أخيرًا ، المتغيرات المستندة إلى sigil: fut@await و fut@ . لا أحب المتغير fut@await ، إذا قدمنا ​​sigil فلماذا تهتم بالجزء await ؟ هل لدينا خطط للتمديدات المستقبلية fut@something ؟ إذا لم يكن الأمر كذلك ، فهو ببساطة يشعر بأنه زائدة عن الحاجة. لذلك أحب المتغير fut@ ، فهو يحل مشكلات الأسبقية ، ويصبح من السهل فهم الكود والكتابة والقراءة يمكن حل مشكلات الرؤية عن طريق تمييز التعليمات البرمجية. لن يكون من الصعب شرح هذه الميزة على أنها "@ for wait". العيب الأكبر بالطبع هو أننا سندفع مقابل هذه الميزة من "ميزانية سيجيل" المحدودة للغاية ، ولكن بالنظر إلى أهمية الميزة وعدد مرات استخدامها في قواعد الرموز غير المتزامنة ، أعتقد أنها ستكون ذات قيمة على المدى الطويل . وبالطبع يمكننا رسم أوجه تشابه معينة مع ? . (على الرغم من أننا يجب أن نكون مستعدين لنكات بيرل من نقاد رست)

في الختام: في رأيي إذا كنا مستعدين لتحمل "ميزانية سيجيل" يجب أن نذهب بـ fut@ ، وإذا لم يكن مع fut.await!() .

عند الحديث عن الألفة ، لا أعتقد أننا يجب أن نهتم كثيرًا بمعرفة JS / Python / C # ، نظرًا لأن Rust في مكانة مختلفة ويبدو بالفعل مختلفًا في العديد من الأشياء. إن توفير بناء جملة مشابه لهذه اللغات هو هدف قصير المدى ومنخفض المكافأة. لن يختار أحد Rust فقط لاستخدام كلمة رئيسية مألوفة عندما تعمل بشكل مختلف تمامًا تحت الغطاء.

لكن الإلمام بـ Go مهم ، نظرًا لأن Rust في مكانة مماثلة وحتى بالفلسفة فهي أقرب إلى Go أكثر من اللغات الأخرى. وعلى الرغم من كل الكراهية المتحيزة ، فإن إحدى أقوى النقاط في كليهما هي أنهم لا ينسخون الميزات بشكل أعمى ، ولكن بدلاً من ذلك ينفذون حلولًا لها سبب حقيقي.

IMO ، بهذا المعنى ، بناء الجملة <-- هو الأقوى هنا

مع كل ذلك ، لنتذكر أن تعبيرات Rust يمكن أن ينتج عنها عدة طرق متسلسلة. تميل معظم اللغات إلى عدم القيام بذلك.

أود تذكير تجربة فريق C # dev:

الاعتبار الرئيسي ضد بناء جملة C # هو أسبقية العامل في انتظار foo؟

هذا شيء أشعر أنه يمكنني التعليق عليه. لقد فكرنا كثيرًا في الأولوية مع كلمة "انتظار" وجربنا العديد من النماذج قبل الإعداد للشكل الذي نريده. من بين الأشياء الأساسية التي وجدناها أنه بالنسبة لنا وللعملاء (الداخليين والخارجيين) الذين أرادوا استخدام هذه الميزة ، نادرًا ما كان الناس يريدون فعلاً "ربط" أي شيء بعد المكالمة غير المتزامنة.

كان ميل الناس إلى "الاستمرار" مع "الانتظار" داخل expr نادرًا. نرى أحيانًا أشياء مثل (انتظار expr) .M () ، ولكن هذه تبدو أقل شيوعًا وأقل استحسانًا من عدد الأشخاص الذين ينتظرون expr.M ().

و

ولهذا السبب أيضًا لم نختار أي صيغة "ضمنية" لـ "انتظار". من الناحية العملية ، كان هذا شيئًا أراد الناس التفكير فيه بوضوح شديد ، وأرادوا أن يكون في المقدمة والوسط في الكود الخاص بهم حتى يتمكنوا من الانتباه إليه. ومن المثير للاهتمام ، أنه حتى بعد سنوات ، ظل هذا الاتجاه قائما. على سبيل المثال ، نأسف أحيانًا بعد سنوات عديدة على أن شيئًا ما مفرط الإسهاب. بعض الميزات جيدة بهذه الطريقة في وقت مبكر ، ولكن بمجرد أن يشعر الناس بالراحة معها ، فإنهم يكونون أكثر ملاءمة مع شيء أكثر تضييقًا. لم يكن هذا هو الحال مع "انتظار". يبدو أن الناس لا يزالون يحبون الطبيعة الثقيلة لتلك الكلمة الرئيسية والأولوية التي اخترناها.

هي نقطة جيدة ضد sigil بدلاً من كلمة مخصصة (مفتاح).

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

يجب أن تستمع حقًا إلى اللاعبين الذين لديهم ملايين المستخدمين.

لذلك لا تريد ربط أي شيء ، فأنت تريد فقط الحصول على عدة await ، وتجربتي هي نفسها. كتابة رمز async/await لأكثر من 6 سنوات ، ولم أرغب أبدًا في مثل هذه الميزة. يبدو بناء جملة Postfix غريبًا حقًا ويعتبر حلًا لموقف من المحتمل ألا يحدث أبدًا. Async هو أمر جريء حقًا ، لذا فإن عدد مرات الانتظار في سطر واحد ثقيل جدًا.

كان ميل الناس إلى "الاستمرار" مع "الانتظار" داخل expr نادرًا. نرى أحيانًا أشياء مثل (انتظار expr) .M () ، ولكن هذه تبدو أقل شيوعًا وأقل استحسانًا من عدد الأشخاص الذين ينتظرون expr.M ().

هذا يبدو وكأنه تحليل لاحق. ربما يكون أحد أسباب عدم استمرارهم هو أنه من المحرج للغاية القيام بذلك في بنية البادئة (يمكن مقارنته بعدم الرغبة في try! عدة مرات في البيان لأن ذلك يظل قابلاً للقراءة من خلال المشغل ? ). ما ورد أعلاه يعتبر في الغالب (بقدر ما أستطيع أن أقول) الأسبقية ، وليس الموقف. وأود أن أذكرك أن C # ليس صدأ ، وقد يغير أعضاء السمات بعض الشيء الرغبة في استدعاء الأساليب على النتائج.

@ I60R ،

  1. أعتقد أن الإلمام مهم. Rust هي لغة جديدة نسبيًا وسوف يهاجر الناس من لغات أخرى وإذا كان Rust يبدو مألوفًا فسيكون من الأسهل عليهم اتخاذ قرار لاختيار Rust.
  2. أنا لست ممتعًا جدًا في طرق التسلسل - من الصعب جدًا تصحيح أخطاء السلاسل الطويلة ، وأعتقد أن التسلسل يؤدي فقط إلى تعقيد قابلية قراءة الكود وقد يُسمح به فقط كخيار إضافي (مثل وحدات الماكرو .await!() ). سيجبر نموذج البادئة المطورين على استخراج التعليمات البرمجية إلى طرق بدلاً من التسلسل ، مثل:
let resp = await client.get("http://api")?;
let body: MyResponse = await resp.into_json()?;

في شيء مثل هذا:

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

هذا يبدو وكأنه تحليل لاحق. ربما يكون أحد أسباب عدم استمرارهم هو أنه من المحرج للغاية القيام بذلك في صيغة البادئة. ما ورد أعلاه يعتبر الأسبقية فقط وليس الموقف. وأود أن أذكرك أن C # ليس صدأ ، وقد يغير أعضاء السمات بعض الشيء الرغبة في استدعاء الأساليب على النتائج.

لا ، يتعلق الأمر بتجارب فريق C # الداخلية عندما يكون لها كلا من البادئة / postfix / الصيغ الضمنية. وأنا أتحدث عن تجربتي التي ليست مجرد عادة لا أستطيع فيها رؤية محترفي نماذج postfix.

mehcode المثال الخاص بك لا reqwest بوعي أن يجعل دورة الطلب / الاستجابة الأولية والمعالجة اللاحقة للاستجابة (تدفق الجسم) عمليات متزامنة منفصلة ، ومن ثم يجب انتظارها مرتين ، مثل عروض andreytkachenko .

يمكن أن يعرض reqwest واجهات برمجة التطبيقات التالية تمامًا:

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

أو

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

(هذا الأخير هو سكر بسيط يزيد عن and_then ).

أجد أنه من المثير للقلق أن الكثير من أمثلة ما بعد الإصلاح هنا تستخدم هذه السلسلة كمثال ، حيث أن أفضل قرار لواجهة برمجة تطبيقات reqwest s و hyper s هو إبقاء هذه الأشياء منفصلة.

أعتقد بصدق أن معظم الأمثلة التي تنتظر postfix هنا يجب إعادة كتابتها باستخدام أدوات دمج (والسكر إذا لزم الأمر) أو الاحتفاظ بعمليات مماثلة وانتظارها عدة مرات.

تضمين التغريدة

سيجبر نموذج البادئة المطورين على استخراج التعليمات البرمجية إلى طرق بدلاً من التسلسل ، مثل:

هل هذا شيء جيد أو سيئا؟ في الحالات التي توجد فيها طرق N ، كل منها يمكن أن يؤدي إلى متابعة M ، من المفترض أن يقدم المطور طرق N * M ؟ أنا شخصياً أحب الحلول القابلة للتكوين ، حتى لو كانت أطول قليلاً.

في شيء مثل هذا:

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

تضمين التغريدة

لا أعتقد أنه في C # ستحصل على نفس القدر من الكود المتسلسل / الوظيفي كما تفعل في Rust. أو انا مخطئ؟ هذا يجعل تجارب C # devs / المستخدمين ممتعة ، ولكنها لا تنطبق بالضرورة على Rust. أتمنى أن نتمكن من التناقض مع أوكامل أو هاسكل.

أعتقد أن السبب الجذري للخلاف هو أن البعض منا يتمتع بأسلوب حتمي ، وآخر وظيفي. وهذا كل شيء. يدعم Rust كلاهما ، ويريد كلا الجانبين من المناقشة غير المتزامن أن يتناسب جيدًا مع الطريقة التي يكتبون بها الكود عادةً.

dpc

لا أعتقد أنه في C # ستحصل على نفس القدر من الكود المتسلسل / الوظيفي كما تفعل في Rust. أو انا مخطئ؟ هذا يجعل تجارب C # devs / المستخدمين ممتعة ، ولكنها لا تنطبق بالضرورة على Rust. أتمنى أن نتمكن من التناقض مع أوكامل أو هاسكل.

LINQ والأسلوب الوظيفي شائعان جدًا في C #.

dpc ،
إذا كنت بصدد قابلية القراءة - أعتقد أن الكثير من الكود صريح - أفضل ، وتفيد تجربتي أنه إذا كانت أسماء الطرق / الوظائف تصف نفسها جيدًا ، فليس من الضروري إجراء عمليات المتابعة.

إذا كنت تتحدث عن النفقات العامة - فإن برنامج Rust compiler ذكي إلى حد ما لتضمينها (على أي حال ، لدينا دائمًا #[inline] ).

dpc

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

شعوري هو أن هذا يكرر بشكل أساسي مشكلة API للعقود الآجلة: إنه يجعل التسلسل أسهل ، لكن نوع تلك السلسلة يصبح من الصعب اختراقها وتصحيحها.

ألا تنطبق نفس الحجة على؟ عامل رغم ذلك؟ إنه يسمح بمزيد من التسلسل وبالتالي يجعل تصحيح الأخطاء أكثر صعوبة. لكن لماذا اخترنا؟ فوق المحاولة! ثم؟ ولماذا يفضل Rust واجهات برمجة التطبيقات مع نمط البناء؟ لذلك قام Rust بالفعل بمجموعة من الخيارات لصالح التسلسل في واجهات برمجة التطبيقات. ونعم ، قد يجعل ذلك تصحيح الأخطاء أكثر صعوبة ، لكن ألا يجب أن يحدث ذلك على مستوى الوبر - ربما نسالة جديدة لقص السلاسل الكبيرة جدًا؟ لا يزال يتعين علي أن أرى دافعًا كافيًا لمعرفة مدى اختلاف الانتظار هنا.

هل هذا شيء جيد أو سيئا؟ في الحالات التي توجد فيها طرق N ، كل منها يمكن أن يؤدي إلى متابعة M ، من المفترض أن يقدم المطور طرق N * M ؟ أنا شخصياً أحب الحلول القابلة للتكوين ، حتى لو كانت أطول قليلاً.

N و M ليسا بالضرورة كبيرين ، وقد لا تكون جميعها مفيدة / مفيدة في الاستخراج.

andreytkachenko ،

  1. لا أوافق ، الألفة مبالغ فيها هنا. عند الترحيل من لغة أخرى ، سيسعى الأشخاص أولاً إلى القدرة على القيام ببرمجة على غرار async-await ولكن ليس لنفس الصيغة تمامًا. إذا تم تطبيقه بشكل مختلف ولكن ذلك سيوفر تجربة برمجة أفضل ، فسيصبح ميزة أخرى.

  2. عدم القدرة على تصحيح أخطاء السلاسل الطويلة هو تقييد لمصححات الأخطاء وليس نمط التعليمات البرمجية. أعتقد أن الأمر يعتمد على قابلية القراءة ، وفرض أسلوب الأمر أمر مقيد بشكل غير ضروري هنا. إذا كانت استدعاءات الوظائف async لا تزال عبارة عن استدعاءات وظيفية ، فمن المدهش ألا تدعم التسلسل. وعلى أي حال ، فإن <-- هو عامل تشغيل بادئة مع إمكانية استخدامه في سلاسل الوظائف كخيار إضافي ، كما قلت

skade متفق.

كل الأمثلة ذات السلاسل الطويلة من الانتظار هي رائحة كود. يمكن حلها بشكل أكثر أناقة مع الدمج أو واجهات برمجة التطبيقات المحدثة ، بدلاً من إنشاء آلات حالة السباغيتي للمولد هذه تحت الغطاء. صيغة الاختزال المتطرفة هي وصفة لتصحيح الأخطاء أكثر صعوبة من العقود الآجلة الحالية.

ما زلت معجبًا بكل من البادئة الكلمة الرئيسية await والماكرو await!(...) / .await!() ، بالإضافة إلى وظائف المولد async و #[async] كما هو موضح في تعليقي هنا .

إذا سارت الأمور بشكل صحيح ، فمن المحتمل أن يتم إنشاء ماكرو واحد مقابل await! للتعامل مع كل من البادئة واللاحقة ، وستكون الكلمة الأساسية await فقط كلمة أساسية داخل وظائف async .

skade ،
بالنسبة لي ، هناك عيب كبير في استخدام أدوات الدمج على future هو أنها ستخفي استدعاءات الوظيفة async وسيكون من المستحيل تمييزها عن الوظائف العادية. أريد أن أرى جميع النقاط القابلة للتعليق حيث من المحتمل جدًا أن يتم استخدامها داخل سلاسل منشئ نمط.

@ I60R تصحيح أخطاء سلاسل المكالمات الطويلة ليس تمامًا مشكلة مصحح الأخطاء ، لأن مشكلة كتابة هذه السلاسل هي الحصول على الاستدلال الصحيح. بالنظر إلى أن العديد من هذه الأساليب تأخذ معلمات عامة وتوزع معلمات عامة ، يحتمل أن تكون مرتبطة بإغلاق ، فإن هذه مشكلة خطيرة.

لست متأكدًا مما إذا كان إظهار جميع نقاط التعليق هو أحد أهداف الميزة. هذا على المنفذين لاتخاذ القرار. وهو ممكن تمامًا مع جميع الإصدارات المقترحة من بناء الجملة await .

novacrazy نقطة جيدة ، الآن بعد أن ذكرتها ، بصفتي javascripter سابقًا ، لم أستخدم أبدًا سلاسل الانتظار ، لقد استخدمت الكتل دائمًا

أعتقد أنه سيبدو شيء مثل

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

التي لا أعرف حتى ما إذا كان ذلك ممكنًا ، أحتاج إلى البحث عن العقود الآجلة في Rust

richardanaya هذا ممكن تمامًا (بناء جملة مختلف).

مثل المربع الضمني:

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

يمكننا إدخال كلمة رئيسية جديدة await والسمة Await مع مثل هذا الضمني:

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

واستخدم await كطريقة وككلمة رئيسية أيضًا:

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

richardanaya موافق . كنت من بين بعض المطورين الأوائل الذين تبنوا عدم التزامن / الانتظار لأشياء webdev الخاصة بي منذ سنوات عديدة ، وجاءت القوة الحقيقية من الجمع بين غير المتزامن / الانتظار مع الوعود / العقود المستقبلية الحالية أيضًا ، ربما يمكن تبسيط مثالك أيضًا على النحو التالي:

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

let value = result.unwrap();

إذا كان لديك عقود مستقبلية متداخلة أو نتائج مستقبلية أو نتائج مستقبلية ، يمكن لمجمع .flatten() تبسيط ذلك بشكل كبير. سيكون شكلًا سيئًا لفك الغلاف وانتظار كل واحدة يدويًا.

XX هذا زائد وغير صالح. await موجود فقط في وظائف async ، ويمكن أن يعمل فقط على الأنواع التي تنفذ Future / IntoFuture أي حال ، لذلك ليست هناك حاجة إلى جديد سمة.

novacrazy ، richardanaya

يمكن حلها بشكل أكثر أناقة مع الدمج أو واجهات برمجة التطبيقات المحدثة ، بدلاً من إنشاء آلات حالة السباغيتي للمولد هذه تحت الغطاء. صيغة الاختزال المتطرفة هي وصفة لتصحيح الأخطاء أكثر صعوبة من العقود الآجلة الحالية.

novacrazy نقطة جيدة ، الآن بعد أن ذكرتها ، بصفتي javascripter سابقًا ، لم أستخدم أبدًا سلاسل الانتظار ، لقد استخدمت الكتل دائمًا

الموحدين لديهم خصائص وسلطات مختلفة تمامًا في Rusts غير المتزامن / انتظار ، على سبيل المثال فيما يتعلق بالاقتراض عبر نقاط العائد. لا يمكنك كتابة أدوات دمج لها نفس قدرات الكتل غير المتزامنة بأمان. دعنا نترك مناقشة المُجمع خارج هذا الموضوع ، لأنه ليس مفيدًا.

@ I60R

بالنسبة لي ، من العيوب الكبيرة لاستخدام الدمج في المستقبل أنها ستخفي استدعاءات الدوال غير المتزامنة وسيكون من المستحيل تمييزها عن الوظائف العادية. أريد أن أرى جميع النقاط القابلة للتعليق حيث من المحتمل جدًا أن يتم استخدامها داخل سلاسل منشئ نمط.

لا يمكنك إخفاء نقاط التعليق. الشيء الوحيد الذي يسمح به الدمج هو خلق مستقبل آخر. في وقت ما يجب انتظار هؤلاء.

ضع في اعتبارك أن C # ليس لديها مشكلة حيث أن معظم الأكواد التي تستخدم البادئة await ستجمعها مع عامل postfix ? (الموجود بالفعل). على وجه الخصوص ، تظهر الأسئلة حول الأسبقية ، والإحراج العام لوجود "مزينين" متشابهين على جانبي التعبير.

@ Matthias247 إذا كنت بحاجة إلى استعارة البيانات ، فلا تتردد في استخدام كشوف حسابات متعددة await . ومع ذلك ، في كثير من الأحيان تحتاج ببساطة إلى نقل البيانات ، وتكون أدوات الدمج صالحة تمامًا لذلك ، ولديها إمكانية التجميع إلى رمز أكثر كفاءة. في بعض الأحيان يتم تحسينها بالكامل.

مرة أخرى ، تكمن القوة الحقيقية في الجمع بين الأشياء معًا. لا توجد طريقة واحدة صحيحة للقيام بأشياء معقدة مثل هذه. هذا جزئيًا هو السبب الذي يجعلني أعتبر أن كل هذا الأسلوب في بناء الجملة هو بالضبط ما إذا كان لن يساعد المستخدمين الجدد القادمين من لغة أخرى ويساعد في إنشاء تعليمات برمجية قابلة للصيانة وفعالة وقابلة للقراءة . 80٪ من الوقت أشك في أنني سأتطرق حتى إلى async / await بغض النظر عن البنية ، فقط لتوفير واجهات برمجة التطبيقات الأكثر استقرارًا والأداء باستخدام العقود الآجلة.

في هذا الصدد ، فإن الكلمات الرئيسية البادئة await و / أو الماكرو المختلط await!(...) / .await!() هي الخيارات الأكثر قابلية للقراءة والألفة وسهلة التصحيح.

تضمين التغريدة

لقد رأيت منشوراتك ووافقت تمامًا ، في الواقع ، الآن بعد أن أفكر في "الألفة" ، أعتقد أن هناك نوعين من الألفة:

  1. جعل صيغة الانتظار تبدو مثل اللغات المشابهة التي تستخدم الكثير من الانتظار. جافا سكريبت كبيرة جدًا هنا ، وهي وثيقة الصلة بإمكانيات WASM كمجتمع.

  2. النوع الثاني من الألفة هو جعل الكود يبدو مألوفًا للمطورين الذين يعملون فقط مع التعليمات البرمجية المتزامنة. أعتقد أن أعظم جوانب الانتظار هو جعل مظهر الكود غير المتزامن متزامنًا. هذا في الواقع شيء واحد خطأ في جافا سكريبت حقًا هو أنه تم تطبيعه لفترة طويلة ثم السلاسل التي تبدو غريبة تمامًا عن المطورين المتزامنين بشكل أساسي.

كان أحد الأشياء التي كنت سأقدمها من أيام جافا سكريبت غير المتزامنة ، الشيء الأكثر فائدة بالنسبة لي كمطور في الماضي هو القدرة على تجميع الوعود / المستقبل معًا. Promise.all (p1 ()، p2 ()) لذلك يمكنني بسهولة موازنة العمل. كان التسلسل () في ذلك الوقت مجرد صدى لماضي وعد جافا سكريبت ، ولكنه قديم إلى حد كبير وغير ضروري الآن بعد أن أفكر في الأمر.

قد أقدم فكرة الانتظار هذه. "حاول أن تجعل الاختلافات بين الشفرة غير المتزامنة وشفرة المزامنة إلى أدنى حد ممكن"

novacrazy ترجع الدالة غير المتزامنة Future ، أليس كذلك؟ ما الذي يمنعنا من إضافة طريقة الانتظار إلى سمة Future ؟ مثل هذا:

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

XX كما أفهمها ، يتم تحويل وظائف async في Rust إلى آلات حالة باستخدام المولدات. هذا المقال هو شرح جيد. لذا فإن await يحتاج إلى وظيفة async للعمل بها ، لذلك يمكن للمجمع تحويلهما بشكل صحيح. لا يمكن أن يعمل await بدون الجزء async .

Future لا يملك wait الأسلوب، وهو على غرار ما توحي لك، ولكن يمنع الترابط الحالي.

skade ،

ولكن كيف يمكن أن يؤثر نمط الكود على الاستدلال على الكتابة؟ لا أرى اختلافًا في نفس الكود المكتوب بأسلوب متسلسل وحتمي. يجب أن تكون الأنواع متطابقة تمامًا. إذا لم يتمكن المصحح من اكتشافها ، فهذه بالتأكيد مشكلة في مصحح الأخطاء ، وليس في الكود.

skade ، @ Matthias247 ،XX

مع الدمج سيكون لديك بالضبط نقطة معلقة واحدة في بداية سلسلة الوظائف. كل الآخر سيكون ضمنيًا في الداخل. هذه بالضبط نفس المشكلة مع أخذ mut ضمنيًا ، والذي كان بالنسبة لي شخصيًا أحد أكبر نقاط الارتباك في Rust على الإطلاق. قد تقوم بعض واجهات برمجة التطبيقات بإرجاع العقود الآجلة بينما قد يُرجع البعض الآخر نتائج العقود الآجلة - لا أريد ذلك. يجب أن تكون نقاط التعليق صريحة إذا كان ذلك ممكنًا ، كما أن التركيب المناسب للتركيب يشجع على ذلك

@ I60R let الروابط هي نقاط اتصال لكتابة الاستدلال والمساعدة في الإبلاغ عن الخطأ.

تضمين التغريدة

لذا انتظار يحتاج إلى وظيفة غير متزامنة للعمل فيها

هل يمكن التعبير عن هذا في نوع الإرجاع؟ على سبيل المثال ، أن نوع الإرجاع هو impl Future + Async ، بدلاً من impl Future .

skade أعتقد دائمًا أن بنية استدعاء الأسلوب . تخدم نفس الغرض تمامًا

dpc

لا أعتقد أنه في C # ستحصل على نفس القدر من الكود المتسلسل / الوظيفي كما تفعل في Rust. أو انا مخطئ؟ هذا يجعل تجارب C # devs / المستخدمين ممتعة ، ولكنها لا تنطبق بالضرورة على Rust. أتمنى أن نتمكن من التناقض مع أوكامل أو هاسكل.

تحصل على قدر الصدأ. انظر إلى أي كود LINQ وسترى ذلك.

يبدو الرمز غير المتزامن النموذجي كما يلي:

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();
    }
}

أو أكثر عمومية

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

أنت لا تقوم بتسلسل المكالمات ، بل تقوم بتعيينها إلى متغير ثم تستخدم بطريقة ما. هذا صحيح بشكل خاص عندما تتعامل مع مدقق الاستعارة.


يمكنني أن أوافق على أنه يمكنك تسلسل العقود الآجلة في موقف واحد. عندما تريد معالجة خطأ قد ينتج أثناء المكالمة. على سبيل المثال ، let a = await!(service_a)? . هذا هو الموقف الوحيد الذي يكون فيه بديل postfix أفضل. يمكنني أن أرى أنه مفيد هنا ، لكنني لا أعتقد أنه يفوق كل السلبيات.

سبب آخر: ماذا عن impl Add for MyFuture { ... } و let a = await a + b; ؟

أود أن أذكر بـ Generalized Type Ascription RFC في سياق كلمة postfix انتظار. سيسمح بالشفرة التالية:

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

يشبه إلى حد كبير الكلمة الأساسية التي تنتظر postfix:

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

مع نفس الجوانب السلبية عند التنسيق بشكل سيئ:

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

أعتقد أننا إذا ابتلعنا الحبة من نوع Ascription RFC ، يجب أن نبتلع fut await أجل الاتساق.

راجع للشغل ، هل تعلم أن هذا بناء جملة صالح:

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

ومع ذلك ، لم أر أي تكرار لهذا في الكود الحقيقي.

واسمحوا لي أن استطرادا قليلا. أعتقد أننا يجب أن نعطي وحدات ماكرو postfix ، كما اقترح BenoitZugmeyer ، فكرة أخرى. يمكن القيام بذلك إما expr!macro أو expr@macro ، أو ربما expr.macro!() . أعتقد أن الخيار الأول سيكون الأفضل. لست متأكدًا مما إذا كانت فكرة جيدة ، ولكن إذا أردنا استخراج مفهوم عام وليس حلًا مخصصًا مع استمرار انتظار ما بعد الإصلاح ، فيجب أن نفكر على الأقل في وحدات ماكرو postfix كحل محتمل.

ضع في اعتبارك أنه حتى لو أردنا جعل await ماكرو postfix ، فسيظل ماكروًا سحريًا (مثل compile_error! ). ومع ذلك ، فقد أثبتjplatte وآخرون بالفعل أن هذه ليست مشكلة.

كيف يمكنني القيام بذلك إذا أردنا السير في هذا الطريق ، أود أولاً تحديد كيفية عمل وحدات ماكرو postfix بالضبط ، ثم السماح فقط await كماكرو postfix سحري ثم السماح لاحقًا بوحدات ماكرو postfix محددة.

ليكسينج

بالنسبة إلى lexing / التحليل ، قد تكون هذه مشكلة. إذا نظرنا إلى expr!macro ، فقد يعتقد المترجم أن هناك ماكرو يسمى expr! ثم هناك بعض الأحرف غير الصالحة macro بعد ذلك. ومع ذلك ، يجب أن يكون expr!macro ممكنًا في lex من خلال نظرة أولية ، ويصبح شيء ما ماكرو postfix عندما يكون هناك expr متبوعًا بـ ! ، متبوعًا بـ identifier . أنا لست مطور لغة ولست متأكدًا مما إذا كان هذا يجعل lexing معقدًا للغاية. سأفترض فقط أن وحدات ماكرو postfix يمكن أن تأخذ شكل expr!macro .

هل ستكون وحدات ماكرو postfix مفيدة؟

في الجزء العلوي من رأسي ، توصلت إلى حالات استخدام أخرى لوحدات الماكرو postfix. لا أعتقد أنه يمكن تنفيذها جميعًا بواسطة وحدات ماكرو مخصصة ، لذلك لست متأكدًا مما إذا كانت هذه القائمة مفيدة للغاية.

  • stream!await_all : لانتظار التدفقات
  • option!or_continue : عندما يكون option شيء ، تابع الحلقة
  • monad!bind : لعمل =<< بدون ربطه باسم

stream!await_all

هذا لا يسمح لنا فقط بانتظار العقود الآجلة ، ولكن أيضًا التدفقات.

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

ستكون معادلة لـ (في كتلة async -stream-esque):

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

UpdateTickerAction::new(exchange_rate.value)

option!or_continue

هذا يسمح لنا بإلغاء تغليف أحد الخيارات ، وإذا كان None ، فتابع الحلقة.

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);
}

سيكون معادلاً لـ:

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

monad!bind

سيسمح لنا هذا بالحصول على monads بطريقة ريفية إلى حد ما (أي تتمحور حول التعبير). لا يحتوي Rust على أي شيء مثل monads حتى الآن ، ولا أبيعهم عند إضافتهم إلى Rust. ومع ذلك ، إذا تمت إضافتها ، فقد يكون هذا التركيب مفيدًا.

أخذت ما يلي من هنا .

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 ++ "!")

ستكون مكافئة لـ:

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;
}

أو ، بخط أكثر ، بأقل من 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;
}

تقييم

أنا منقسمة جدًا على هذا. يريد الجزء الرياضي من عقلي إيجاد حل معمم لـ await ، وقد تكون وحدات الماكرو postfix وسيلة لتحقيقها. يفكر الجزء البراغماتي من عقلي: لماذا أتعب عناء صنع لغة لا أحد يفهم نظامها الكلي بالفعل أكثر تعقيدًا.

ومع ذلك ، بدءًا من ? و await ، لدينا مثالان لمشغلات postfix مفيدة للغاية. ماذا لو وجدنا أشياء أخرى نريد إضافتها في المستقبل ، ربما مشابهة لتلك التي ذكرتها؟ إذا قمنا بتعميمها ، فيمكننا إضافتها إلى الصدأ بشكل طبيعي. إذا لم نقم بذلك ، فسنضطر إلى التوصل إلى بناء جملة آخر في كل مرة ، مما قد يؤدي إلى تضخم اللغة أكثر من وحدات ماكرو postfix.

ماذا تظنون يا جماعة؟

تضمين التغريدة

لقد توصلت إلى فكرة مشابهة جدًا ، وتأكد من أن وحدات الماكرو postfix في Rust هي مسألة وقت.
أفكر في ذلك في كل مرة عند التعامل مع شرائح ndarray :

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

ولكن سيكون أفضل بكثير

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

وقد يختفي الكثير من أدوات الدمج ، مثل تلك التي تحتوي على _with أو _else postfixes:

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

EyeOfPythonandreytkachenko لا تعد وحدات ماكرو postfix حاليًا ميزة في Rust وسيحتاج IMHO إلى مرحلة تنفيذ RFC + FCP + كاملة.

هذه ليست مناقشة RFC ، ولكنها مناقشة RFC مقبولة والتي يجب تنفيذها.

لهذا السبب ، لا أعتقد أنه من العملي مناقشتها هنا أو اقتراحها لبناء جملة غير متزامن. سيؤدي ذلك إلى تأخير الميزة بشكل كبير.

للحد من هذا النقاش الضخم بالفعل ، أعتقد أنه ليس من المفيد مناقشتها هنا ، يمكن اعتبارها خارج نطاق القضية التي تمت مناقشتها.

مجرد فكرة: بينما هذا الموضوع مخصص لـ await ، أعتقد أنه سيكون لدينا نفس المناقشة لتعبيرات yield على الطريق ، والتي يمكن ربطها أيضًا. نظرًا لأنني أفضل رؤية بناء جملة يمكن تعميمه ، كيفما يبدو.

أسباب عدم استخدام وحدات الماكرو هنا:

  1. يتم توفير وحدات الماكرو للأشياء الخاصة بالمجال واستخدامها بأي طريقة تؤدي إلى تغيير البرامج في التحكم في التدفق أو محاكاة ميزات اللغة الأساسية يعد مبالغة
  2. سيتم إساءة استخدام وحدات ماكرو Postfix على الفور لتنفيذ عوامل تشغيل مخصصة أو ميزات لغة أخرى تؤدي إلى تعليمات برمجية سيئة
  3. قد لا يشجعون على تطوير ميزات اللغة المناسبة:

    • stream.await_all هو حالة استخدام مثالية للمجمع

    • option.or_continue واستبدال أدوات التجميع _else هي حالة استخدام مثالية لمشغل الاندماج الصفري

    • monad.bind هو حالة استخدام مثالية لسلاسل if-let

    • ndarray slicing هو حالة استخدام مثالية للأدوية العامة const

  4. سوف يضعون قيد السؤال بالفعل تنفيذ المشغل ?

تضمين التغريدة

التي يمكن أيضا تقييدها بالسلاسل

لماذا تعتقد أنه يمكن تقييدهم بالسلاسل؟ صفر مرات ظهور في الكود الحقيقي ، تمامًا مثل مثال println أعلاه.

Pzixel من المحتمل أن يأخذ Generator::resume قيمة في النهاية ، وبالتالي فإن yield expr سيكون نوع غير () .

vlaff لا أرى بوضوح حجة لـ await يجب أن تكون متسقة مع Type Ascription. فهي أشياء مختلفة جدا.

أيضًا ، يعد Type Ascription أحد الميزات الأخرى التي تمت تجربتها بشكل متكرر ، وليس هناك ما يضمن أن هذه الميزة ستنجح. على الرغم من أنني لا أريد معارضة ذلك ، فإن TA هي RFC مستقبلية وهذه مناقشة حول ميزة مقبولة مع بناء جملة مقترح بالفعل.

قراءة التعليقات العديدة ، لإضافة مزيد من الملخص عن سبب الانتظار! (...) يبدو مسارًا مثاليًا للغاية:

  1. يبدو أكثر شيوعًا في التعليمات البرمجية الموجودة لأن وحدات الماكرو مألوفة
  2. هناك عمل قائم يستخدم في الانتظار! (...) https://github.com/alexcrichton/futures-await ويمكن أن يساعد في تقليل إعادة كتابة الكود
  3. نظرًا لأن وحدات ماكرو postfix ليست على الطاولة ، فقد لا تكون أبدًا جزءًا من اللغة ، و "انتظر!" في سياق غير قياسي لا يبدو أنه حتى احتمال وفقًا لـ RFCs
  4. إنه يمنح المجتمع مزيدًا من الوقت للنظر في الاتجاه طويل المدى بعد الاستخدام المستقر على نطاق واسع مع توفير شيء ليس خارج القاعدة تمامًا (على سبيل المثال ، حاول! ())
  5. يمكن استخدامه كنمط مشابه للعائد القادم! () حتى نكتشف مسارًا رسميًا يمكن أن يرضي الانتظار والعائد
  6. قد لا يكون التسلسل ذا قيمة كما هو مأمول ، وربما يتم تعزيز الوضوح من خلال وجود فترات انتظار متعددة على أسطر متعددة
  7. لن تحدث أي تغييرات لأدوات تمييز بناء جملة IDE
  8. الأشخاص من اللغات الأخرى ربما لن يتم تجاهلهم من خلال الانتظار! (...) الماكرو بعد رؤية استخدامات الماكرو الأخرى (الحمل الزائد المعرفي أقل)
  9. من المحتمل أن يكون أقل مسار جهد من جميع المسارات للمضي قدمًا نحو الاستقرار

انتظر! الماكرو موجود بالفعل هنا ، السؤال يدور حول التسلسل.

كلما فكرت في الأمر أكثر ، كلما بدت أكثر إيجابية بالنسبة لي. فمثلا:

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)`

يسمح بالتداخل من أي مستوى ، و quire قابل للقراءة. يعمل بسلاسة مع ? والمشغلين المستقبليين المحتملين. يبدو غريبًا بعض الشيء بالنسبة للوافدين الجدد ، لكن ربح القواعد قد يفوق وزنه.

السبب الرئيسي هو أن await يجب أن يكون كلمة رئيسية منفصلة ، وليس استدعاء وظيفي. إنه مهم جدًا لذا يجب أن يكون له تمييزه الخاص ومكانه في النص.

Pzixel ، HeroicKatora ، skade

انظر على سبيل المثال تعبيرات Python yield و yield from : هناك يمكن أن توفر الوظيفة الخارجية قيمة ستصبح نتيجة yield عند استئناف المولد. هذا ما تعنيه valff ، وليس له علاقة ومن ثم ، فإن yield سيكون له أيضًا نوع آخر غير ! أو () .

من نقطة coroutine ، يقوم كل من yield و await بتعليقه ويمكن (في النهاية) إرجاع قيمة. نظرًا لأنهما مجرد وجهين لعملة واحدة.

وللتخلص من إمكانية صياغة أخرى ، _square-brackets-with-keyword_ ، جزئيًا لتمييز هذا (باستخدام yield لتمييز بناء الجملة):

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

"الكلمة الأساسية postfix" هي الأكثر منطقية بالنسبة لي. postfix بحرف آخر غير المسافة البيضاء التي تفصل بين تعبير المستقبل و await أيضًا منطقية بالنسبة لي ولكن ليس "." لأن هذا للطرق و await ليس طريقة. ومع ذلك ، إذا كنت تريد await ككلمة رئيسية بادئة ، فأنا أحب اقتراح XX لسمة أو طريقة تستدعي فقط await self لأولئك الذين يرغبون في تجميع مجموعة من الأشياء معًا (على الرغم من أننا على الأرجح لا يمكن تسمية الطريقة await ؛ فقط wait سيفعل ذلك على الرغم من أنني أعتقد). شخصيًا ، ربما ينتهي بي الأمر بعمل انتظار لكل سطر وليس سلسلة كثيرًا لأنني أجد السلاسل الطويلة أقل قابلية للقراءة ، لذا ستعمل البادئة أو postfix بالنسبة لي.

[عدل] لقد نسيت أن wait موجود بالفعل ويمنع المستقبل ، لذا اخدش هذه الفكرة.

roland أنا أشير إلى هذا على وجه التحديد ، والذي يناقش كتابة الأوصاف: https://github.com/rust-lang/rust/issues/57640#issuecomment -456023146

rolandsteiner حتى تكتب

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

عندما أكتبها مثل:

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

skade أوه ، لقد قصدت تعليقًا مختلفًا عما كنت أعتقد ، آسف. : stuck_out_tongue:

Pzixel من المحتمل أن يأخذ Generator::resume قيمة في النهاية ، وبالتالي فإن yield expr سيكون نوع غير () .

ومن ثم ، فإن yield سيكون له أيضًا نوع آخر غير ! أو () .

valff ، rolandsteiner أجد أنه من غير المحتمل أن yield قيمة الاستئناف ، التي يصعب ملاءمتها للغة مكتوبة بشكل ثابت دون جعل بناء جملة المولد و / أو سمة مزعجة للعمل بها استخدم النموذج الأصلي مع وسيطات السيرة الذاتية الكلمة الأساسية gen arg للإشارة إلى هذه الوسيطة ، ومن المرجح أن يعمل شيء من هذا القبيل بشكل جيد IMO. من وجهة النظر هذه ، سيظل yield يُرجع () لذلك لا يجب أن يؤثر على مناقشة await كثيرًا.

أعتقد أن await يجب أن يكون إما عامل تشغيل بادئة ، لأن async (وأتوقع أن يكون yield بادئة أيضًا). أو استخدمها كطريقة عادية ، تُستخدم في موضع postfix ، إذن.

ومع ذلك ، لا أحصل على جميع عروض الدراجات هنا: لماذا حتى التفكير في كلمة رئيسية postfix لا يوجد بها Rust أخرى؟ سيجعل اللغة غريبة للغاية.

أيضًا ، في تسلسل العقود الآجلة ، أفهم لماذا قد يكون مثيرًا للاهتمام. لكن التسلسل ينتظر؟ ما الذي يعني حتى؟ مستقبل يعيد مستقبل جديد؟ هل هو حقًا مصطلح شائع جدًا نريد أن يكون لراست هذا المصطلح كمواطن من الدرجة الأولى؟ إذا كنا مهتمين بذلك حقًا ، أعتقد أنه ينبغي علينا:

  1. انتقل إلى الطريقة (مثل foo.await() ). لا تقدم كلمة مفتاحية غريبة بعد الإصلاح ونعلم جميعًا ما تعنيه. يمكننا أيضا أن نربط مع ذلك.
  2. إذا كنا نريد حقًا كلمة رئيسية / loloperator ، فيمكننا تسوية هذا السؤال لاحقًا.

أيضًا ، من أجل إبداء رأيي الشخصي ، أكره await في موضع الكلمات الرئيسية postfix.

تُستخدم البادئة await في لغات أخرى لسبب ما - إنها تطابق اللغة الطبيعية. أنت لا تقول "سأنتظر وصولك". على الرغم من أنني كتبت JavaScript ، فقد أكون متحيزًا بعض الشيء.

سأقول أيضا أن أعتبر await -chaining مبالغا فيه. قيمة async/await على أدوات الدمج المستقبلية هي أنها تسمح للشخص بكتابة رمز غير متزامن بالتسلسل ، باستخدام تركيبات اللغة القياسية مثل if ، match ، إلخ. أنت تريد تفريق انتظار الخاص بك على أي حال.

لا تجعلنا نصنع الكثير من التركيب السحري لـ async/await ، إنه جزء صغير فقط من اللغة. صيغة الطريقة المقترحة (على سبيل المثال foo.await() ) هي IMO متعامدة جدًا مع استدعاءات الطريقة العادية وتأتي على أنها سحرية جدًا. الماكينة proc-macro في مكانها الصحيح ، فلماذا لا تخفيها وراء ذلك؟

ماذا عن استخدام الكلمة الرئيسية await بدلاً من async في تعريف الوظيفة؟ فمثلا:

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

هذا await fn لا يمكن إلا أن يسمى في async السياق:

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

و desugaring إلى let n = (await foo(bar())); .
ثم بالإضافة إلى await الكلمة، سمة Future يمكن تنفيذ await -method لاستخدام await المنطق في موقف لواحق، على سبيل المثال:

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

أيضا ، هل يمكن لأحد أن يشرح لي العلاقة مع المولدات؟ أنا مندهش من تنفيذ async / await قبل أن يتم تنفيذ المولدات (حتى مستقرة).

تمت إضافة مولدات phaazon إلى Rust async / await . لا توجد خطط حالية لتحقيق الاستقرار فيها ، ولا تزال تتطلب RFC غير تجريبي قبل أن تتمكن من ذلك. (أنا شخصياً أرغب في استقرارها ، ولكن يبدو من المحتمل أن تكون على بعد عام أو عامين على الأقل ، وربما لا يتم RFCed إلا بعد استقرار async / await وكان هناك بعض الخبرة في ذلك).

XX أعتقد أنك تكسر دلالات async إذا كنت تستخدم await في تعريف الوظيفة. دعونا نتمسك بالمعايير ، أليس كذلك؟

phaazon هل يمكنك أن تحدد بمزيد من التفصيل كيف يكسر هذا دلالات async ؟

تستخدم معظم اللغات async لتقديم شيء ما سيكون غير متزامن و await ينتظره. لذا فإن وجود await بدلاً من async أمر غريب نوعًا ما بالنسبة لي.

phaazon لا ، ليس بدلاً من ذلك ، ولكن بالإضافة إلى ذلك. مثال كامل:

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())`

إذا كان صحيحًا ، فسيكون من الممكن تنفيذ طريقة الانتظار للاستخدام في سلسلة:

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

في رأيي ، لا يهم إذا كان انتظار postfix "يبدو" سحريًا للغاية وغير مألوف بالنسبة للغة. إذا اعتمدنا بناء جملة الطريقة .await() أو .await!() فإن الأمر يتعلق فقط بشرح أن Future s لديها طريقة .await() أو .await!() التي يمكنك استخدامها أيضًا في انتظارهم في الحالات المنطقية. ليس من الصعب فهم ما إذا كنت تقضي 5 دقائق في التفكير في الأمر ، حتى لو لم تره من قبل.

بالإضافة إلى ذلك ، إذا استخدمنا الكلمة الأساسية البادئة ، فما الذي يمنعنا من كتابة صندوق يحتوي على ما يلي (رمز كاذب غير مكتمل لأنني لا أمتلك البنية التحتية للتعامل مع التفاصيل في الوقت الحالي):

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

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

بهذه الطريقة يمكننا الحصول على انتظار postfix إذا أردنا. أعني ، حتى لو لم يكن ذلك ممكنًا بطريقة ما وكان علينا تنفيذ postfix في انتظار بطريقة سحرية بواسطة المترجم ، فلا يزال بإمكاننا استخدام شيء مثل المثال أعلاه لشرح كيفية عمله في الغالب. لن يكون من الصعب التعلم.

ivandardi فكرت في نفس الشيء. لكن هذا التعريف

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

لا يقول أنه يمكن استدعاء الوظيفة في async -context فقط.

XX نعم ، كما قلت ، تجاهل الرمز

XX ، ivandardi لكل تعريف ، await يعمل فقط في سياق async . لذلك هذا غير قانوني:

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

يجب ان يكون

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

والتي لا يمكن استدعاؤها إلا في سياق async مثل هذا:

await future.await()

أي نوع من يهزم الغرض كله.

الطريقة الوحيدة لإنجاز هذا العمل هي تغيير async تمامًا ، وهو خارج الطاولة.

ivandardi هذا تطور روايتي:

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

`` صدأ
انتظار fn await (self) -> T {// لأن غير متزامن مأخوذ بالفعل
انتظر النفس
}

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

CryZeXX لماذا يشوش لي؟ أنا على حق.

XX ما تحاول القيام به هو تغيير معنى غير متزامن والانتظار تمامًا (على سبيل المثال ، قم بإنشاء سياق await يختلف إلى حد ما عن سياق async ). لا أعتقد أنه سيحصل على دعم كبير من مطوري اللغة

EyeOfPython هذا التعريف لا يعمل كما هو متوقع:

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

اكثر اعجابا:

#[call_only_in_async_context_with_derived_await_prefix]
fn await(self) -> impl Future {
    self
}

أنا أقوم بالتصويت لأنك تجاهلت أن هذا هو بناء جملة افتراضي زائف. وجهة نظرهم بشكل أساسي هي أن Rust يمكن أن يضيف سمة خاصة مثل Drop and Copy التي تفهمها اللغة ، مما يضيف القدرة على استدعاء .await () على النوع الذي يتفاعل بعد ذلك مع النوع تمامًا مثل الكلمة الأساسية المنتظرة. بالإضافة إلى ذلك ، يعتبر التصويت غير ذي صلة بـ RFCs على أي حال ، حيث أن الهدف هو اكتشاف حل موضوعي ، وليس حلًا يعتمد على المشاعر الذاتية مثل التي يتم تصورها من خلال التصويتات المؤيدة / المعارضة.

لا أفهم ما يجب أن يفعله هذا الرمز. ولكن لا علاقة له بكيفية انتظار عدم التزامن الذي يعمل حاليًا. إذا كنت تهتم بذلك ، يرجى إبعاده عن هذا الموضوع وكتابة RFC مخصص له.

CryZe هذا النوع متاح. إنه يسمى المستقبل. وقد تم الاتفاق منذ وقت طويل على أن عدم التزامن / الانتظار هو آلية تستهدف فقط تحويل الكود غير المتزامن. لا يُقصد به أن يكون تدوين ربط عام.

ivandari لا ينتظر postfix الخاص بك ولكنه يخلق مستقبلًا جديدًا. إنها في الأساس دالة هوية. لا يمكنك الانتظار ضمنيًا ، لأنها ليست وظيفة عادية.

@ Matthias247 ما كانوا يحاولون اقتراحه هو طريقة لجعل postfix في انتظار الحصول على صيغة استدعاء الطريقة. بهذه الطريقة ، لا يحتاج Rust إلى إدخال رموز جديدة عشوائية مثل # أو @ أو ... كما فعلنا مع؟ عامل التشغيل ، ولا تزال تبدو طبيعية إلى حد ما:

let result = some_operation().await()?.some_method().await()?;

لذا فإن الفكرة هي أن ننتظر بطريقة ما أن يكون نوعًا خاصًا من الأسلوب على سمة المستقبل التي يراها المحول البرمجي بنفس الطريقة مثل كلمة رئيسية انتظار عادية (والتي لا تحتاج إلى امتلاكها على الإطلاق في هذه الحالة) وتحويل غير المتزامن رمز في مولد من هناك. ثم لديك تدفق التحكم المنطقي المناسب من اليسار إلى اليمين بدلاً من اليسار واليمين باستخدام await some_future()? ولا تحتاج إلى إدخال رموز جديدة غريبة.

(لذا tl ؛ dr: يبدو وكأنه استدعاء طريقة ولكنه في الواقع مجرد انتظار postfix)

هناك شيء لم أره مذكورًا صراحةً عند النظر في تركيب ما بعد الإصلاح وهو انتشار الأخطاء ومجمعات الخيارات. هذا هو المكان الذي يكون فيه الصدأ هو الأكثر اختلافًا عن جميع اللغات المنتظرة - بدلاً من عمليات التتبع التلقائي ، لدينا .map_err()? .

في أشياء خاصة مثل:

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))?;

أقل قابلية للقراءة من (لا أهتم بأي بناء جملة postfix يتم اعتباره):

let parsed = reqwest::get(..)
    .send() await
    .map_err(add_context)?
    .json() await
    .map_err(add_context)?;

_ بالرغم من _ هذا التنسيق الافتراضي يحتوي على عدد من الخطوط أكثر من الأسلوب متعدد المتغيرات. يبدو أن التعبيرات التي لا تحتوي على فترات انتظار متعددة يجب أن تشغل مساحة رأسية أقل مع postfix-wait.

أنا لا أكتب رمزًا غير متزامن في الوقت الحالي ، لذلك أنا أتحدث من الجهل ، آسف للتراكم - إذا لم تكن هذه مشكلة في الممارسة ، فهذا ممتاز. أنا فقط لا أريد أن يكون التعامل مع الأخطاء بشكل صحيح أقل راحة لمجرد أن يكون أكثر تشابهًا مع اللغات الأخرى التي تتعامل مع الأخطاء بشكل مختلف.

نعم هذا ما اعنيه. سيكون أفضل ما في العالمين ، مما يسمح للأشخاص باختيار استخدام البادئة أو postfix في الانتظار اعتمادًا على الموقف.

XX إذا تعذر التعبير عن ذلك في كود المستخدم ،

CryZe ، XX ،

من النظرة الأولى أنه ينحل بسلاسة ، لكنه لا يتطابق على الإطلاق مع فلسفة الصدأ. حتى طريقة sort على التكرارات لا تُرجع self وتكسر سلسلة استدعاء طريقة الفواصل لتوضيح التخصيص. لكنك تتوقع شيئًا ليس أقل تأثيرًا ليتم تنفيذه بطريقة ضمنية تمامًا لا يمكن تمييزها عن استدعاءات الوظائف العادية. لا توجد فرص IMO.

نعم هذا ما اعنيه. سيكون أفضل ما في العالمين ، مما يسمح للأشخاص باختيار استخدام البادئة أو postfix في الانتظار اعتمادًا على الموقف.

لماذا يجب أن يكون هذا الهدف؟ لا يدعم Rust هذا مع جميع عمليات التحكم الأخرى (على سبيل المثال ، break ، continue ، if ، إلخ. لا يوجد سبب محدد ، باستثناء "هذا قد تبدو أجمل في رأي خاص.

بشكل عام ، أود تذكير الجميع بأن await خاص جدًا وليس استدعاءًا للطريقة العادية:

  • قد يتصرف مصحح الأخطاء بشكل غريب عندما يتخطى الانتظار ، لأن المكدس قد يستريح ويعود إلى وضعه الطبيعي. بقدر ما أتذكر ، استغرق الأمر الكثير من الوقت للحصول على تصحيح أخطاء غير متزامن يعمل في C # و Javascript. وهؤلاء دفعوا الفرق التي تعمل على المصححات!
  • يمكن تخزين الكائنات المحلية التي لا تصل إلى نقاط الانتظار على حزمة نظام التشغيل الحقيقية. يجب نقل العناصر التي لم يتم إنشاؤها إلى المستقبل الذي تم إنشاؤه ، والذي سيحدث في النهاية فرقًا في ذاكرة الكومة المطلوبة (حيث سيعيش المستقبل).
  • الاقتراض عبر نقاط الانتظار هي السبب في ضرورة أن تكون العقود الآجلة المولدة !Unpin ، وتسبب الكثير من الإزعاج مع بعض أدوات الدمج والآليات الحالية. قد يكون من الممكن إنشاء عقود آجلة Unpin من async طرق لا تقترض عبر الانتظار في المستقبل. ومع ذلك ، إذا كانت await s غير مرئية ، فلن يحدث ذلك أبدًا.
  • قد تكون هناك مشكلات أخرى غير متوقعة في مدقق الاستعارة ، والتي لا تغطيها الحالة الحالية لعدم التزامن / انتظار.

تضمين التغريدة

الأمثلة التي تقدمها لـ C # ضرورية تمامًا ، ولا شيء مثل السلاسل الطويلة ذات النمط الوظيفي التي نراها غالبًا في Rust. بناء جملة LINQ هو مجرد DSL ، ولا يشبه foo().bar().x().wih_boo(x).camboom().space_flight(); لقد بحثت في Google قليلاً ، وأمثلة C # تبدو حتمية بنسبة 100٪ ، تمامًا مثل معظم لغات البرمجة الشائعة. لهذا السبب لا يمكننا أخذ ما فعلته اللغة X ، لأنها ليست نفسها.

يتلاءم تدوين بادئة IMO تمامًا مع أسلوب الترميز الضروري. لكن Rust يدعم كلا الأسلوبين.

تضمين التغريدة

أنا لا أتفق مع بعض تعليقاتك (والآخرين) حول مشاكل الأسلوب الوظيفي. لإبقاء الأمر موجزًا ​​- دعنا نتفق فقط على أن هناك أشخاصًا لديهم تفضيل قوي تجاه أحدهم أو الآخر.

@ Matthias247 لا ، هناك سبب معين بخلاف جمال المظهر. هذا لأن رست يشجع على التقييد. وقد تقول "أوه ، آليات تدفق التحكم الأخرى لا تحتوي على صيغة postfix ، فلماذا يجب أن تكون الانتظار خاصة؟". حسنًا ، هذا تقييم خاطئ. لدينا تدفق التحكم بعد الإصلاح. إنها فقط داخلية بالنسبة للطرق التي نسميها. Option::unwrap_or يشبه القيام ببيان مطابقة postfix. Iterator::filter يشبه إجراء عبارة postfix if. فقط لأن تدفق التحكم ليس جزءًا من بناء جملة التسلسل نفسه ، فهذا لا يعني أنه ليس لدينا بالفعل تدفق تحكم ما بعد الإصلاح. من وجهة النظر هذه ، فإن إضافة انتظار postfix سيكون في الواقع متسقًا مع ما لدينا. في هذه الحالة ، يمكن أن يكون لدينا شيء أكثر شبهاً بكيفية عمل Iterator وبدلاً من مجرد انتظار postfix خام ، يمكن أن يكون لدينا بعض المجمعات المنتظرة ، مثل Future::await_or . في كلتا الحالتين ، فإن انتظار ما بعد الإصلاح ليس مجرد مسألة مظهر - إنها مسألة تتعلق بالوظائف وجودة الحياة. وإلا فإننا سنستمر في استخدام الماكرو try!() ، أليس كذلك؟

dpc

الأمثلة التي تقدمها لـ C # ضرورية تمامًا ، ولا شيء مثل السلاسل الطويلة ذات النمط الوظيفي التي نراها غالبًا في Rust. صيغة LINQ هي مجرد DSL ، ولا تشبه شيئًا مثل foo (). bar (). x (). wih_boo (x) .camboom (). space_flight ()؛ لقد بحثت في Google قليلاً ، وتبدو أمثلة كود C # حتمية بنسبة 100٪ ، تمامًا مثل معظم لغات البرمجة الشائعة. لهذا السبب لا يمكننا أخذ ما فعلته اللغة X ، لأنها ليست نفسها.

هذا ليس صحيحًا (يمكنك التحقق من أي إطار عمل ، مثل بولي ) ، لكنني لن أجادل في ذلك. أرى العديد من الرموز المتسلسلة في كلتا اللغتين. ومع ذلك، هناك في الواقع هو الشيء الذي يجعل كل شيء مختلف. وهي تسمى سمة Try . لا يوجد شيء مشابه لـ C # لذلك لا يفترض القيام بأي شيء يتجاوز await نقطة. إذا حصلت على استثناء ، فسيتم تغليفه تلقائيًا ورفعه للمتصل.

ليس الأمر كذلك بالنسبة لـ Rust ، حيث يتعين عليك استخدام عامل التشغيل ? يدويًا.

إذا كان لديك أي شيء يتجاوز نقطة await فيجب عليك إما إنشاء عامل تشغيل "مشترك" await? ، وتوسيع قواعد اللغة وما إلى ذلك ، أو يمكنك فقط جعل ما بعد الإصلاح في الانتظار وتصبح الأمور أكثر طبيعية (باستثناء بناء الجملة الغريبة قليلا ، ولكن من يهتم؟).

لذلك أرى حاليًا انتظار postfix كحل أكثر قابلية للتطبيق. الاقتراح الوحيد الذي لدي يجب أن يكون كلمة رئيسية مخصصة مفصولة بمسافات ، وليس await() أو await!() .

أعتقد أنني توصلت إلى سبب وجيه يبرر طريقة .await () العادية. إذا فكرت في الأمر ، فهو لا يختلف عن أي طريقة أخرى للحظر. يمكنك استدعاء طريقة .await () ، يتم إيقاف تنفيذ مؤشر الترابط (ربما الأخضر) وفي وقت ما ، يستأنف وقت التشغيل التنفيذي تنفيذ مؤشر الترابط في مرحلة ما. لذلك لجميع المقاصد والأغراض ، سواء كان لديك قناة Mutex أو std مثل:

let result = my_channel().recv()?.iter().map(...).collect();

أو مستقبل كهذا:

let result = my_future().await()?.iter().map(...).collect();

لا يختلف. كلاهما يحظر التنفيذ في recv () / wait () الخاص بهما وكلاهما يتسلسل بنفس الطريقة. الاختلاف الوحيد هو أن انتظار () ربما يعمل على منفذ مختلف ليس خيوط الوزن الثقيل لنظام التشغيل (ولكن قد يكون جيدًا جدًا في حالة وجود منفذ واحد مترابط). لذا فإن تثبيط الكميات الكبيرة من التسلسل يؤثر على نفس الطريقة بالضبط وربما يؤدي إلى الوبر الكليبي الفعلي.

ومع ذلك ، فإن وجهة نظري هنا هي أنه من الشخص الذي يكتب وجهة نظر هذا الكود ، لا يوجد فرق كبير في .recv () أو .await () ، فكلاهما أسلوبان يمنعان التنفيذ ويعودان بمجرد توفر النتيجة . لذلك بالنسبة لجميع المقاصد والأغراض ، يمكن أن تكون طريقة عادية إلى حد كبير ولا تتطلب كلمة رئيسية كاملة. ليس لدينا كلمة رئيسية recv أو lock لقناة Mutex و std أيضًا.

ومع ذلك ، من الواضح أن rustc يريد تحويل الكود بأكمله إلى مولد. ولكن هل هذا في الواقع ضروري من الناحية اللغوية من وجهة نظر اللغة؟ أنا متأكد تمامًا من أنه يمكن للمرء أن يكتب مترجمًا بديلاً لـ rustc والذي ينفذ في انتظار عبر حظر مؤشر ترابط نظام التشغيل الفعلي (سيحتاج إدخال fn غير متزامن إلى إنتاج مؤشر ترابط ثم الانتظار سيؤدي إلى حظر هذا الموضوع). في حين أن هذا سيكون تنفيذًا ساذجًا وبطيئًا للغاية ، إلا أنه سيتصرف بشكل لغوي بنفس الطريقة بالضبط. لذا فإن حقيقة أن الصدأ الفعلي يتحول إلى مولد ليس ضروريًا من الناحية المعنوية. لذلك أود أن أجادل في الواقع أن تحويل rustc الاستدعاء إلى طريقة .await () إلى مولد يمكن اعتباره تفاصيل تنفيذ محسّنة لـ rustc. بهذه الطريقة يمكنك تبرير .await () كونها طريقة كاملة وليست كلمة رئيسية كاملة ، ولكن لا يزال لديك rustc لتحويل كل شيء إلى مولد.

CryZe بخلاف .recv() يمكننا مقاطعة await بأمان من أي مكان آخر في البرنامج ثم لن يتم تنفيذ الكود بجوار await . هذا فرق كبير وهذا هو السبب الأكثر قيمة لعدم قيامنا بـ await ضمنيًا.

@ I60R ليس أقل صراحة من الانتظار ككلمة رئيسية. لا يزال بإمكانك تمييزه بنفس الطريقة في IDE إذا أردت ذلك. إنه ينقل فقط الكلمة الرئيسية إلى موضع postfix ، حيث يمكنني القول أنه من الصعب تفويتها بالفعل (كما هو الحال بالضبط في الموضع الذي يتوقف فيه التنفيذ).

فكرة مجنونة: انتظار ضمني . لقد انتقلت إلى سلسلة رسائل منفصلة ، لأن هذا بالفعل مشغول جدًا بمناقشة postfix مقابل البادئة.

تضمين التغريدة

أعتقد أنني توصلت إلى سبب وجيه يبرر طريقة .await () العادية. إذا فكرت في الأمر ، فهو لا يختلف عن أي طريقة أخرى للحظر.

يرجى قراءة https://github.com/rust-lang/rust/issues/57640#issuecomment -456147515 مرة أخرى

@ Matthias247 هذه نقاط جيدة للغاية ، ويبدو أنني فاتني تلك.

كل هذا الحديث عن جعل الانتظار في وظيفة أو عضو أو ضمنيًا بطريقة أخرى غير صحيح في الأساس. إنه ليس إجراءً ، إنه تحول.

يقوم المترجم ، على مستوى عالٍ ، سواء كان ذلك من خلال كلمة رئيسية أو ماكرو ، بتحويل التعبيرات المنتظرة إلى تعبيرات إنتاجية خاصة للمولد ، في مكانها ، مع مراعاة الاقتراضات وغيرها من الأشياء على طول الطريق.

يجب أن يكون هذا صريحًا.

يجب أن تكون إما مناسبة قدر الإمكان ككلمة رئيسية ، أو من الواضح إنشاء رمز باستخدام ماكرو.

من السهل جدًا إساءة فهم الطرق السحرية والسمات والأعضاء وما إلى ذلك ، خاصة للمستخدمين الجدد.

الكلمات الرئيسية البادئة في انتظار أو انتظار بادئة الماكرو تبدو وكأنها الطريقة الأكثر قبولًا للقيام بذلك ، وهو أمر منطقي كما تفعل العديد من اللغات الأخرى بهذه الطريقة. لا يتعين علينا أن نكون مميزين لنجعلها جيدة.

تضمين التغريدة

من السهل جدًا إساءة فهم الطرق السحرية والسمات والأعضاء وما إلى ذلك ، خاصة للمستخدمين الجدد.

هل تستطيع التمديد؟ بشكل أكثر تحديدًا على المتغير .await!() ، إن أمكن.

ليس من الصعب قراءة foo.await!() ، في رأيي ، خاصة مع إبراز بناء الجملة المناسب ، والذي لا ينبغي تجاهله.

يسيئون فهم معنى ذلك؟ ما يفعله هو في الأساس ما يلي (تجاهل الغموض من النوع):

trait Future {
    fn await!(self) -> Self::Item {
        await self
    }
}

AKA await this_foo و this_foo.await!() متساويان تمامًا. ما الذي يسهل فهمه بشأن ذلك؟

وحول موضوع المستخدمين الجدد: مستخدمين جدد على ماذا؟ البرمجة بشكل عام ، أو المستخدمين الجدد الصدأ كلغة ولكن مع خلفية لغة البرمجة؟ لأنه إذا كان الأول ، فأنا أشك في أنهم سوف يشتغلون في البرمجة غير المتزامنة مباشرة. وإذا كان الأمر الأخير ، فمن الأسهل شرح دلالات ما بعد الإصلاح (كما هو موضح أعلاه) دون التباس.

بدلاً من ذلك ، إذا تمت إضافة البادئة المنتظرة فقط ، فلن يوقف أي شيء أعرفه عن إنشاء برنامج يأخذ كمدخل كود Rust للنموذج

foo.bar().baz().quux().await!().melo().await!()

ويحولها إلى

await (await foo.bar().baz().quux()).melo()

تضمين التغريدة

.await!() لطيف في بعض الحالات ، وربما يعمل جنبًا إلى جنب مع await!(...) مثل:

macro_rules! await {
    // prefix
    ($fut:expr) => {...}

    // postfix
    ($self:Self) => { await!($self) }
}

ومع ذلك ، لا توجد وحدات ماكرو أسلوب postfix في الوقت الحالي ، وقد لا توجد أبدًا.

إذا كنا نعتقد أن هذا احتمال في المستقبل ، فيجب أن نذهب مع الماكرو await!(...) الآن ونضيف ببساطة postfix في المستقبل عندما يتم تنفيذ ذلك.

سيكون وجود كلتا الماكرو مثاليًا ، ولكن إذا لم تكن هناك نية لتنفيذ وحدات ماكرو postfix في المستقبل ، فمن المحتمل أن تكون الكلمة الرئيسية البادئة await أفضل رهان.

novacrazy يمكنني أن أتفق مع ذلك وهو اقتراحي الأصلي. يجب أن نضيف await!() في الوقت الحالي ونكتشف نموذج postfix مع تقدمنا. من المحتمل أيضًا مناقشة إمكانية وحدات ماكرو postfix أيضًا ، وإذا كان بإمكاننا تخصيص postfix .await!() في اللغة قبل الحصول على دعم ماكرو postfix كامل في اللغة. كندة مثل ما حدث مع ? والسمة Try : تمت إضافتها أولاً كحالة خاصة وبعد ذلك تم توسيعها إلى حالة أكثر عمومية. الشيء الوحيد الذي نحتاج إلى توخي الحذر عند اتخاذ قرار بشأنه هو كيف ستبدو البنية العامة لما بعد الإصلاح ، والتي قد تستحق مناقشة منفصلة.

الكلمات الرئيسية البادئة في انتظار أو انتظار بادئة الماكرو تبدو وكأنها الطريقة الأكثر قبولًا للقيام بذلك ، وهو أمر منطقي كما تفعل العديد من اللغات الأخرى بهذه الطريقة.

من الواضح أنه _ عملي _ ، لكني أتذكر حجتين فقط ، ولا أجدهما مقنعين:

  • هكذا تفعل اللغات الأخرى

    • هذا أمر رائع ، لكننا نقوم بالفعل بأشياء مختلفة مثل عدم التشغيل ما لم - poll ed بدلاً من الركض إلى الأول- await ، لأن الصدأ يختلف اختلافًا جوهريًا لغة

  • يحب الناس رؤية await في بداية السطر

    • لكنها ليست موجودة دائمًا ، لذلك إذا كان هذا هو المكان الوحيد الذي يبدو أنه سيواجه مشاكل

    • من السهل جدًا رؤية await s في foo(aFuture.await, bFuture.await) كما لو كانت بادئة

    • في حالة الصدأ ، يقوم أحدهم بالفعل بمسح _ نهاية_ السطر مقابل ? إذا كنت تبحث في تدفق التحكم

هل فاتني شيء؟

إذا كان النقاش هو "meh ، فإنهم جميعًا متشابهون" ، فأنا أتفق تمامًا مع "حسنًا ، إذا لم نهتم حقًا ، فقد نفعل ما يفعله الآخرون أيضًا". لكنني لا أعتقد أن هذا هو المكان الذي نحن فيه.

scottmcm نعم ، "meh ،

لذلك يجب أن نجد توازنًا لائقًا بين قابلية القراءة والألفة وقابلية الصيانة والأداء. لذلك ، فإن بياني في تعليقي الأخير صحيح ، مع الماكرو await!() إذا كنا نعتزم إضافة وحدات ماكرو لطريقة postfix ( .await!() ) في المستقبل ، أو مجرد كلمة رئيسية بادئة مملة await خلاف ذلك.

أقول ممل ، لأن الملل أمر جيد. نريد أن نبقي أذهاننا بعيدة عن بناء الجملة نفسه عند كتابة التعليمات البرمجية باستخدام هذه.

إذا لم يكن f.await() فكرة جيدة ، فأنا أفضل بناء جملة البادئة.

  1. كمستخدم ، آمل أن تحتوي اللغة التي أستخدمها على عدد قليل من قواعد بناء الجملة ، وباستخدام هذه القواعد ، يمكنني أن أستنتج بشكل موثوق ما قد تفعله. ليست استثناءات هنا وهناك. في Rust ، async في البداية ، await في البداية لن يكون استثناءً. ومع ذلك ، f await ، نموذج نشر الكلمة الرئيسية سيكون. f.await مثل الوصول إلى الحقل ، استثناء . f.await!() على ماكرو postfix لم يظهر مطلقًا في اللغة ، ولا أعرف ما هي الحالات الأخرى التي قد تكون مفيدة ، استثناء . ليس لدينا إجابة كيف ستصبح هذه الصيغ قواعد وليست استثناءات لمرة واحدة .

  2. عندما يكون هناك استثناء ، آمل أن يكون له معنى بديهي. خذ ? كمثال ، والذي يمكن اعتباره استثناء لأنه غير متكرر في اللغات الأخرى. يقرأ f()?.map() تقريبًا مثل حساب f () وهل هذه النتيجة جيدة؟ ? هنا يشرح نفسه. لكن بالنسبة إلى f await أسأل لماذا هو postfix ؟، بالنسبة لـ f.await أطلب هو await a حقل ، f.await!() أسأل لماذا يظهر الماكرو في هذا الموضع؟ إنها لا توفر لي إحساسًا مقنعًا / بديهيًا ، على الأقل للوهلة الأولى.

  3. لتوسيع النقطة الأولى ، فإن Rust ترغب في أن تكون لغة أنظمة. اللاعبون الرئيسيون هنا ، C / C ++ / Go / Java جميعهم ضروريون إلى حد ما. أعتقد أيضًا أن معظم الرجال يبدأون حياتهم المهنية بلغة حتمية ، C / Python / Java وليس Haskell ، وما إلى ذلك ، أعتقد أنه لإقناع مطوري الأنظمة ومطوري الجيل القادم بتبني Rust ، يجب أن يقوم Rust أولاً بأسلوب حتمي ثم وظيفي ، وليس أن تكون وظيفيًا للغاية ولكن ليس لديك شعور حتمي مألوف.

  4. أعتقد أنه لا حرج في تقسيم سلسلة وكتابتها في عدة أسطر. لن يكون مطولاً. إنه مجرد صريح .

عند رؤية كيفية انتقال المناقشة باستمرار من اتجاه إلى آخر (البادئة مقابل postfix) ، قمت بتطوير رأي قوي بأن هناك خطأ ما في الكلمة الرئيسية await ويجب علينا التراجع عنها تمامًا. بدلاً من ذلك ، أقترح الصيغة التالية التي أعتقد أنها ستكون حلاً وسطًا جيدًا للغاية بين جميع الآراء التي تم نشرها في الموضوع الحالي:

// 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();

في الخطوة الأولى ، سنقوم بتنفيذها تمامًا كمشغل بادئة عادي دون أي خطأ في معالجة التركيبات العلوية المحددة:

// 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()?;

في الخطوة الثانية ، سنقوم بتطبيق صيغة مشغل البادئة المؤجلة التي من شأنها أن تسمح بمعالجة الأخطاء بشكل صحيح أيضًا.

// now `go` takes precedence over `?` if present
let response = client.get("https://my_api").go send()?;
let body: MyResponse = response.go into_json()?;

هذا كل شئ.


الآن دعنا نرى بعض الأمثلة الإضافية التي توفر المزيد من السياق:


// 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()?;

هناك مخفي تحت نسخة المفسد مع تمكين تمييز بناء الجملة


// 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 هذا النحو أفضل من البدائل في كل جانب:

✓ الاتساق: يبدو عضويًا جدًا داخل كود Rust ولا يتطلب كسر نمط الكود
✓ التوافق: يتكامل جيدًا مع ميزات الصدأ الأخرى مثل التسلسل ومعالجة الأخطاء
✓ البساطة: إما أن تكون قصيرة ، وصفية ، وسهلة الفهم ، وسهلة الاستخدام
قابلية إعادة الاستخدام: سيكون بناء جملة عامل البادئة المؤجلة مفيدًا في سياقات أخرى أيضًا
✓ التوثيق: يعني أن الإرساليات من جهة الاتصال تتدفق في مكان ما وتنتظر حتى تعود
الألفة: يوفر نمطًا مألوفًا بالفعل ولكنه يفعل ذلك بمقايضات أقل
✓ سهولة القراءة: يقرأ كلغة إنجليزية بسيطة ولا يشوه معنى الكلمات
✓ الرؤية: موقعها يجعل من الصعب للغاية إخفاءها في مكان ما في الكود
✓ قابلية الوصول: يمكن الوصول إليها بسهولة عبر Google
✓ تم اختباره جيدًا: أصبح golang شائعًا في الوقت الحاضر مع بناء جملة متشابه إلى حد ما
✓ مفاجآت: من الصعب أن يساء فهمها وكذلك إساءة استعمال هذه الصيغة
✓ الإرضاء: بعد قدر ضئيل من التعلم ، سيكون كل مستخدم راضيًا عن النتيجة


تحرير: كما أشار ivandardi في التعليق أدناه ، يجب توضيح بعض الأشياء:

1. نعم ، من الصعب بعض الشيء تحليل بناء الجملة هذا ، ولكن في نفس الوقت من المستحيل اختراع بناء جملة هنا لا يحتوي على أي مشاكل في القراءة. يبدو أن بناء الجملة go أقل شرًا هنا ، نظرًا لأنه في موضع البادئة هو نفسه تمامًا كما في البادئة await ، وفي الوضع المؤجل يكون IMO أكثر قابلية للقراءة من postfix await على سبيل المثال:

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

حيث لا يبدو await غامضًا وله ارتباط مختلف عن جميع الكلمات الرئيسية الموجودة ، ولكن يمكن أيضًا أن يصبح مانعًا إذا قررنا تنفيذ كتل await في وقت ما في المستقبل.

2. سيتطلب إنشاء RFC ، وتنفيذ واستقرار بناء جملة "عامل البادئة المؤجلة". لن يكون بناء الجملة هذا خاصًا بالشفرة غير المتزامنة ، ولكن استخدامها في غير متزامن سيكون الدافع الأساسي لها.

3. في صيغة "عامل تشغيل البادئة المؤجلة" ، تعتبر الكلمة الأساسية ذات أهمية لأن:

  • الكلمة الرئيسية الطويلة ستزيد المسافة بين المتغير والطريقة وهو أمر سيء لسهولة القراءة
  • الكلمة الطويلة هي اختيار غريب لعامل البادئة
  • الكلمة الرئيسية الطويلة مطولة بشكل غير ضروري عندما نريد أن تبدو الشفرة غير المتزامنة كمزامنة

على أي حال ، من الممكن تمامًا البدء في استخدام await بدلاً من go ثم إعادة تسميته في إصدار 2022. حتى ذلك الوقت ، من المحتمل جدًا أن يتم اختراع كلمة رئيسية أو عامل تشغيل مختلف. ويمكننا حتى أن نقرر أنه لا يلزم إعادة تسمية على الإطلاق. لقد وجدت await مقروءًا أيضًا.

يوجد مخفي أسفل إصدار المفسد مع استخدام await بدلاً من 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()?;


إذا كنت هنا للتصويت معارضًا ، فتأكد من:

  • أنك فهمتها بشكل صحيح ، لأنني قد لا أكون أفضل المتحدثين لشرح أشياء جديدة
  • أن رأيك ليس متحيزًا ، بشرط أن هناك الكثير من الأحكام المسبقة من حيث نشأ هذا النحو
  • أنك سوف يضع سبب وجيه أدناه، منذ downvotes الصمت سامة للغاية
  • أن سببك لا يتعلق بالمشاعر الفورية ، حيث أنني حاولت تصميم ميزة طويلة المدى هنا

@ I60R

لدي القليل من السيطرة على ذلك.

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

هذا صعب بعض الشيء لتحليل العين. للوهلة الأولى ، كنت تعتقد أننا سنطابق على db.go ، ولكن بعد ذلك هناك شيء أكثر من ذلك وتذهب "أوه ، حسنًا ، التحميل هو طريقة ديسيبل". IMO أن بناء الجملة هذا في الأساس لن يعمل لأنه يجب أن تكون الطرق دائمًا قريبة من الكائن الذي تنتمي إليه ، مع عدم وجود مسافة ومقاطعة الكلمات الرئيسية بينهما.

ثانيًا ، يتطلب اقتراحك تحديد ما هو "بناء جملة عامل البادئة المؤجلة" وربما تعميمه في اللغة أولاً. وإلا فسيكون شيئًا يستخدم فقط للتعليمات البرمجية غير المتزامنة ، وسنعود إلى مناقشة "لماذا لا تستخدم وحدات ماكرو postfix؟" جدا.

ثالثًا ، لا أعتقد أن الكلمة الرئيسية مهمة على الإطلاق. ومع ذلك ، يجب أن نفضل await حيث تم حجز الكلمة الرئيسية لإصدار 2018 ، في حين أن الكلمة الرئيسية go لم يجب أن تخضع لبحث أعمق عن الصناديق.

عند رؤية كيفية انتقال المناقشة باستمرار من اتجاه إلى آخر (البادئة مقابل postfix) ، قمت بتطوير رأي قوي بأن هناك خطأ ما في انتظار الكلمات الرئيسية ويجب علينا التراجع عنها تمامًا.

يمكن أن يكون لدينا انتظار (شبه) ضمني وسيكون كل شيء على ما يرام ، ولكن سيكون من الصعب بيعه في مجتمع Rust ، على الرغم من حقيقة أن الهدف الكامل من الوظائف غير المتزامنة هو إخفاء تفاصيل التنفيذ وجعلها تبدو فقط مثل حظر io one.

dpc أعني ، أفضل طريقة يمكنني التفكير بها تعمل كحل وسط بين الانتظار شبه الضمني والرغبة في انتظار أقسام الكود بشكل صريح هو شيء مشابه لـ unsafe .

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

حيث سيتم تلقائيًا انتظار كل شيء داخل الكتلة await . هذا يحل الحاجة إلى انتظار postfix للتسلسل ، حيث يمكنك فقط انتظار السلسلة بأكملها بدلاً من ذلك. لكن في الوقت نفسه ، لست متأكدًا مما إذا كان هذا النموذج مرغوبًا فيه. ربما يمكن لشخص آخر أن يجد سببًا جيدًا حقًا ضد هذا.

dpc الهدف من الدوال غير المتزامنة ليس إخفاء التفاصيل ، بل جعلها أكثر سهولة في التعامل معها وأسهل في الاستخدام. يجب ألا نخفي مطلقًا العمليات التي يحتمل أن تكون مكلفة ، ويجب أن نستفيد من العقود الآجلة الواضحة حيثما أمكن ذلك.

لا أحد يخلط بين الوظائف غير المتزامنة والوظائف المتزامنة بأي لغة. إنه مجرد تنظيم تسلسل العمليات في شكل أكثر قابلية للقراءة والصيانة ، بدلاً من الكتابة اليدوية لآلة الحالة.

أيضًا ، الانتظار الضمني كما هو موضح في أدوات دمج Future ، والتي كما بينت بالفعل لا تزال قوية بشكل لا يصدق. لا يمكننا الحصول على أي Future وانتظر ذلك ، سيكون ذلك غير فعال للغاية.

صريح ومألوف وقابل للقراءة وقابل للصيانة وأداء: الكلمة الأساسية البادئة await أو الماكرو await!() ، مع ماكرو postfix .await!() في المستقبل.

novacrazy حتى لو تم اختيار البنية المثالية ، ما زلت أرغب في استخدام العقود الآجلة المخصصة

بالطبع يمكنك الاستمرار في استخدام الدمج ، فلا شيء يتم إهماله.

إنه نفس الموقف مثل ? : عادةً ما يستخدم الكود الجديد ? (لأنه أفضل من المجمعات) ، ولكن لا يزال من الممكن استخدام مُجمِّعات الخيار / النتائج (والأكثر مرحًا مثل or_else لا تزال تستخدم

على وجه الخصوص ، يمكن أن يحل غير المتزامن / انتظار محل map و and_then ، ولكن لا يزال من الممكن (وسيظل) استخدام أدوات الدمج المستقبلية.

لا تزال الأمثلة التي تقدمها تبدو رهيبة مقارنة بالمُجمِّعات ،

لا أعتقد ذلك ، أعتقد أنها أكثر وضوحًا ، لأنها تستخدم ميزات Rust القياسية.

نادرًا ما يتعلق الوضوح بعدد الشخصيات ، لكن له علاقة بالمفاهيم العقلية.

مع postfix await يبدو أفضل:

some_op().await?
    .another_op().await?
    .final_op().await

يمكنك مقارنة ذلك بأصلك:

some_op()
    .and_then(|v| v.another_op())
    .and_then(|v2| v2.final_op())

ومع حمل المولد ، من المحتمل أن يكون أبطأ قليلاً وينتج المزيد من كود الماكينة.

لماذا تدعي أنه سيكون هناك المزيد من النفقات غير المتزامنة / الانتظار؟ تم تصميم كل من المولدات وغير المتزامن / انتظار خصيصًا لتكون خالية من التكلفة ومُحسَّنة بشكل كبير.

على وجه الخصوص ، التجميعات غير المتزامنة / انتظارًا إلى آلة حالة مُحسّنة بشكل كبير ، تمامًا مثل أدوات الدمج المستقبلية.

بدلاً من إنشاء آلات مولد السباغيتي هذه تحت الغطاء.

يصنع المحامون المستقبليون أيضًا "آلة حالة السباغيتي" تحت الغطاء ، وهو بالضبط ما صُمموا من أجله.

لا تختلف اختلافًا جوهريًا عن عدم التزامن / الانتظار.

[الدمجون المستقبليون] لديهم القدرة على التحويل إلى كود أكثر كفاءة.

من فضلك توقف عن نشر المعلومات الخاطئة. على وجه الخصوص ، يمكن أن يكون Async / wait أسرع من أدوات الدمج المستقبلية.

إذا اتضح أن المستخدمين ليسوا سعداء بكلمة رئيسية بادئة ، فيمكن تغييرها في إصدار 2019/2020.

كما قال آخرون ، الإصدارات ليست شيئًا تعسفيًا نصنعه عندما نشعر بذلك ، فهي مصممة خصيصًا لتحدث فقط كل بضع سنوات (على أقرب تقدير).

وكما أشار Centril ، فإن التوصل إلى صيغة سيئة لمجرد استبدالها لاحقًا هي طريقة غير فعالة للغاية للقيام بالأشياء.

وتيرة المناقشة عالية جدًا في الوقت الحالي ؛ لذلك في محاولة لإبطاء الأمور والسماح للأشخاص بمواكبة ما فاتهم ، أغلقت المشكلة مؤقتًا . سيتم فتحه خلال يوم واحد. في ذلك الوقت ، يرجى محاولة أن تكون بناء قدر الإمكان للمضي قدمًا.

فتح المشكلة بعد يوم ... يُرجى مراعاة عدم إبداء نقاط بالفعل والاحتفاظ بالتعليقات على الموضوع (على سبيل المثال ، هذا ليس المكان المناسب للنظر في الانتظار الضمني أو أشياء أخرى خارج النطاق ..).

نظرًا لأن هذا كان سلسلة طويلة ، دعنا نسلط الضوء على بعض التعليقات الأكثر إثارة للاهتمام المدفونة فيه.

زودناmehcode برمز عالمي شامل await في كل من مواضع البادئة و postfix: https://github.com/rust-lang/rust/issues/57640#issuecomment -455846086

جادل Centril بشكل مقنع بأن تثبيت await!(expr) يرقى إلى إضافة الديون التقنية عن عمد: https://github.com/rust-lang/rust/issues/57640#issuecomment -455806584

ذكرنا valff أن صيغة الكلمات الرئيسية postfix expr await سوف تتلاءم تمامًا مع النوع العام Ascription: https://github.com/rust-lang/rust/issues/57640#issuecomment -456023146

أبرز quodlibetor أن التأثير النحوي https://github.com/rust-lang/rust/issues/57640#issuecomment -456143523

في نهاية اليوم ، سيحتاج فريق lang إلى إجراء مكالمة هنا. من أجل تحديد الأرضية المشتركة التي قد تؤدي إلى توافق في الآراء ، ربما يكون من المفيد تلخيص المواقف المعلنة لأعضاء فريق lang بشأن القضية الرئيسية لبناء جملة postfix:

  • أعرب Centril عن دعمه لبناء جملة postfix واستكشف عددًا من الاختلافات في هذه البطاقة. لا يريد Centril بشكل خاص تثبيت await!(expr) .

  • أعرب cramertj عن دعمه لبناء جملة postfix وبشكل خاص لبناء جملة الكلمة الأساسية postfix expr await .

  • أعرب joshtriplett عن دعمه لبناء جملة postfix واقترح علينا أيضًا توفير نسخة بادئة.

  • أعرب scottmcm عن دعمه لبناء جملة postfix.

  • withoutboats لا يريد تثبيت بناء جملة ماكرو. بينما نشعر بالقلق حيال استنفاد "ميزانية عدم الإلمام" لدينا ، فإن بدون زوارق تعتبر أن بناء جملة الكلمات الرئيسية postfix expr await لها "لقطة حقيقية".

  • aturon و eddyb و nikomatsakis و pnkfelix لم

إذن الأشياء التي نحتاجها لمعرفة إجابة نعم / لا على:

  • هل يجب أن يكون لدينا صيغة البادئة؟
  • هل يجب أن يكون لدينا بناء جملة بعد الإصلاح؟
  • هل يجب أن يكون لدينا صيغة البادئة الآن ومعرفة بناء جملة postfix لاحقًا؟

إذا ذهبنا إلى بناء جملة البادئة ، فهناك متنافسان رئيسيان أشعر أن الناس يقبلهما أكثر: مفيد وواضح ، كما هو موضح في هذا التعليق . الحجج ضد await!() قوية جدًا ، لذلك أنا أفكر في استبعادها. لذلك نحن بحاجة إلى معرفة ما إذا كنا نريد بناء جملة مفيدًا أم واضحًا. في رأيي ، يجب أن نذهب إلى أي بناء يستخدم الأقواس الأقل بشكل عام.

وبالنسبة إلى بناء الجملة بعد الإصلاح ، فهذا أصعب. هناك أيضًا حجج قوية للكلمة الأساسية postfix await بمسافة. لكن هذا يعتمد بشكل كبير على كيفية تنسيق الكود. إذا كان تنسيق الشفرة سيئًا ، فقد تبدو الكلمة الأساسية postfix await تحتوي على مسافة سيئة للغاية. لذلك أولاً ، سنحتاج إلى افتراض أن جميع رموز Rust سيتم تنسيقها بشكل صحيح باستخدام rustfmt ، وأن rustfmt دائمًا ما يكون مقسمًا إلى أسطر مختلفة ، حتى لو كان مناسبًا في سطر واحد. إذا استطعنا فعل ذلك ، فإن بناء الجملة هذا جيد جدًا ، لأنه يحل مشكلة قابلية القراءة والارتباك مع المسافات في منتصف سلسلة من سطر واحد.

وأخيرًا ، إذا استخدمنا كل من البادئة وما بعد الإصلاح ، فنحن بحاجة إلى معرفة وكتابة دلالات كلتا الصيغتين لمعرفة كيفية تفاعلهما مع بعضهما البعض وكيف يمكن تحويل أحدهما إلى الآخر. يجب أن يكون من الممكن القيام بذلك ، لأن الاختيار بين postfix أو بادئة الانتظار يجب ألا يغير طريقة تشغيل الكود.

لتوضيح عملية التفكير الخاصة بي ، وكيف أقوم بالتعامل مع المقترحات النحوية السطحية وتقييمها. await ،
ها هي الأهداف التي لدي (بدون أي ترتيب للأهمية):

  1. يجب أن يظل await كلمة رئيسية لتمكين تصميم اللغة في المستقبل.
  2. يجب أن يكون بناء الجملة من الدرجة الأولى.
  3. يجب أن يكون الانتظار قابلاً للتسلسل ليؤلف جيدًا باستخدام ? والطرق بشكل عام نظرًا لأنها منتشرة في Rust. لا ينبغي إجبار المرء على عمل ارتباطات مؤقتة let والتي قد لا تكون تقسيمات فرعية ذات مغزى.
  4. يجب أن يكون من السهل الحصول على grep مقابل نقاط الانتظار.
  5. يجب أن يكون من الممكن رؤية نقاط الانتظار في لمحة.
  6. يجب أن تكون أسبقية بناء الجملة بديهية.
  7. يجب أن يتكون بناء الجملة بشكل جيد مع IDEs وذاكرة العضلات.
  8. يجب أن يكون بناء الجملة سهل التعلم.
  9. يجب أن يكون بناء الجملة مريحًا للكتابة.

بعد تحديد (وربما نسيت ...) بعض أهدافي ، إليك ملخص وتقييمي لبعض المقترحات فيما يتعلق بتلك:

  1. يجعل الاحتفاظ بـ await ككلمة رئيسية من الصعب استخدام صيغة تعتمد على الماكرو سواء كانت await!(expr) أو expr.await!() . لاستخدام صيغة الماكرو ، يصبح await كماكرو إما ثابتًا وغير متكامل مع دقة الاسم (على سبيل المثال ، يصبح use core::await as foo; مستحيلًا) ، أو يتم التخلي عن await ككلمة رئيسية.

  2. أعتقد أن وحدات الماكرو لديها شعور غير من الدرجة الأولى تجاهها لأنها تهدف إلى اختراع بناء الجملة في مساحة المستخدم. على الرغم من أنها ليست نقطة تقنية ، إلا أن استخدام بناء الجملة الكلي في مثل هذا التركيب المركزي للغة يعطي انطباعًا غير مصقول. يمكن القول إن بناء الجمل .await و .await() ليسا من الصيغ الأكثر من الدرجة الأولى أيضًا ؛ ولكن ليس من الدرجة الثانية كما تشعر وحدات الماكرو.

  3. تؤدي الرغبة في تسهيل التسلسل إلى جعل أي بناء جملة بادئة يعمل بشكل سيئ ، في حين أن التركيبات اللاحقة للإصلاح تتكون بشكل طبيعي من سلاسل الطريقة و ? وجه الخصوص.

  4. يكون Grepping أسهل عند استخدام الكلمة الرئيسية await سواء كانت postfix أو بادئة أو ماكرو وما إلى ذلك. عند استخدام بناء جملة يعتمد على sigil ، على سبيل المثال ، # ، @ ، ~ ، يصبح الاستيلاء أكثر صعوبة. الفرق ليس كبيرًا ، ولكن .await أسهل قليلاً من grep مقابل await و await حيث يمكن تضمين أي منهما ككلمات في التعليقات بينما من غير المحتمل أكثر .await .

  5. من المحتمل أن يكون من الصعب تحديد Sigils في لمحة بينما await أسهل في الرؤية وخاصة بناء الجملة. لقد تم اقتراح أنه من الصعب التعرف على صيغ تركيب postfix .await أو غيرها من صيغ ما بعد الإصلاح في لمحة أو أن postfix await يقدم قراءة ضعيفة. ومع ذلك ، من وجهة نظري ، يلاحظ scottmcm بحق أن العقود الآجلة للنتائج شائعة وأن .await? يساعد في جذب المزيد من الانتباه إلى نفسه. علاوة على ذلك ، يلاحظ سكوت أن البادئة await تؤدي إلى جمل مسار الحديقة وأن البادئة المنتظرة في منتصف التعبيرات ليست أكثر قابلية للقراءة من postfix. مع ظهور عامل التشغيل ? ، يحتاج مبرمجو Rust بالفعل إلى مسح نهاية السطور للبحث عن تدفق التحكم .

  6. أسبقية .await و .await() ملحوظة لكونها متوقعة تمامًا ؛ إنهم يعملون كنظرائهم في الوصول إلى المجال وطريقة الاتصال. من المفترض أن يكون لماكرو postfix الأسبقية نفسها لاستدعاء الطريقة. وفي الوقت نفسه ، فإن البادئة await لها أسبقية متسقة ويمكن التنبؤ بها وليست مفيدة فيما يتعلق بـ ? (أي await (expr?) ) ، أو أن الأسبقية غير متسقة ومفيدة (على سبيل المثال (await expr)? ). يمكن إعطاء سيجيل الأسبقية المرغوبة (على سبيل المثال أخذ الحدس من ? ).

  7. في عام 2012 ، لاحظ nikomatsakis أن Simon Peyton Jones لاحظ مرة واحدة (ص 56) أنه يغار من "قوة النقطة" وكيف أنها توفر سحر IDE حيث يمكنك تضييق الوظيفة أو المجال الذي تقصده. نظرًا لوجود قوة النقطة في العديد من اللغات الشائعة (مثل Java و C # و C ++ و ..) ، فقد أدى ذلك إلى "الوصول إلى النقطة" المتأصلة في ذاكرة العضلات. لتوضيح مدى قوة هذه العادة ، إليك لقطة شاشة ، بسبب scottmcm ، من Visual Studio مع Re # er:
    Re#er intellisense

    C # ليس لديه انتظار postfix. ومع ذلك ، يعد هذا مفيدًا جدًا بحيث يتم عرضه في قائمة الإكمال التلقائي دون أن يكون بناء جملة صالحًا. ومع ذلك ، قد لا يحدث للكثيرين ، بمن فيهم أنا ، تجربة .aw عندما لا يكون .await هو بناء الجملة السطحي. النظر في فائدة تجربة IDE إذا كانت كذلك. لا تقدم التركيبات التركيبية await expr و expr await هذه الميزة.

  8. من المحتمل أن تقدم سيجيلز معرفة ضعيفة. تستفيد البادئة await من الإلمام بـ C # و JS وما إلى ذلك. ومع ذلك ، لا تفصل هذه البادئة await و ? في عمليات مميزة بينما Rust يفعل. كما أنه ليس امتدادًا للانتقال من await expr إلى expr.await - التغيير ليس جذريًا مثل الذهاب مع sigil. علاوة على ذلك ، نظرًا لقوة النقطة المذكورة أعلاه ، فمن المحتمل أن يتم التعرف على .await بكتابة expr. ورؤية await كخيار أول في النافذة المنبثقة للإكمال التلقائي.

  9. Sigils سهلة الكتابة وستوفر بيئة عمل جيدة. ومع ذلك ، في حين أن .await أطول للكتابة ، فإنه يأتي أيضًا مع نقاط القوة التي يمكن أن تجعل تدفق الكتابة أفضل. يؤدي عدم الاضطرار إلى اقتحام عبارات let تسهيل بيئة العمل بشكل أفضل. البادئة await ، أو ما هو أسوأ من await!(..) ، تفتقر إلى كل من قدرات النقاط وقدرات التسلسل. عند مقارنة .await و .await() و .await!() ، تكون العروض الأولى أكثر إيجازًا.

نظرًا لأنه لا يوجد بناء جملة واحد هو الأفضل في تحقيق جميع الأهداف في وقت واحد ، يجب إجراء مفاضلة. بالنسبة لي ، فإن الصيغة التي تحتوي على معظم الفوائد وأقل عيوب هي .await . من الجدير بالذكر أنه يمكن ربطها بالسلاسل ، وتحافظ على await ككلمة رئيسية ، وهي قابلة للإمساك ، ولها قوى نقطية ، وبالتالي فهي قابلة للتعلم ومريحة ، ولها أسبقية واضحة ، وأخيراً يمكن قراءتها (خاصةً مع التنسيق الجيد والتمييز)

Centril فقط .await كونها وصول ميداني؟ هل من المقبول أن يكون لديك .await كما هو ، أم يجب تفضيل .await() مع الأقواس للإشارة إلى حدوث نوع من العمليات هناك؟ وثالثًا ، مع بناء الجملة .await ، هل سينتظر ذلك الكلمة الأساسية أم مجرد معرف "وصول إلى الحقل"؟

أولاً ، أود فقط أن أؤكد أنك تريد انتظار postfix فقط ، أليس كذلك؟

نعم. الآن على الأقل.

ثانيًا ، كيف يمكن التعامل مع ازدواجية .await كونها وصول ميداني؟

إنه عيب طفيف. هناك اتجاه صعودي كبير في قوة النقطة. التمييز هو شيء أعتقد أن المستخدمين سيتعلمونه بسرعة ، خاصة أنه يتم تمييز الكلمات الرئيسية وسيتم استخدام الإنشاء بشكل متكرر. علاوة على ذلك ، كما أشار سكوت ، سيكون .await? الأكثر شيوعًا مما سيؤدي إلى زيادة تخفيف الموقف. يجب أن يكون من السهل أيضًا إضافة إدخال كلمة رئيسية جديد مقابل await في ملف rustdoc إلى حد كبير كما فعلنا لنقل fn .

هل سيكون من المقبول أن يكون لديك .await كما هو ، أم يجب تفضيل .await() مع الأقواس للإشارة إلى حدوث نوع من العمليات هناك؟

أفضل .await بدون ذيل () ؛ يبدو أن الحصول على () في النهاية أمر غير ضروري في غالبية الحالات ، وأعتقد أنه سيجعل المستخدمين أكثر ميلًا للبحث عن الطريقة.

وثالثًا ، مع بناء الجملة .await ، هل سينتظر ذلك الكلمة الأساسية أم مجرد معرف "وصول إلى الحقل"؟

ستظل await كلمة رئيسية. من المفترض أنك تقوم بتغيير libsyntax بطريقة لا تخطئ عند مواجهة await بعد . ثم تمثيلها بشكل مختلف إما في AST أو عند التخفيض إلى HIR ... .

شكرا لتوضيح كل ذلك! 👍

في هذه المرحلة ، يبدو أن القضية الرئيسية هي ما إذا كان ينبغي اتباع بنية ما بعد الإصلاح. إذا كان هذا هو الاتجاه ، فبالتأكيد يمكننا التضييق في أي اتجاه. عند قراءة القاعة ، سيقبل معظم مؤيدي بناء جملة postfix أي بناء جملة postfix معقول على صيغة البادئة.

عند الدخول في هذا الموضوع ، يبدو أن بناء جملة postfix هو المستضعف. ومع ذلك ، فقد اجتذبت دعمًا واضحًا من أربعة أعضاء من فريق لانج وانفتاحًا من الخمس (كان الأربعة الآخرون صامتين حتى الآن). بالإضافة إلى ذلك ، فقد اجتذب دعمًا كبيرًا من المجتمع الأوسع الذي يعلق هنا.

علاوة على ذلك ، يبدو أن مؤيدي بناء الجملة بعد الإصلاح لديهم قناعة واضحة بأنه أفضل طريق لـ Rust. يبدو أن بعض المشاكل العميقة أو الدحض المقنع للحجج المقدمة حتى الآن سيكون ضروريًا للتراجع عن هذا الدعم.

بالنظر إلى هذا ، يبدو أننا بحاجة إلى الاستماع إلى withoutboats ، الذي كان يتابع هذا الموضوع عن كثب ، ومن أعضاء فريق لانج الأربعة الآخرين. من المحتمل أن تقود وجهات نظرهم هذا الموضوع. إذا كانت لديهم مخاوف ، فإن مناقشة هذه ستكون الأولوية. إذا كانوا مقتنعين بقضية بناء جملة postfix ، فيمكننا الانتقال إلى إيجاد إجماع على أي منها.

عفوًا ، لقد لاحظت للتو تعليقCentril بالفعل ، لذلك

ivandardi نظرًا للهدف (1) من loop {} تعبيرًا حرفيًا مطلقًا. وأتوقع أن يتم تسليط الضوء عليه لجعل الأمر أكثر وضوحًا (مثل https://github.com/rust-lang/rust-enhanced/issues/333 الذي ينتظر القيام به في Sublime).

ما يقلقني هو أن جعل بناء الجملة المنتظر يبدو وكأنه وصول مختلف قد يتسبب في حدوث ارتباك في إصدار 2015 من قاعدة بيانات بدون إدراك. (2015 هو الإعداد الافتراضي عندما يكون غير محدد ، يفتح مشروع شخص آخر ، وما إلى ذلك) طالما أن إصدار 2015 به خطأ واضح عندما لا يكون هناك حقل await (وتحذير إذا كان هناك) ، أعتقد هذا (جنبًا إلى جنب مع حجة Centril الرائعة) يزيل مخاوفي الشخصية بشأن حقل الكلمات الرئيسية (أو الطريقة).

أوه ، والشيء الوحيد الذي يجب أن نقرره أيضًا أثناء وجودنا فيه هو كيف سينتهي الأمر بـ rustfmt بتنسيقه.

let val = await future;
let val = await returns_future();
let res = client.get("https://my_api").await send()?.await json()?;
  1. يستخدم await - check
  2. الدرجة الأولى - تحقق
  3. التسلسل - تحقق
  4. Grepping - تحقق
  5. غير سيجيل - تحقق
  6. الأسبقية - ¹ تحقق
  7. نقطة القوة - ² check
  8. سهل التعلم - تحقق
  9. مريح - تحقق

¹ الأسبقية: المسافة بعد await تجعلها غير واضحة في المركز المؤجل. ومع ذلك فهي تمامًا كما في الكود التالي: client.get("https://my_api").await_send()?.await_json()? . بالنسبة لجميع المتحدثين باللغة الإنجليزية ، فإنه أمر طبيعي أكثر من جميع المقترحات الأخرى

²القوة النقطية: سيتطلب دعمًا إضافيًا لـ IDE لنقل await إلى يسار استدعاء الأسلوب بعد كتابة . أو ? أو ; بعد ذلك. لا يبدو أن التنفيذ صعب للغاية

³ سهل التعلم: في وضع البادئة ، فهو مألوف بالفعل للمبرمجين. في الوضع غير المرئي ، سيكون من الواضح بعد استقرار بناء الجملة هذا


لكن أقوى نقطة في هذه الصيغة هي أنها ستكون متسقة للغاية:

  • لا يمكن الخلط بينه وبين الوصول إلى الممتلكات
  • لها أسبقية واضحة مع ?
  • سيكون لها نفس التنسيق دائمًا
  • إنها تطابق لغة الإنسان
  • لا تتأثر بالحدوث الأفقي المتغير في سلسلة استدعاء الأسلوب *

* الشرح مخفي تحت المفسد


باستخدام متغير postfix ، من الصعب التنبؤ بالدالة التي ستكون async وأين سيكون التكرار التالي لـ await على المحور الأفقي:

let res = client.get("https://my_api")
    .very_long_method_name(param, param, param).await?
    .short().await?;

مع المتغير المؤجل سيكون دائمًا هو نفسه:

let res = client.get("https://my_api")
    .await very_long_method_name(param, param, param)?
    .await short()?;

لن يكون هناك. بين الانتظار وطريقة النداء التالية؟ مثل
get().await?.json() .

في الأربعاء ، 23 يناير 2019 ، 05:06 م كتب I60R < [email protected] :

دع فال = انتظر المستقبل ؛
اسمحوا res = client.get ("https: // my_api") .await send () ؟. wait json () ؟؛

  1. الاستخدامات تنتظر - تحقق
  2. الدرجة الأولى - تحقق
  3. التسلسل - تحقق
  4. Grepping - تحقق
  5. غير سيجيل - تحقق
  6. الأسبقية - ¹ تحقق
  7. نقطة القوة - ² check
  8. سهل التعلم - تحقق
  9. مريح - تحقق

الأسبقية: المساحة تجعلها غير واضحة في الوضع المؤجل. ومع ذلك فهو
تمامًا كما في الكود التالي: client.get ("https: // my_api
") .await_send () ؟. await_json () ؟. بالنسبة لجميع المتحدثين باللغة الإنجليزية ، فهي أكثر
من الطبيعي مع جميع المقترحات الأخرى

²الطاقة النقطية: قد تتطلب دعمًا إضافيًا لـ IDE للانتقال إلى
على يسار طريقة الاتصال بعد. أو؟ يكتب بعد ذلك. لا يبدو أن يكون كذلك
صعب التنفيذ

³ سهل التعلم: في وضع البادئة يكون مألوفًا بالفعل للمبرمجين ،
وفي موقف مذنب سيكون من الواضح بعد أن كان بناء الجملة

استقر

لكن أقوى نقطة في بناء الجملة هي أنها ستكون شديدة جدًا
ثابتة:

  • لا يمكن الخلط بينه وبين الوصول إلى الممتلكات
  • لها أسبقية واضحة مع؟
  • سيكون لها نفس التنسيق دائمًا
  • إنها تطابق لغة الإنسان
  • لا يتأثر بالحدوث الأفقي المتغير في استدعاء الأسلوب
    سلسلة*

* الشرح مخفي تحت المفسد

مع متغير postfix ، من الصعب التنبؤ بالوظيفة غير المتزامنة و
حيث سيكون التكرار التالي للانتظار على المحور الأفقي:

اسمحوا res = client.get ("https: // my_api")

.very_long_method_name(param, param, param).await?

.short().await?;

مع المتغير المؤجل سيكون دائمًا هو نفسه:

اسمحوا res = client.get ("https: // my_api")

.await very_long_method_name(param, param, param)?

.await short()?;

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/57640#issuecomment-456693759 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AIA8Ogyjc8LW6teZnXyzOCo31-0GPohTks5vGAoDgaJpZM4aBlba
.

يجب علينا أيضًا أن نقرر أثناء وجودنا كيف سينتهي الأمر بـ rustfmt بتنسيقه

هذا هو مجال https://github.com/rust-dev-tools/fmt-rfcs ، لذلك أود أن أقول أنه خارج الموضوع بالنسبة لهذا الإصدار. أنا متأكد من أنه سيكون هناك شيء ما يتوافق مع أي إصلاح وأسبقية يتم اختيارها.

@ I60R

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

كيف سيبحث ذلك عن وظائف مجانية؟ إذا كنت أقرأها بشكل صحيح ، فهي في الواقع بادئة await .

أعتقد أن هذا يدل على أننا لا ينبغي أن نتسرع في تشويه سمعة تجربة فريق لغة C #. تضع Microsoft الكثير من التفكير في دراسات قابلية الاستخدام ، ونتخذ قرارًا هنا بناءً على سطر واحد من التعليمات البرمجية وفرضية أن Rust خاص بما يكفي للحصول على بنية مختلفة عن جميع اللغات الأخرى. أنا لا أتفق مع هذه الفرضية - لقد ذكرت أعلاه LINQ وطرق الامتداد شائعة في C #.

ومع ذلك ، قد لا يحدث للكثيرين ، بمن فيهم أنا ، تجربة .aw عندما لا يكون .await هو بناء الجملة السطحي.

~ لا أعتقد أنه سيحدث لأي شخص على دراية باللغات الأخرى. ~ بغض النظر عن ذلك ، هل تتوقع أن تتضمن النافذة المنبثقة للإنجاز كلاً من الأساليب await و Future ؟

[...] ونحن نتخذ قرارًا هنا استنادًا إلى سطر واحد من التعليمات البرمجية وفرضية أن Rust خاص بما يكفي للحصول على صيغة مختلفة من جميع اللغات الأخرى. أنا لا أتفق مع هذا الافتراض [...]

lnicola أريد فقط أن أشير في حال فاتتك أنت والآخرون (من السهل أن تفوتك في طوفان التعليقات): https://github.com/rust-lang/rust/issues/57640#issuecomment -455846086

عدة أمثلة حقيقية من كود الإنتاج الفعلي.

نظراCentril الصورة تعليق أعلاه ، يبدو أن الخيارات لواحق في أقوى الخلاف هي expr await لواحق الكلمة بناء الجملة و expr.await بوستفيكس جملة الميدان (وجملة أسلوب ربما بوستفيكس ليست بها حتى الآن).

بالمقارنة مع الكلمة الأساسية postfix ، يجادل Centril بأن حقل postfix يستفيد من "قوة النقطة" - أن IDEs قد تكون أكثر قدرة على

على العكس من ذلك ، جادل cramertj لصالح بناء جملة الكلمة الأساسية postfix على أساس أن هذا التركيب يوضح أنه ليس استدعاء أسلوب أو وصول إلى حقل. جادل withoutboats أن الكلمة الأساسية postfix هي الأكثر شيوعًا بين خيارات postfix.

فيما يتعلق بـ "قوة النقطة" ، يجب أن نأخذ في الاعتبار أن IDE لا يزال بإمكانه تقديم await كإكمال بعد نقطة ثم إزالة النقطة عند تحديد الإكمال. يمكن أن يلاحظ IDE الذكي بدرجة كافية (على سبيل المثال مع RLS) المستقبل ويقدم await بعد الفراغ.

يبدو أن خيارات الحقل والطريقة postfix تقدم مساحة أكبر للاعتراضات ، بسبب الغموض ، بالمقارنة مع الكلمة الأساسية postfix. وبعض من الدعم لحقل بوستفيكس / الطريقة على الكلمة لواحق يبدو أن تأتي من عدم الارتياح طفيف مع وجود مساحة في طريقة تسلسل، يجب أن نستعرض والانخراط معvalff الصورة ملاحظة أن لواحق الكلمة ( foo.bar() await ) لن يبدو أكثر إثارة للدهشة من إسناد النوع المعمم ( foo.bar() : Result<Vec<_>, _> ). لكليهما ، يستمر التسلسل بعد هذه الفاصلة بين المسافات.

ivandardi ، lnicola ،

لقد قمت بتحديث تعليقي الأخير بمثال مفقود لمكالمة الوظيفة المجانية. إذا كنت تريد المزيد من الأمثلة ، يمكنك الرجوع إلى المفسد الأخير في تعليقي السابق

من أجل مناقشة future.await? مقابل future await? ...

الشيء الذي لم تتم مناقشته كثيرًا هو التجميع المرئي أو الاستيلاء على سلسلة طريقة.

ضع في اعتبارك ( match بدلاً من await لتمييز بناء الجملة)

post(url).multipart(form).send().match?.error_for_status()?.json().match?

مقارنة ب

post(url).multipart(form).send() match?.error_for_status()?.json() match?

عندما أقوم بالمسح الضوئي للسلسلة الأولى مقابل await ، حددت بوضوح وسرعة send().await? وأرى أننا ننتظر نتيجة send() .

ومع ذلك ، عندما أقوم بمسح السلسلة الثانية بصريًا مقابل await ، ألتقط أولاً لرؤية await?.error_for_status() ويجب أن أذهب ، لا ، النسخ الاحتياطي ، ثم توصيل send() await معًا.


نعم ، تنطبق بعض هذه الحجج نفسها على وصف النوع المعمم ، لكن هذه ليست ميزة مقبولة حتى الآن. بينما أحب الكتابة بالنسب بمعنى أشعر أنه في سياق تعبير _ عام (غامض ، أعرف) يجب أن يكون ملفوفًا في أقواس. هذا كل شيء خارج الموضوع لهذه المناقشة على الرغم من. ضع في اعتبارك أيضًا أن مبلغ await في قاعدة الكود له تغيير كبير لكونه أعلى بكثير من مقدار إسناد النوع.

كما يربط اسم النوع المعمم النوع بالتعبير المنسوب من خلال استخدام عامل تشغيل مميز : . تمامًا مثل أي عامل ثنائي آخر ، فإن قراءته توضح (بصريًا) أن التعبيرين عبارة عن شجرة واحدة. في حين أن future await ليس لديه عامل تشغيل ويمكن بسهولة أن يخطئ في أنه future_await خارج نطاق رؤية اسمين لا يفصل بينهما عامل تشغيل إلا إذا كان الأول عبارة عن كلمة رئيسية (تنطبق الاستثناءات ، هذا ليس لـ أقول إنني أفضل بناء جملة البادئة ، لا أفعل).

قارن هذا باللغة الإنجليزية إذا صح التعبير ، حيث يتم استخدام واصلة ( - ) لتجميع الكلمات المرئية التي يمكن تفسيرها بسهولة على أنها منفصلة.

أعتقد أن هذا يدل على أننا لا ينبغي أن نتسرع في تشويه سمعة تجربة فريق لغة C #.

لا أعتقد أن "سريعًا جدًا" و "تشويه السمعة" عادلان هنا. أعتقد أن الشيء نفسه يحدث هنا في حالة عدم التزامن / انتظار RFC نفسها: التفكير بعناية في سبب القيام بذلك بطريقة واحدة ، ومعرفة ما إذا كان التوازن سيخرج بنفس الطريقة بالنسبة لنا. تم ذكر فريق C # صراحةً في واحد:

اعتقدت أن فكرة await هي أنك تريد النتيجة وليس المستقبل
لذلك ربما يجب أن يكون بناء الجملة في انتظار أشبه بتركيب "المرجع"

let future = task()
لكن
let await result = task()

لذلك للتسلسل يجب أن تفعل أي منهما

task().chained_method(|future| { /* do something with future */ })

لكن

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

تعمل جميعها بشكل جيد مع التسلسل ، وكلها تحتوي على سلبيات ليست مجال / طريقة / ماكرو حقيقي.
ولكن نظرًا لأن await ، ككلمة رئيسية ، هو بالفعل شيء مميز ، فلا داعي لجعله أكثر خصوصية.
يجب علينا فقط اختيار الأبسط ، foo.await . كلا من () و !() زائدين عن الحاجة هنا.

تضمين التغريدة

- 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.

@ mehcode

عندما أقوم بمسح السلسلة الأولى ضوئيًا للانتظار ، أحدد بوضوح وسرعة إرسال (). انتظر؟ ونرى أننا ننتظر نتيجة الإرسال ().

أنا أحب الإصدار المتباعد أكثر ، لأنه من الأسهل بكثير رؤية أين يتم بناء المستقبل ، وأين يتم انتظاره. من الأسهل بكثير رؤية ما يحدث ، IMHO

image

مع فصل النقاط يبدو كثيرًا مثل استدعاء الطريقة. لن يساعد تمييز الكود كثيرًا ، وهو ليس متاحًا دائمًا.

أخيرًا ، أعتقد أن التسلسل ليس حالة الاستخدام الرئيسية (باستثناء ? ، لكن foo await? واضح قدر الإمكان) ، ومع انتظار واحد يصبح

post(url).multipart(form).send().match?

ضد

post(url).multipart(form).send() match?

حيث يبدو الأخير أصغر حجمًا في المنظمة البحرية الدولية.

لذا ، إذا قلصنا إلى future await و future.await ، هل يمكننا فقط جعل "النقطة" السحرية اختيارية؟ حتى يتمكن الناس من اختيار الشخص الذي يريدون ، والأهم من ذلك ، وقف الجدل والمضي قدمًا!

ليس ضارًا أن يكون لديك بناء جملة اختياري ، لدى Rust الكثير من هذه الأمثلة. أكثرها شهرة هو المبسط الأخير ( ; أو , أو أيًا كان ، بعد العنصر الأخير ، كما في (A,B,) ) غالبًا ما تكون اختيارية. لم أجد سببًا قويًا لعدم قدرتنا على جعل النقطة اختيارية.

أعتقد أنه سيكون وقتًا مناسبًا للقيام بذلك في Nightly ، والسماح للنظام البيئي بتحديد أيهما أفضل بالنسبة لهم. يمكن أن يكون لدينا نسيج لفرض النمط المفضل ، وجعله قابلاً للتخصيص.

ثم قبل الهبوط إلى Stable ، نراجع استخدام المجتمع ونقرر ما إذا كان علينا اختيار واحد أو ترك الاثنين معًا.

لا أوافق تمامًا على فكرة أن وحدات الماكرو لن تشعر بأنها من الدرجة الأولى بما يكفي لهذه الميزة (لن أعتبرها ميزة أساسية في المقام الأول) وأود إعادة إبراز تعليقي السابق حول await!() (بغض النظر عما إذا كانت البادئة أو postfix) ليست "ماكروًا حقيقيًا" ، ولكن أعتقد في النهاية أن الكثير من الأشخاص يريدون استقرار هذه الميزة في أسرع وقت ممكن ، بدلاً من حظر هذا على وحدات ماكرو postfix وانتظار البادئة في انتظار يصلح لصدأ.

على أي حال ، فإن قفل هذا الموضوع ليوم واحد قد ساعد كثيرًا وسأقوم بإلغاء الاشتراك الآن. لا تتردد في ذكرني إذا كنت ترد مباشرة على إحدى نقاطي.

حجتي ضد بناء الجملة مع . هي أن await ليس حقلاً وبالتالي لا يجب أن يبدو كحقل. حتى مع تمييز بناء الجملة والخط الغامق ، سيبدو وكأنه حقل خاص إلى حد ما ، وحتى التنسيق المخصص لن يسمح بالتأكيد على أنه ليس حقلاً لأن . متبوعًا بالكلمة يرتبط بقوة بالوصول إلى الحقل.

لا يوجد سبب لاستخدام بناء الجملة مع . لتحسين تكامل IDE أيضًا ، حيث سنكون قادرين على تمكين أي بناء جملة مختلف بإكمال . باستخدام شيء مثل ميزة إكمال postfix المتوفرة في Intellij IDEA.

نحتاج إلى إدراك await ككلمة رئيسية للتحكم في التدفق. إذا قررنا بشدة استخدام postfix await ، فيجب أن نفكر في المتغير بدون . وأقترح استخدام التنسيق التالي جنبًا إلى جنب للتأكيد بشكل أفضل على نقاط الحظر مع مقاطعة محتملة:

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)
}

مزيد من الأمثلة

// 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?;

مع تسليط الضوء على بناء الجملة

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?;


يمكن أن يكون هذا أيضًا إجابة على نقطة ivandardi حول التنسيق

أجد كل هذه الأمثلة الخاصة بـ postfix تنتظر أن تكون ضدها أكثر من كونها

قارن (مع العنصر match مثل await لإبراز بناء الجملة):

post(url)?.multipart(form).send()?.match?.error_for_status()?.json().match?

إلى

let value = await post(url)?.multipart(form).send()?.error_for_status()
                 .and_then(|resp| resp.json()) // and then parse JSON

// now handle errors

تلاحظ قلة الانتظار في الوسط؟ لأن التسلسل يمكن حله عادة عن طريق تصميم API أفضل. إذا أرجع send() مستقبلًا مخصصًا به طرق إضافية عليه ، على سبيل المثال ، لتحويل رسائل الحالة غير 200 إلى أخطاء فادحة ، فلا داعي لمزيد من فترات الانتظار. في الوقت الحالي ، في reqwest على الأقل ، يبدو أنه يعمل في نفس المكان وينتج Result عاديًا بدلاً من المستقبل الجديد ، حيث تأتي المشكلة هنا.

انتظار التسلسل هو رمز قوي لرائحة في رأيي ، و .await أو .await() سوف يربك الكثير من المستخدمين الجدد حتى أنه ليس مضحكًا. .await بشكل خاص وكأنه شيء خارج Python أو PHP ، حيث يمكن أن يكون لوصول الأعضاء المتغير سلوك خاص بطريقة سحرية.

يجب أن يكون await مقدمًا واضحًا ، كأن يقول ، "قبل المتابعة ، ننتظر هذا" ليس "وبعد ذلك ، ننتظر هذا". الأخير هو حرفيا المركب and_then .

هناك طريقة أخرى محتملة للتفكير في الأمر وهي اعتبار التعبيرات والعقود الآجلة async / await مثل المتكررين. كلاهما كسول ، ويمكن أن يكون لهما العديد من الحالات ، ويمكن إنهاءه باستخدام بناء الجملة ( for-in للمكررات ، await للعقود الآجلة). لن نقترح امتدادًا نحويًا للمكررات لربطهم معًا كتعبيرات منتظمة مثل ما نحن عليه هنا ، أليس كذلك؟ يستخدمون أدوات التجميع للتسلسل ، ويجب أن تفعل العمليات / العقود الآجلة غير المتزامنة الشيء نفسه ، وتنتهي مرة واحدة فقط. انها تعمل بشكل جيد نوعا ما.

أخيرًا ، والأهم من ذلك ، أريد أن أكون قادرًا على التمرير الرأسي عبر المسافات البادئة القليلة الأولى للدالة وأرى بوضوح مكان حدوث العمليات غير المتزامنة. إن رؤيتي ليست بهذه الروعة ، ووجود فترات انتظار في نهاية السطر أو عالق في المنتصف سيجعل الأمور أكثر صعوبة لدرجة أنني قد أبقى مع المستقبل الخام تمامًا.

يرجى من الجميع قراءة هذه المدونة قبل التعليق.

عدم التزامن / الانتظار لا يتعلق بالراحة. لا يتعلق الأمر بتجنب المجمعات.

يتعلق الأمر بتمكين أشياء جديدة لا يمكن القيام بها باستخدام أدوات الدمج.

لذا فإن أي اقتراحات لـ "دعنا نستخدم الدمج بدلاً من ذلك" هي خارج الموضوع ويجب مناقشتها في مكان آخر.

تلاحظ قلة الانتظار في الوسط؟ لأن التسلسل يمكن حله عادة عن طريق تصميم API أفضل.

لن تحصل على سلسلة جميلة في مثال غير متزامن حقيقي. مثال حقيقي:

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)
})

الموحدين لا يساعدون هنا. يمكن إزالة التعشيش ، لكنه ليس بالأمر السهل (حاولت مرتين وفشلت). لديك أطنان من Either:A(Either:A(Either:A(...))) في النهاية.

OTOH يتم حلها بسهولة مع عدم التزامن / الانتظار .

Pauan كتبته قبل أن أنهي رسالتي. ليكن. لا تذكر الدمج ، لا ينبغي أن يبدو async/await متشابهًا. لا بأس إذا حدث ذلك ، فهناك أشياء أكثر أهمية يجب مراعاتها.


التصويت المنخفض لتعليقي لن يجعل عمليات الدمج أكثر ملاءمة.

من خلال قراءة التعليق من النوع ، كنت أفكر في كيفية ظهوره مع await . نظرًا للحجز الذي أجراه بعض الأشخاص باستخدام .await أو .await() كحقول عضو / وصول للطريقة مربكة ، أتساءل عما إذا كان بناء جملة "إسناد" عام يمكن أن يكون خيارًا ، (على سبيل المثال ، "أعطني ما await يعود ").

استخدام yield مقابل await لتمييز بناء الجملة

let result = connection.fetch("url"):yield?.collect_async():yield?;

مقترنًا بنسب النوع ، مثل:

let result = connection.fetch("url") : yield?
    .collect_async() : yield Vec<u64>?;

المزايا: لا يزال يبدو لطيفًا (IMHO) ، بناء جملة مختلف عن الوصول إلى المجال / الأسلوب.
العيوب: تعارض محتمل مع نوع الإسناد (؟)

foo():yield:yield

تحرير: لقد خطر لي أن استخدام نسختين سيكون أكثر منطقية في المثال المجمع:

let result = connection.fetch("url") : yield?
    .collect_async() : yield : Vec<u64>?;

Pauan سيكون من غير المناسب الإفراط في تطبيق منشور المدونة هذا. يمكن تطبيق المجمعات جنبًا إلى جنب مع بناء الجملة المنتظر لتجنب المشاكل التي يصفها.

كانت المشكلة الأساسية دائمًا هي أن المستقبل القابل للنشر يجب أن يكون 'static ، ولكن إذا التقطت المتغيرات بالرجوع إلى تلك المجمعات ، فسينتهي بك الأمر بمستقبل لم يكن 'static . لكن انتظار مستقبل غير 'static لا يجعلك غير متزامن لست 'static . لذلك يمكنك القيام بأشياء مثل هذا:

`` صدأ
// شريحة: & [T]

اسمح x = انتظار المستقبل .and_then (| i | if slice.contains (i) {async_fn (slice)}) ؛

لذلك ، كنت أخطط لعدم التعليق على الإطلاق ، لكنني أردت إضافة بعض الملاحظات من شخص لا يكتب الكثير من التعليمات البرمجية غير المتزامنة ، ولكن لا يزال يتعين علي قراءتها:

  • البادئة await أسهل بكثير للملاحظة. يحدث هذا عندما تتوقعه (داخل async fn) أو خارجه (في تعبير بعض أجزاء الجسم الكلي).
  • قد تبرز Postfix await كونها كلمة رئيسية بشكل جيد في سلاسل مع كلمات رئيسية قليلة أو معدومة. ولكن بمجرد أن يكون لديك عمليات إغلاق أو ما شابه ذلك مع هياكل التحكم الأخرى ، سيصبح مبلغك awaits أقل وضوحًا ويسهل التغاضي عنه.
  • أجد .await كحقل زائف غريبًا جدًا. إنه ليس مثل أي مجال آخر بأي طريقة تهمني.
  • لست متأكدًا مما إذا كان الاعتماد على اكتشاف IDE وإبراز بناء الجملة فكرة جيدة. هناك قيمة في سهولة القراءة عند عدم توفر تمييز بناء الجملة. بالإضافة إلى المناقشات الأخيرة حول تعقيد القواعد والمشكلات التي ستسببها للأدوات ، قد لا يكون من الجيد الاعتماد على IDEs / المحررين في تصحيح الأمور.

أنا لا أشارك الشعور بأن await!() باعتباره ماكرو "لا يبدو من الدرجة الأولى". وحدات الماكرو هي مواطن من الدرجة الأولى تمامًا في روست. كما أنها ليست موجودة من أجل "اختراع بناء الجملة في كود المستخدم" فقط. العديد من وحدات الماكرو "المضمنة" ليست معرّفة من قبل المستخدم وليست موجودة لأسباب نحوية فقط. format!() موجود لمنح المترجم قوة سلاسل تنسيق التحقق من النوع الثابت. include_bytes!() أيضًا غير معرّف من قبل المستخدم وليس موجودًا لأسباب نحوية فقط ، إنه ماكرو لأنه يحتاج إلى امتداد بناء جملة لأنه ، مرة أخرى ، يقوم بأشياء خاصة داخل المترجم ولا يمكنه يمكن كتابتها بشكل معقول بأي طريقة أخرى دون إضافتها كميزة أساسية للغة.

وهذا نوع من وجهة نظري: وحدات الماكرو هي طريقة مثالية لإدخال وإخفاء سحر المترجم بطريقة موحدة وغير مفاجئة. انتظار مثال جيد على ذلك: فهو يحتاج إلى معالجة خاصة ، لكن مدخلاته مجرد تعبير ، وعلى هذا النحو ، فهو مناسب تمامًا للتحقيق باستخدام الماكرو.

لذلك ، لا أرى حقًا الحاجة إلى كلمة رئيسية خاصة على الإطلاق. ومع ذلك ، إذا كانت ستكون كلمة رئيسية ، فيجب أن تكون مجرد كلمة رئيسية عارية - لا أفهم حقًا الدافع وراء تمويهها كحقل. هذا يبدو خاطئًا ، إنه ليس وصولًا إلى الحقل ، إنه ليس حتى مثل الوصول إلى الحقل عن بعد (على عكس ، على سبيل المثال ، يمكن أن تكون طرق getter / setter مع السكر) ، لذا فإن معاملتها على أنها واحدة لا تتفق مع كيفية عمل الحقول اليوم في اللغة.

@ H2CO3 await! ماكرو جيد بكل طريقة مرغوبة باستثناء خاصيتين:

  1. الأقواس الإضافية مزعجة حقًا عندما تكتب ألفًا آخر من الانتظار. قام Rust بإزالة الأقواس من كتل if/while/... لنفس السبب (على ما أعتقد). قد يبدو الأمر غير مهم في أجهزة الصراف الآلي ، لكن هذا هو الحال بالفعل
  2. عدم التناسق مع async كلمة رئيسية. يجب أن يقوم Async بعمل شيء أكبر من مجرد السماح بوحدة ماكرو واحدة في نص الطريقة. وإلا فقد تكون السمة #[async] أعلى الوظيفة. أعي أن السمة لا تسمح لك لاستخدامه في كتل الخ، ولكنه يوفر بعض التوقعات من مواجهة await الكلمة. قد يكون هذا التوقع ناتجًا عن الخبرة في لغات أخرى ، من يدري ، لكنني أعتقد بقوة أنها موجودة. قد لا تتم معالجتها ، ولكن يجب النظر فيها.

وإلا فقد تكون السمة #[async] أعلى الوظيفة.

لقد كانت كذلك لفترة طويلة ، وأنا آسف لرؤيتها تذهب. نحن نقدم كلمة رئيسية أخرى في حين أن السمة #[async] عملت بشكل جيد تمامًا ، والآن مع وحدات ماكرو إجرائية تشبه السمة مستقرة ، حتى أنها ستكون متسقة مع بقية اللغة على حد سواء من الناحية التركيبية والمعنوية ، حتى لو كانت لا تزال معروفة (وتم التعامل معها بشكل خاص) بواسطة المترجم.

@ H2CO3 ما هو الغرض أو الميزة من وجود await كماكرو من منظور المستخدم ؟ هل هناك أي وحدات ماكرو مضمنة تعمل على تغيير البرامج التي تتحكم في التدفق تحت الغطاء؟

@ I60R الميزة هي التوحيد مع بقية اللغة ، وبالتالي لا داعي للقلق بشأن كلمة رئيسية أخرى ، مع كل الأمتعة التي قد تجلبها مع نفسها - على سبيل المثال ، الأسبقية والتجميع واضحان في استدعاء الماكرو.

لا أفهم سبب أهمية الجزء الثاني - ألا يمكن أن يكون هناك ماكرو مضمن يقوم بشيء جديد؟ في الواقع ، أعتقد أن كل ماكرو مدمج يفعل شيئًا "غريبًا" لا يمكن تحقيقه (بشكل معقول / فعال / إلخ.) بدون دعم المترجم. لن يكون await!() دخيلًا جديدًا في هذا الصدد.

لقد اقترحت سابقًا انتظارًا ضمنيًا ، واقترحت مؤخرًا انتظارًا غير قابل للتسلسل .

يبدو أن المجتمع يؤيد بشدة الانتظار الصريح (أرغب في تغيير ذلك ، لكن ...). ومع ذلك ، فإن دقة الوضوح هي ما يخلق الصيغة المعيارية (أي أن الحاجة إليها عدة مرات في نفس التعبير تؤدي إلى إنشاء نموذج معياري).

[ تحذير : الاقتراح التالي مثير للجدل ، ولكن يرجى إعطائه فرصة صادقة]

أشعر بالفضول إذا كان معظم المجتمع سيتنازل ويسمح "بالانتظار الضمني الجزئي"؟ ربما قراءة الانتظار مرة واحدة لكل سلسلة تعبير واضحة بما فيه الكفاية؟

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

هذا شيء مشابه لقيمة إلغاء الهيكلة ، والذي أسميه التعبير عن تفكيك البنية.

// 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?);

سأكون سعيدًا أيضًا بالتطبيقات الأخرى للفكرة الأساسية ، "قراءة الانتظار مرة واحدة لكل سلسلة تعبير من المحتمل أن تكون صريحة بدرجة كافية". لأنه يقلل من الصيغة المعيارية ولكنه أيضًا يجعل التسلسل ممكنًا باستخدام بناء جملة قائم على البادئة وهو الأكثر شيوعًا للجميع.

yazaddaruvala كان هناك تحذير أعلاه لذا كن حذرًا.

أشعر بالفضول إذا كان معظم المجتمع سيتنازل ويسمح "بالانتظار الضمني الجزئي"؟ ربما قراءة الانتظار مرة واحدة لكل سلسلة تعبير واضحة بما فيه الكفاية؟

  1. لا أعتقد أن معظم المجتمع يريد أي شيء جزئي-ضمني. نهج الصدأ الأصلي أشبه بـ "كل شيء واضح". حتى يلقي u8 -> u32 تتم صراحة.
  2. لا تعمل بشكل جيد مع السيناريوهات الأكثر تعقيدًا. ماذا يجب أن يفعل المترجم بـ Vec<Future> ؟
await response = client.get("https://my_api").send()?.json()?.parse_as::<Vec<String>>()?.map(|x| client.get(x))?;

هل يجب تنفيذ await لكل مكالمة متداخلة؟ حسنًا ، ربما يمكننا أن نجعل انتظار العمل لسطر واحد من التعليمات البرمجية (لذلك لن يتم انتظار mapFunc المتداخلة) ، ولكن من السهل جدًا كسر هذا السلوك. إذا كان الانتظار يعمل لسطر كود واحد ، فإن أي إعادة بناء ديون مثل "استخراج القيمة" يكسرها.

yazaddaruvala

هل أفهم أنك تصحح ما تقصده هو البادئة التي تنتظر أن تكون لها الأسبقية على ? مع إمكانية إضافية لاستخدام انتظار بدلاً من الكلمة الرئيسية let لتغيير السياق لرش كل المستقبل الضمني <_ i = "7"> يعود مع الانتظار؟

معنى هذه العبارات الثلاثة أدناه متكافئة:

// foo.bar() -> Result<impl Future<_>, _>>
let a = await foo.bar()?;
let a = await (foo.bar()?);
await a = foo.bar()?;

وهاتان العبارتان أدناه متساويتان:

// foo.bar() -> impl Future<Result<_, _>>
await a = foo.bar()?;
let a = await (foo.bar())?;

لست متأكدًا من أن بناء الجملة مثالي ، ولكن ربما وجود كلمة أساسية تتغير إلى "انتظار السياق الضمني" يجعل مشاكل الكلمة الأساسية البادئة أقل أهمية. وفي نفس الوقت السماح باستخدام المزيد من التحكم الدقيق في مكان استخدام الانتظار عند الحاجة.

في رأيي ، بالنسبة للبادئة القصيرة المكونة من سطر واحد ، فإن البادئة المنتظرة لها ميزة التشابه مع العديد من لغات البرمجة الأخرى والعديد من اللغات البشرية . إذا تقرر في النهاية أن الانتظار يجب أن يكون لاحقة فقط ، أتوقع أن ينتهي بي الأمر أحيانًا باستخدام ماكرو Await!() (أو أي شكل مشابه لا يتعارض مع الكلمات الرئيسية) ، عندما أشعر أن الاعتماد على سيساعد الإلمام العميق ببنية الجملة الإنجليزية في تقليل العبء الذهني لقراءة / كتابة التعليمات البرمجية. هناك قيمة محددة في أشكال اللاحقة للتعبيرات ذات السلاسل الأطول ، لكن وجهة نظري الصريحة غير القائمة على الحقائق الموضوعية هي أنه مع التعقيد البشري للبادئة التي تنتظر المدعومة باللغة المنطوقة ، فإن فائدة البساطة المتمثلة في وجود شكل واحد فقط لا تفوق بوضوح وضوح الكود المحتمل لوجود كليهما. بافتراض أنك تثق في أن المبرمج سيختار أيهما أكثر ملاءمة لمجموعة التعليمات البرمجية الحالية ، على الأقل.

تضمين التغريدة

لا أعتقد أن معظم المجتمع يريد أي شيء جزئي-ضمني. نهج الصدأ الأصلي أشبه بـ "كل شيء واضح".

أنا لا أراها بنفس الطريقة. أرى أنه دائمًا ما يكون بمثابة حل وسط صريح وضمني. فمثلا:

  • أنواع المتغيرات مطلوبة في نطاق الوظيفة

    • يمكن أن تكون أنواع المتغيرات ضمنية ضمن نطاق محلي.

    • سياقات Clousure ضمنية

    • بالنظر إلى أنه يمكننا بالفعل التفكير في المتغيرات المحلية.

  • مطلوب مدى الحياة لمساعدة مدقق الاستعارة.

    • ولكن يمكن استنتاجها ضمن النطاق المحلي.

    • على مستوى الوظيفة ، لا يلزم أن تكون الأعمار صريحة ، عندما تكون جميعها بنفس القيمة.

  • أنا متأكد من أن هناك المزيد.

يعد استخدام await مرة واحدة فقط لكل تعبير / عبارة ، مع جميع مزايا التسلسل ، حل وسط معقول للغاية بين الصيغة المعيارية الصريحة والمختصرة.

لا تعمل بشكل جيد مع السيناريوهات الأكثر تعقيدًا. ما الذي يجب أن يفعله المترجم باستخدام Vec؟

await response = client.get("https://my_api").send()?.json()?.parse_as::<Vec<String>>()?.map(|x| client.get(x))?;

أود أن أزعم أن المترجم يجب أن يخطئ. هناك اختلاف كبير في النوع لا يمكن استنتاجه. في الوقت نفسه ، المثال الذي يحتوي على Vec<impl Future> لم يتم حله بواسطة أي من التركيبات في هذا الموضوع.

// 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])?;

أنا أزعم أن Vec<impl Future> هو مثال سيئ ، أو يجب علينا الحفاظ على كل بناء جملة مقترح بنفس المعيار. وفي الوقت نفسه ، فإن بناء جملة "انتظار ضمني جزئي" الذي اقترحته يعمل بشكل أفضل من المقترحات الحالية لسيناريوهات أكثر تعقيدًا مثل await (a, b) = (client.get("a")?, client.get("b")?); باستخدام postfix عادي أو بادئة تنتظر let (a, b) = (client.get("a") await?, client.get("b") await?); نتائج في عمليات الشبكة المتسلسلة ، حيث -يمكن تشغيل النسخة الضمنية بشكل متزامن من قبل المترجم desugarding بشكل مناسب (كما أشرت في رسالتي الأصلية).

في عام 2011 ، عندما كانت الميزة async في C # لا تزال قيد الاختبار ، سألت عن await كونه عامل تشغيل بادئة وحصلت على رد من C # Language PM. نظرًا لوجود مناقشة حول ما إذا كان يجب على Rust استخدام عامل تشغيل بادئة ، فقد اعتقدت أنه قد يكون من المفيد نشر هذا الرد هنا. من لماذا يعتبر "انتظار" عامل تشغيل بادئة بدلاً من عامل تشغيل postfix؟ :

كما يمكنك أن تتخيل ، كان بناء جملة تعبيرات الانتظار نقطة نقاش ضخمة قبل أن نستقر على ما هو موجود الآن :-). ومع ذلك ، لم نفكر كثيرًا في عوامل postfix. هناك شيء ما حول postfix (راجع HP caluclators ولغة البرمجة Forth) التي تجعل الأمور أبسط من حيث المبدأ وأقل سهولة في التعامل معها أو قراءتها من الناحية العملية. ربما تكون هذه هي الطريقة التي يغسلنا بها دماغنا ونحن أطفال ...

لقد وجدنا بالتأكيد أن عامل البادئة (مع نكهته الحتمية حرفياً - "افعل هذا!") كان إلى حد بعيد الأكثر بديهية. معظم المشغلين الأحاديين لدينا هم بادئة بالفعل ، ويبدو أن الانتظار يتناسب مع ذلك. نعم ، إنه يغرق في التعبيرات المعقدة ، وكذلك ترتيب التقييم بشكل عام ، نظرًا لحقيقة أن تطبيق الوظيفة ليس postfix أيضًا. تقوم أولاً بتقييم الوسيطات (الموجودة على اليمين) ثم استدعاء الوظيفة (الموجودة على اليسار). نعم ، هناك اختلاف في بناء جملة تطبيق الوظيفة في C # يأتي مع أقواس مدمجة بالفعل ، بينما في الانتظار يمكنك عادةً إسقاطها.

ما أراه على نطاق واسع (وقد تبنته بنفسي) حول الانتظار هو أسلوب يستخدم الكثير من المتغيرات المؤقتة. أنا أميل إلى الأفضل

var bResult = await A().BAsync();
var dResult = await bResult.C().DAsync();
dResult.E()

أو شيء من هذا القبيل. بشكل عام ، عادةً ما أتجنب وجود أكثر من انتظار في جميع التعبيرات باستثناء أبسط التعبيرات (أي أنها جميعًا وسيطات لنفس الوظيفة أو المشغل ؛ ربما تكون هذه الحجج جيدة) وسأتجنب وضع أقواس في انتظار التعبيرات ، مفضلاً استخدام المزيد من السكان المحليين لكلتا الوظيفتين .

منطقي؟

مادس تورجرسن ، C # Language PM


تجربتي الشخصية كمطور C # هي أن بناء الجملة يدفعني إلى استخدام هذا النمط من العبارات المتعددة ، وهذا يجعل الكود غير المتزامن أكثر صعوبة في القراءة والكتابة من المكافئ المتزامن.

yazaddaruvala

أنا لا أراها بنفس الطريقة. أرى أنه دائمًا ما يكون بمثابة حل وسط صريح وضمني

لا يزال صريحًا. يمكنني ربط مقالة جيدة حول الموضوع بالمناسبة .

أود أن أزعم أن المترجم يجب أن يخطئ. هناك اختلاف كبير في النوع لا يمكن استنتاجه. وفي الوقت نفسه ، فإن المثال مع Vecلم يتم حلها بأي من التركيبات في هذا الموضوع.

لماذا يخطئ؟ يسعدني الحصول على Vec<impl future> ، حيث يمكنني بعد ذلك الانضمام معًا. تقترح قيودًا إضافية بدون سبب.

سأجادل بأن Vecهو مثال سيئ ، أو يجب أن نحافظ على كل بناء جملة مقترح بنفس المعيار.

يجب علينا فحص كل حالة. لا يمكننا فقط تجاهل قضايا الحافة لأن "مهلا ، كما تعلم ، لا أحد يكتب هذا على أي حال". يتعلق تصميم اللغة في المقام الأول بحالات الحافة.

chescock أتفق معك ، لكن قيل من قبل ، Rust له فرق كبير جدًا عن C # هنا: في C # ، لا تكتب أبدًا (await FooAsync()).Bar() . لكن في Rust تفعل. وأنا أتحدث عن ? . في C # لديك استثناء ضمني يتم نشره من خلال استدعاءات غير متزامنة. لكن في حالة الصدأ ، يجب أن تكون صريحًا وأن تكتب ? على نتيجة الدالة بعد انتظارها.

بالطبع ، يمكنك أن تطلب من المستخدمين الكتابة

let foo = await bar();
let bar = await foo?.baz();
let bar = bar?;

لكنها أغرب بكثير من

let foo = await? bar();
let bar = await? foo.baz();

يبدو هذا أفضل بكثير ، لكنه يتطلب تقديم تركيبة جديدة من await و ? doint (await expr)? . علاوة على ذلك ، إذا كان لدينا بعض المشغلين الآخرين ، فيمكننا كتابة await?&@# وعمل جميع التركيبات معًا ... يبدو الأمر معقدًا بعض الشيء.

ولكن بعد ذلك يمكننا فقط وضع الانتظار باعتباره postfix وسوف يتناسب بشكل طبيعي مع اللغة الحالية:

let foo = bar() await?;
let bar = foo.baz() await?;

الآن await? رمزان منفصلان ، لكنهما يعملان معًا. يمكن أن يكون لديك await?&@# ولن تهتم باختراقه في المترجم نفسه.

بالطبع ، تبدو أغرب قليلاً من طريقة البادئة ، لكن يقال ، الأمر كله يتعلق باختلاف Rust / C #. قد نستخدم تجربة C # للتأكد من أننا بحاجة إلى صيغة انتظار صريحة من المحتمل أن تحتوي على كلمة رئيسية منفصلة ، ولكن لا ينبغي لنا اتباع نفس الطريقة بشكل أعمى وتجاهل الاختلافات Rust / C #

كنت مؤيدًا للبادئة await بنفسي لفترة طويلة وقمت حتى بدعوة مطوري لغة C # إلى الموضوع (لذلك لدينا معلومات أحدث من عام 2011 😄) ، لكن الآن أعتقد أن postfix await الكلمة الرئيسية هي أفضل طريقة.

لا تنس أن هناك ليلة ، لذا يمكننا إعادة صياغتها لاحقًا إذا وجدنا طريقة أفضل.

لقد خطر لي أننا قد نكون قادرين على الحصول على أفضل ما في العالمين بمزيج من:

  • بادئة كلمة رئيسية async
  • ميزة الماكرو العامة postfix

وهو ما سيتيح معًا تنفيذ ماكرو postfix .await!() بدون سحر المترجم.

في الوقت الحالي ، يتطلب ماكرو postfix .await!() سحر المترجم. ومع ذلك ، إذا تم تثبيت متغير البادئة للكلمة الأساسية await ، فلن يكون هذا صحيحًا بعد الآن: يمكن تنفيذ الماكرو postfix .await!() بشكل تافه كماكرو يسبق وسيطته (تعبير) باستخدام الكلمة الرئيسية await وملفوفة بين قوسين.

مزايا هذا النهج:

  • البادئة await الكلمة الأساسية متاحة ، ويمكن الوصول إليها من قبل المستخدمين الذين هم على دراية بهذا البناء من لغات أخرى
  • يمكن استخدام الكلمة الأساسية غير المتزامنة للبادئة حيث يكون من المرغوب فيه أن "تبرز" الطبيعة غير المتزامنة للتعبير.
  • سيكون هناك متغير postfix .await!() ، يمكن استخدامه في حالات التسلسل ، أو أي موقف آخر يكون فيه بناء جملة البادئة محرجًا.
  • لن تكون هناك حاجة (من المحتمل أن تكون غير متوقعة) لسحر مترجم لمتغير postfix (كما قد تكون مشكلة لحقل postfix أو طريقة أو خيارات الماكرو).
  • ستكون وحدات ماكرو Postfix متاحة أيضًا لحالات أخرى (مثل .or_else!(continue) ) ، والتي تحتاج بالمصادفة إلى بناء جملة الماكرو postfix لأسباب مماثلة (تتطلب خلاف ذلك أن يتم تغليف التعبير السابق بطريقة محرجة وغير قابلة للقراءة).
  • البادئة await الكلمة الأساسية يمكن أن تستقر بسرعة نسبيًا (مما يسمح للنظام البيئي بالتطور) دون الحاجة إلى انتظار تنفيذ وحدات الماكرو postfix. ولكن على المدى المتوسط ​​إلى الطويل ، فإننا لا نزال نترك المجال مفتوحًا لإمكانية انتظار تركيب جملة ما بعد الإصلاح.

nicoburns ، @ H2CO3 ،

لا ينبغي أن نطبق عوامل التحكم في التدفق مثل await!() و .or_else!(continue) كوحدات ماكرو لأنه من منظور المستخدم لا يوجد سبب وجيه للقيام بذلك. على أي حال ، سيكون لدى المستخدمين في كلا النموذجين نفس الميزات تمامًا ويجب أن يتعلموا كليهما ، ولكن إذا تم تنفيذ هذه الميزات كوحدات ماكرو ، فسيقلق المستخدمون أيضًا بشأن سبب تنفيذها بهذه الطريقة بالضبط. من المستحيل عدم ملاحظة ذلك ، لأنه سيكون هناك اختلاف غريب ومصطنع بين مشغلي الدرجة الأولى العاديين والمشغلين المنتظمين الذين يتم تطبيقهم كوحدات ماكرو (نظرًا لأن وحدات الماكرو بحد ذاتها هي من الدرجة الأولى ، لكن الأشياء التي ينفذونها ليست كذلك).

إنها بالضبط نفس الإجابة الخاصة بـ . قبل await : لسنا بحاجة إليها. لا تستطيع وحدات الماكرو تقليد تدفق التحكم من الدرجة الأولى: لديهم شكل مختلف ، لديهم حالات استخدام مختلفة ، لديهم تمييز مختلف ، يعملون بطريقة مختلفة ، وهم يدركون بشكل مختلف تمامًا.

لكل من الميزتين .await!() و .or_else!(continue) يوجد حلان مناسبان ومريحان بتركيبات جميلة: await كلمة رئيسية و none عامل تشغيل. يجب أن نفضل تنفيذها بدلاً من شيء عام ، نادرًا ما يستخدم ، ويبدو قبيحًا مثل وحدات ماكرو postfix.

لا يوجد سبب لاستخدام وحدات الماكرو ، نظرًا لعدم وجود شيء معقد مثل format!() ، لا يوجد شيء نادر الاستخدام مثل include_bytes!() ، لا يوجد DSL مخصص ، لا يوجد إزالة للتكرار في رمز المستخدم ، هناك ليس مطلوبًا بناء جملة يشبه vararg ، وليس هناك حاجة إلى شيء يبدو مختلفًا ، ولا يمكننا استخدام هذه الطريقة لمجرد أنه ممكن.

لا ينبغي أن نطبق عوامل التحكم في التدفق كوحدات ماكرو لأنه من منظور المستخدم لا يوجد سبب وجيه للقيام بذلك.

تم تنفيذ try!() كماكرو. كان هناك سبب جيد لذلك.

لقد وصفت بالفعل سبب جعل await ماكروًا - ليست هناك حاجة لعنصر لغة جديد إذا كان بإمكان الميزة الحالية تحقيق الهدف أيضًا.

لا يوجد شيء معقد مثل format!()

أختلف: format!() لا يتعلق بالمضاعفات ، إنه يتعلق بفحص وقت الترجمة. إذا لم يكن الأمر يتعلق بفحص وقت الترجمة ، فقد تكون وظيفة.

بالمناسبة لم أقترح أنه يجب أن يكون ماكرو postfix. (أعتقد أنه لا ينبغي.)

لكلا الميزتين توجد حلول مناسبة ومريحة مع تركيب جميل

لا ينبغي أن نعطي كل استدعاء وظيفة واحدة ، أو تعبير التحكم في التدفق ، أو ميزة الراحة البسيطة الخاصة به. ينتج عن هذا فقط لغة منتفخة ، مليئة بالصياغة بدون سبب ، وهي عكس "الجميلة" تمامًا.

(لقد قلت ما بوسعي ؛ لن أرد على هذا الجانب من السؤال بعد الآن).

تضمين التغريدة

لقد خطر لي أننا قد نكون قادرين على الحصول على أفضل ما في العالمين بمزيج من:

  • بادئة كلمة رئيسية await
  • ميزة الماكرو العامة postfix

يستلزم هذا جعل await كلمة رئيسية سياقية بدلاً من الكلمة الأساسية الحقيقية الموجودة حاليًا. لا أعتقد أننا يجب أن نفعل ذلك.

وهو ما سيتيح معًا تنفيذ ماكرو postfix .await!() بدون سحر المترجم.

هذا حل أكثر تعقيدًا من حيث التنفيذ من foo await أو foo.await (خاصةً الأخير). لا يزال هناك "سحر" ، بالقدر نفسه ؛ لقد أجريت للتو مناورة محاسبية .

مزايا هذا النهج:

  • البادئة await الكلمة الأساسية متاحة ، ويمكن الوصول إليها للمستخدمين المطلعين على هذا البناء من لغات أخرى.

إذا أردنا إضافة .await!() لاحقًا ، فنحن نمنح المستخدمين المزيد للتعلم (كلاً من await foo و foo.await!() ) وسيسأل المستخدمون الآن "أيهما يجب أن أستخدم متى..". يبدو أن هذا يستهلك قدرًا أكبر من ميزانية التعقيد أكثر من foo await أو foo.await كما تفعل الحلول الفردية.

  • ستكون وحدات ماكرو Postfix متاحة أيضًا لحالات أخرى (مثل .or_else!(continue) ) ، والتي تحتاج بالمصادفة إلى بناء جملة الماكرو postfix لأسباب مشابهة (تتطلب خلاف ذلك أن يتم تغليف التعبير السابق بطريقة محرجة وغير قابلة للقراءة).

أعتقد أن وحدات ماكرو postfix لها قيمة ويجب إضافتها إلى اللغة. هذا لا يعني مع ذلك أنه يجب استخدامها مقابل await ing ؛ كما ذكرت ، هناك .or_else!(continue) والعديد من الأماكن الأخرى حيث ستكون وحدات ماكرو postfix مفيدة.

  • يمكن تثبيت البادئة await الكلمة الأساسية بسرعة نسبيًا (مما يسمح للنظام البيئي بالتطور) دون الحاجة إلى انتظار تنفيذ وحدات ماكرو postfix. ولكن على المدى المتوسط ​​إلى الطويل ، فإننا لا نزال نترك المجال مفتوحًا لإمكانية انتظار تركيب جملة ما بعد الإصلاح.

لا أرى أي قيمة في "ترك الاحتمالات مفتوحة" ؛ قيمة postfix للتكوين وتجربة IDE وما إلى ذلك معروفة اليوم. لست مهتمًا بـ "تثبيت await foo اليوم وآمل أن نتمكن من الوصول إلى إجماع على foo.await!() غدًا".


@ H2CO3

تم تنفيذ try!() كماكرو. كان هناك سبب جيد لذلك.

تم إهمال try!(..) ، مما يعني أننا اعتبرناها غير مناسبة للغة ، على وجه الخصوص لأنها لم تكن postfix. يبدو أن استخدامها كحجة - بخلاف ما يجب ألا نفعله - يبدو غريبًا. علاوة على ذلك ، يتم تعريف try!(..) بدون أي دعم من المترجم .

بالمناسبة لم أقترح أنه يجب أن يكون ماكرو postfix. (أعتقد أنه لا ينبغي.)

await ليست "كل واحدة .." - على وجه الخصوص ، إنها ليست ميزة راحة بسيطة ؛ بدلاً من ذلك ، يتم إضافة ميزة لغوية رئيسية.

تضمين التغريدة

بالإضافة إلى المناقشات الأخيرة حول تعقيد القواعد والمشكلات التي ستسببها للأدوات ، قد لا يكون من الجيد الاعتماد على IDEs / المحررين في تصحيح الأمور.

يهدف بناء الجملة foo.await إلى تقليل مشكلات الأدوات حيث أن أي محرر به أي مظهر من أشكال الدعم الجيد لـ Rust سيفهم بالفعل نموذج .ident . ما يحتاجه المحرر هو إضافة await إلى قائمة الكلمات الرئيسية. علاوة على ذلك ، فإن IDEs الجيدة لديها بالفعل إكمال للكود بناءً على . - لذلك يبدو من الأسهل توسيع RLS (أو ما يعادلها ...) لتوفير await كاقتراح أول عندما يكتب المستخدم . بعد my_future .

بالنسبة إلى تعقيد القواعد ، فإن احتمال تعقيد القواعد النحوية هو الأقل احتمالًا أن يؤدي استخدام .await إلى تعقيد القواعد نظرًا لأن دعم .await في بناء الجملة هو في الأساس تحليل .$ident ثم عدم الخطأ في ident == keywords::Await.name() .

يُقصد بالصيغة foo.await تقليل المشكلات المتعلقة بالأدوات حيث أن أي محرر لديه أي مظهر من مظاهر الدعم الجيد لـ Rust سيفهم بالفعل نموذج .ident. ما يحتاجه المحرر هو إضافة انتظار إلى قائمة الكلمات الرئيسية. علاوة على ذلك ، فإن IDEs الجيدة لديها بالفعل إكمال رمز بناءً على. - لذلك يبدو من الأسهل توسيع RLS (أو ما يعادلها ...) لتوفير انتظار كاقتراح أول عندما يكتب المستخدم. بعد مستقبلي.

أجد هذا يتعارض مع مناقشة القواعد. والمسألة ليست الآن فقط ، ولكن بعد 5 ، 10 ، 20 سنة ، بعد طبعتين. ولكن كما آمل أن تكون قد لاحظت ، فقد أوضحت أيضًا أنه حتى إذا تم تمييز كلمة رئيسية ، فقد تمر نقاط الانتظار دون أن يلاحظها أحد إذا كانت هناك كلمات رئيسية أخرى متضمنة.

أما بالنسبة لتعقيد القواعد ، فمن غير المرجح أن يؤدي استخدام .await إلى تعقيد القواعد نظرًا لأن دعم .await في بناء الجملة هو في الأساس تحليل.

أعتقد أن هذا الشرف ينتمي إلى await!(future) ، لأنه مدعوم بالكامل بالفعل من قبل القواعد.

Centril try! أصبح في النهاية زائداً عن الحاجة لأن عامل التشغيل ? يمكنه فعل المزيد. إنه ليس "غير مناسب للغة". قد لا يعجبك ، وهو ما أقبله. لكن بالنسبة لي هو في الواقع أحد أفضل الأشياء التي اخترعها رست ، وكان أحد نقاط البيع. وأنا أعلم أنه تم تنفيذه بدون دعم المترجم - لكنني أخفقت في معرفة مدى صلة ذلك عند مناقشة ما إذا كان ينفذ التحكم في التدفق أم لا. يفعل بغض النظر.

انتظار ليس "كل واحد .."

لكن الآخرين المذكورين هنا (على سبيل المثال or_else ) هم ، وما زالت وجهة نظري تنطبق على الميزات الرئيسية على أي حال. إن إضافة بناء الجملة فقط من أجل إضافة بناء الجملة ليست ميزة ، لذلك كلما كان هناك شيء يعمل بالفعل في حالة أكثر عمومية ، يجب تفضيله على اختراع تدوين جديد. (أعلم أن الحجة الأخرى ضد وحدات الماكرو هي "أنها ليست postfix". أنا ببساطة لا أعتقد أن فوائد انتظار كونك عامل postfix خاص به عالية بما يكفي لتبرير التكلفة. لقد نجونا من استدعاءات الوظائف المتداخلة. نحن سيكون جيدًا بنفس القدر بعد كتابة بضع وحدات ماكرو متداخلة.)

في الأربعاء 23 يناير 2019 الساعة 09:59:36 مساءً +0000 ، كتب Mazdak Farrokhzad:

  • ستكون وحدات ماكرو Postfix متاحة أيضًا لحالات أخرى (مثل .or_else!(continue) ) ، والتي تحتاج بالمصادفة إلى بناء جملة الماكرو postfix لأسباب مماثلة (تتطلب خلاف ذلك أن يتم تغليف التعبير السابق بطريقة محرجة وغير قابلة للقراءة).

أعتقد أن وحدات ماكرو postfix لها قيمة ويجب إضافتها إلى اللغة. هذا لا يعني مع ذلك أنه يجب استخدامها مقابل await ing ؛ كما ذكرت ، هناك .or_else!(continue) والعديد من الأماكن الأخرى حيث ستكون وحدات ماكرو postfix مفيدة.

السبب الأساسي لاستخدام .await!() هو أنه يبدو وكأنه ماكرو
من الواضح أنه يمكن أن يؤثر على تدفق التحكم.

.await مثل الوصول إلى الحقل ، ويبدو .await() كدالة
الاتصال ، ولا يمكن أن تؤثر عمليات الوصول الميدانية ولا المكالمات الوظيفية على التحكم
تدفق. .await!() كماكرو ، ويمكن أن تؤثر وحدات الماكرو على التحكم
تدفق.

joshtriplett أنا لا يؤثر على تدفق التحكم بالمعنى القياسي. هذه هي الحقيقة الأساسية لتبرير أن المدقق الاقتراض يجب أن تعمل في العقود الآجلة على النحو المحدد (والرقم السري هو المنطق كيف). من وجهة نظر تنفيذ الوظيفة المحلية ، فإن تنفيذ الانتظار يشبه معظم استدعاءات الوظائف الأخرى. يمكنك المتابعة من حيث توقفت ، ولديك قيمة مرتجعة في المكدس.

أجد هذا يتعارض مع مناقشة القواعد. والمسألة ليست الآن فقط ، ولكن بعد 5 ، 10 ، 20 سنة ، بعد طبعتين.

ليس لدي فكرة عما تعنيه بهذا.

أعتقد أن هذا الشرف ينتمي إلى await!(future) ، لأنه مدعوم بالكامل بالفعل من قبل القواعد.

أنا أفهم ، ولكن في هذه المرحلة ، نظرًا للعديد من العيوب الأخرى التي تنطوي عليها هذه البنية ، أعتقد أنها مستبعدة بشكل فعال.

Centril try! أصبح في النهاية زائداً عن الحاجة لأن عامل التشغيل ? يمكنه فعل المزيد. إنه ليس "غير مناسب للغة". قد لا تعجبك ، رغم ذلك ، وهو ما أقبله.

لقد تم إهمالها بشكل صريح والمزيد من الخطأ الصعب كتابة try!(expr) في Rust 2018. قررنا بشكل جماعي جعلها كذلك ، وبالتالي تم اعتبارها غير مناسبة.

السبب الرئيسي لاستخدام .await!() هو أن المظهر مثل الماكرو يوضح أنه يمكن أن يؤثر على تدفق التحكم.

.await مثل الوصول إلى الحقل ، ويبدو .await() وكأنه استدعاء وظيفة ، ولا يمكن أن تؤثر عمليات الوصول إلى الحقول أو استدعاءات الوظائف على تدفق التحكم.

وأعتقد أن الفرق هو أن مستخدمي شيء يتعلم بسهولة نسبيا، خاصة و .await وعادة ما تليها ? (والذي هو التحكم في التدفق وظيفة محلية). بالنسبة لاستدعاءات الوظائف ، وكما ذكرنا سابقًا ، أعتقد أنه من العدل أن نقول إن طرق التكرار هي شكل من أشكال تدفق التحكم. وعلاوة على ذلك، لمجرد ماكرو يمكن أن تؤثر على التحكم في التدفق لا يعني انها لن تفعل ذلك. العديد من وحدات الماكرو لا تفعل ذلك (على سبيل المثال ، dbg! ، format! ، ...). فهم أن .await!() أو .await سيؤثر على تدفق التحكم (بمعنى أضعف بكثير من ? ، وفقًا لملاحظةHeroicKatora ) يتدفق من الكلمة await نفسه.

تضمين التغريدة

هذا يستلزم انتظار كلمة رئيسية سياقية بدلاً من الكلمة الأساسية الحقيقية الموجودة حاليًا. لا أعتقد أننا يجب أن نفعل ذلك.

إيك. هذا مؤلم قليلاً. ربما يمكن أن يكون للماكرو اسم مختلف مثل .wait!() أو .awaited!() (الأخير جميل جدًا لأنه يوضح أنه ينطبق على التعبير السابق).

"وهذا معًا سيسمح بتنفيذ ماكرو postfix .await! () بدون سحر المترجم."
هذا حل أكثر تعقيدًا من حيث التنفيذ من foo wait أو foo.await (خاصةً الأخير). لا يزال هناك "سحر" ، بالقدر نفسه ؛ لقد أجريت للتو مناورة محاسبية.

علاوة على ذلك ، try! (..) يعرف بدون أي دعم من المترجم.

وإذا كان لدينا بادئة await كلمة رئيسية ووحدات ماكرو postfix ، فيمكن أيضًا تنفيذ .await!() (ربما باسم مختلف) بدون دعم المترجم ، أليس كذلك؟ بالطبع لا تزال الكلمة الرئيسية await نفسها تستلزم قدرًا كبيرًا من سحر المترجم ، ولكن سيتم تطبيق ذلك ببساطة على نتيجة ماكرو ، ولا يختلف عن علاقة try!() بـ return كلمة رئيسية.

إذا أردنا إضافة .await! () لاحقًا ، فنحن نمنح المستخدمين المزيد للتعلم (كلاهما في انتظار foo و foo.await! ()) وسيسأل المستخدمون الآن "أيهما يجب أن أستخدمه ومتى ..". يبدو أن هذا يستهلك قدرًا أكبر من الميزانية المعقدة أكثر من foo wait أو foo.await كما تفعل الحلول الفردية.

أعتقد أن التعقيد الذي يضيفه هذا إلى المستخدم ضئيل للغاية (قد يكون تعقيد التنفيذ مسألة أخرى ، ولكن إذا كنا نريد وحدات ماكرو postfix على أي حال ...). كلا النموذجين يقرأان بشكل حدسي ، بحيث عند قراءة أي منهما ، يجب أن يكون واضحًا ما يحدث. بالنسبة للاختيار عند كتابة التعليمات البرمجية ، يمكن للمستندات ببساطة قول شيء مثل:

"هناك طريقتان لانتظار مستقبل في الصدأ: await foo.bar(); و foo.bar().await!() . أيهما يُعد تفضيلًا أسلوبيًا ، ولا يُحدث فرقًا في تدفق التنفيذ"

لا أرى أي قيمة في "ترك الاحتمالات مفتوحة" ؛ قيمة postfix للتكوين وتجربة IDE وما إلى ذلك معروفة اليوم.

هذا صحيح. أعتقد في رأيي أن الأمر يتعلق أكثر بالتأكد من أن تطبيقنا لا يغلق إمكانية وجود لغة موحدة أكثر في المستقبل.

ليس لدي فكرة عما تعنيه بهذا.

لا يهم كثيرا. لكن بشكل عام ، أنا أفضله أيضًا عندما يكون بناء الجملة واضحًا دون تمييز جيد. لذلك تبرز الأشياء في git diff وغيرها من السياقات المماثلة.

من الناحية النظرية نعم ، وبالنسبة للبرامج التافهة نعم ، ولكن في الواقع المبرمجين
بحاجة إلى معرفة مكان نقاط التعليق الخاصة بهم.

للحصول على مثال بسيط ، يمكنك الاحتفاظ بـ RefCell عبر نقطة تعليق ، و
سيكون سلوك برنامجك مختلفًا عما لو كان RefCell
صدر قبل نقطة التعليق. في البرامج الكبيرة سيكون هناك
الدقيقة لا تعد ولا تحصى مثل هذا ، حيث حقيقة أن الوظيفة الحالية
هو تعليق معلومات مهمة.

يوم الأربعاء 23 يناير 2019 الساعة 2:21 مساءً HeroicKatora [email protected]
كتب:

joshtriplett https://github.com/joshtriplett أنا لا أؤثر على السيطرة
يتدفق بالمعنى القياسي. هذه هي الحقيقة الأساسية لتبرير أن
يجب أن يعمل مدقق الاقتراض في العقود الآجلة على النحو المحدد. من وجهة نظر
تنفيذ الوظيفة المحلية ، تنفيذ الانتظار يشبه أي استدعاء وظيفة أخرى.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/57640#issuecomment-456989262 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABGmeqcr2g4olxQhH9Fb9r6g3XRaFUu2ks5vGOBkgaJpZM4aBlba
.

في الأربعاء 23 يناير 2019 الساعة 02:26:07 مساءً -0800 ، كتب Mazdak Farrokhzad:

السبب الأساسي لاستخدام .await!() هو أن المظهر مثل الماكرو يوضح أنه يمكن أن يؤثر على تدفق التحكم.

.await مثل الوصول إلى الحقل ، ويبدو .await() وكأنه استدعاء وظيفة ، ولا يمكن أن تؤثر عمليات الوصول إلى الحقول أو استدعاءات الوظائف على تدفق التحكم.

أعتقد أن التمييز هو شيء سيتعلمه المستخدمون بسهولة نسبيًا

لماذا يجب عليهم ذلك؟

أعتقد أن التمييز هو شيء سيتعلمه المستخدمون بسهولة نسبيًا ، خاصة وأن .await سيتبعه عادةً؟ (وهو تدفق التحكم المحلي للوظيفة).

في كثير من الأحيان نعم ، ولكن ليس دائمًا ، ويجب أن تعمل البنية بالتأكيد في الحالات التي لا يكون فيها هذا هو الحال.

بالنسبة لاستدعاءات الوظائف ، وكما ذكرنا سابقًا ، أعتقد أنه من العدل أن نقول إن طرق التكرار هي شكل من أشكال تدفق التحكم.

ليس بنفس الطريقة مثل return أو ? (أو حتى break ). سيكون من المفاجئ جدًا أن يقوم استدعاء دالة بإرجاع مستويين من الوظيفة التي يطلق عليها (أحد أسباب عدم وجود استثناءات لـ Rust هو على وجه التحديد لأن هذا مفاجئ) ، أو كسر حلقة في الاستدعاء وظيفة.

علاوة على ذلك ، لا يعني مجرد تأثير الماكرو على تدفق التحكم أنه سيفعل ذلك. العديد من وحدات الماكرو لا تفعل ذلك (مثل dbg !، format !، ...). يتدفق فهم أن .await! () أو .await سيؤثر على تدفق التحكم (بمعنى أضعف بكثير من؟ ، وفقًا لملاحظةHeroicKatora ) يتدفق من الكلمة انتظار نفسها.

أنا لا أحب هذا على الإطلاق. يعد تأثير التحكم في التدفق من الآثار الجانبية المهمة حقًا . يجب أن يكون لها شكل نحوي مختلف للتركيبات التي لا تستطيع عادة القيام بذلك. التركيبات التي يمكنها القيام بذلك في Rust هي: الكلمات الرئيسية ( return ، break ، continue ) ، عوامل التشغيل ( ? ) ، ووحدات الماكرو (عن طريق التقييم إلى واحد من الأشكال الأخرى). لماذا تعكر المياه بإضافة استثناء؟

ejmahler لا أرى كيف يختلف إجراء RefCell للمقارنة باستدعاء الوظيفة العادية. قد يكون أيضًا أن الوظيفة الداخلية تريد الحصول على نفس RefCell. ولكن لا يبدو أن أي شخص لديه مشكلة كبيرة في عدم استيعاب الرسم البياني الكامل للمكالمات المحتملة على الفور. وبالمثل ، لا تلقى المشكلات المتعلقة باستنفاد المكدس أي اهتمام خاص بضرورة وضع تعليق توضيحي على مساحة المكدس المتوفرة. هنا المقارنة ستكون مواتية للكوروتين! هل يجب أن نجعل استدعاءات الوظائف العادية أكثر وضوحًا إذا لم تكن مضمنة؟ هل نحتاج إلى مكالمات ذيل صريحة للحصول على هذه المشكلة المتزايدة الصعوبة في المكتبات؟

الجواب ، imho ، أن الخطر الأكبر عند استخدام RefCell والانتظار هو عدم الإلمام. عندما يمكن تجريد المشكلات الأخرى المذكورة أعلاه بعيدًا عن الأجهزة ، أعتقد أننا كمبرمجين يمكننا أيضًا تعلم عدم التمسك بـ RefCell وما إلى ذلك عبر نقاط العائد باستثناء ما تم فحصه.

في الأربعاء 23 يناير 2019 الساعة 10:30:10 مساءً +0000 ، كتب إليوت مالر:

من الناحية النظرية نعم ، وبالنسبة للبرامج التافهة نعم ، ولكن في الواقع المبرمجين
بحاجة إلى معرفة مكان نقاط التعليق الخاصة بهم.

: +1:

تضمين التغريدة

يتمثل الاختلاف الجوهري في مقدار التعليمات البرمجية التي يجب فحصها وعددها
الاحتمالات التي عليك حسابها. المبرمجين معتادون بالفعل على
التأكد من عدم استخدام أي شيء يسمونه أيضًا RefCell الذي قاموا بفحصه
خارج. ولكن مع الانتظار ، بدلاً من فحص مكدس مكالمات واحد ، لا يوجد لدينا
السيطرة على ما يتم تنفيذه أثناء الانتظار - حرفيا أي سطر من
قد يتم تشغيل البرنامج. حدسي يخبرني أن متوسط ​​المبرمج هو
أقل استعدادًا للتعامل مع هذا الانفجار في الاحتمالات ، كما هم
يجب أن تتعامل معها بشكل أقل تكرارًا.

للحصول على مثال آخر على أن النقاط المعلقة هي معلومات مهمة ، في اللعبة
البرمجة ، نكتب عادةً coroutines التي يُقصد استئنافها
مرة واحدة لكل إطار ، ونقوم بتغيير حالة اللعبة في كل سيرة ذاتية. إذا أغفلنا أ
نقطة التعليق في إحدى هذه الكوروتينات ، قد نترك اللعبة في حالة كسر
دولة لإطارات متعددة.

يمكن التعامل مع كل هذه الأمور بالقول "كن أكثر ذكاءً واعمل أقل
بالطبع ، لكن هذا النهج لا يعمل تاريخيًا.

يوم الأربعاء 23 يناير 2019 الساعة 2:44 مساءً Josh Triplett [email protected]
كتب:

في الأربعاء 23 يناير 2019 الساعة 10:30:10 مساءً +0000 ، كتب إليوت مالر:

من الناحية النظرية نعم ، وبالنسبة للبرامج التافهة نعم ، ولكن في الواقع المبرمجين
بحاجة إلى معرفة مكان نقاط التعليق الخاصة بهم.

: +1:

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/57640#issuecomment-456995913 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABGmep05nhZizdE5xil4FsnpONJCxqn-ks5vGOXhgaJpZM4aBlba
.

للحصول على مثال آخر على أن نقاط التعليق هي معلومات مهمة ، في برمجة الألعاب ، نكتب عادةً coroutines التي يُقصد استئنافها مرة واحدة لكل إطار ، ونغير حالة اللعبة في كل استئناف. إذا أغفلنا نقطة تعليق في إحدى هذه النماذج ، فقد نترك اللعبة في حالة معطلة لإطارات متعددة.

إذا قمت بإسقاط المفتاح في SlotMap ، فستسرب الذاكرة. اللغة نفسها لا تساعدك في هذا بطبيعتها. ما أريد أن أقوله هو ، أن الأمثلة الخاصة بك عن مدى الغموض الذي لا يمكن رؤيته بالانتظار (طالما أنها كلمة رئيسية سيكون حدوثها فريدًا) لا يبدو أنها تعمم على المشكلات التي لم تحدث لميزات أخرى. أعتقد أنه يجب عليك تقييم الانتظار بدرجة أقل بشأن الميزة التي تمنحك إياها اللغة على الفور والمزيد حول كيفية قيامها بالتعبير عن ميزاتك الخاصة. قم بتقييم أدواتك ثم قم بتطبيقها. اقتراح حل لحالة اللعبة ، وكيفية معالجة ذلك بشكل جيد بما فيه الكفاية: اكتب غلافًا يؤكد عند العودة أن حالة اللعبة قد حددت في الواقع المبلغ المطلوب ، يتيح لك غير المتزامن استعارة حالتك الخاصة لهذا السبب. تنتظر حظر أخرى في هذا القانون المجزأة.

لا يأتي انفجار الحالة في RefCell من عدم التحقق من عدم استخدامه في أي مكان آخر ولكن من نقاط رمز أخرى لا تعرف أنك اعتمدت على هذا الثابت وإضافته بصمت. يتم تناولها بنفس الطريقة ، التوثيق ، التعليقات ، التغليف الصحيح.

لا يختلف الاحتفاظ بـ RefCell كثيرًا عن الضغط على Mutex عند تنفيذ حظر io. يمكنك الحصول على طريق مسدود ، وهو أسوأ وأصعب في تصحيحه من الذعر. ومع ذلك ، لا نعلق على عمليات الحظر باستخدام كلمة رئيسية صريحة block . :).

ستؤدي مشاركة RefCell بين وظيفة غير متزامنة إلى الحد بشكل كبير من قابليتها للاستخدام العام ( !Send ) ، لذلك لا أعتقد أنها ستكون شائعة. ويجب تجنب RefCell بشكل عام ، وعند استخدامها ، يجب اقتراضها لأقصر وقت ممكن.

لذلك لا أعتقد أن التلاعب في RefCell بالصدفة على نقطة العائد هو صفقة كبيرة.

إذا أغفلنا نقطة تعليق في إحدى هذه النماذج ، فقد نترك اللعبة في حالة معطلة لإطارات متعددة.

تعد وظائف Coroutines والوظائف غير المتزامنة شيئًا مختلفًا. نحن لا نحاول جعل yield ضمنيًا.

لأي شخص يفضل بناء جملة الماكرو بدلاً من بناء جملة الكلمات الرئيسية هنا: يرجى وصف ما هو حول await وهذا يعني أنه يجب أن يكون ماكرو وكيف يختلف هذا ، على سبيل المثال ، عن while ، والذي _ يمكن أن يكون لقد كان مجرد ماكرو while! ( حتى باستخدام macro_rules! ؛ لا يوجد غلاف خاص على الإطلاق).

تحرير: هذا ليس كلاميا. أنا مهتم حقًا بشيء من هذا القبيل ، بالطريقة نفسها التي اعتقدت أنني أردت انتظارها أولاً (مثل C #) لكن RFC أقنعني بخلاف ذلك.

لأي شخص يفضل بناء جملة الماكرو بدلاً من بناء جملة الكلمات الرئيسية هنا: يرجى وصف ما يتعلق بالانتظار وهذا يعني أنه يجب أن يكون ماكرو وكيف يختلف ذلك ، على سبيل المثال ، عن الوقت الذي قد يكون مجرد فترة! ماكرو (حتى باستخدام وحدات macro_rules فقط! ؛ لا يوجد غلاف خاص على الإطلاق).

أتفق في الغالب مع هذا المنطق ، وأريد استخدام كلمة رئيسية لبناء جملة البادئة.

ومع ذلك ، كما هو موضح أعلاه (https://github.com/rust-lang/rust/issues/57640#issuecomment-456990831) ، فأنا أؤيد أيضًا وجود ماكرو postfix لبناء جملة postfix (ربما .awaited!() لتجنب تضارب الأسماء مع البادئة الأساسية). كان تفكيري جزئيًا هو أنه يبقي الأمور أبسط للحصول على كلمة رئيسية لا يمكن استخدامها إلا بطريقة واحدة ، مع توفير المزيد من الاختلافات لوحدات الماكرو ، ولكن نظرًا لأنني لم أستطع إيجاد حل لما بعد الإصلاح بشكل أساسي بنية الكلمات الرئيسية التي أعتبرها مقبولة:

  • foo.bar().await و foo.bar().await() الناحية الفنية كلمات رئيسية ، لكنها تبدو مثل وصول حقل أو استدعاء طريقة ، ولا أعتقد أنه يجب إخفاء بنية تعديل تدفق التحكم على هذا النحو.
  • foo.bar() await محير فقط ، خاصة مع مزيد من التسلسل مثل foo.bar() await.qux() . لم أر مطلقًا كلمة رئيسية مستخدمة مثل postfix (أنا متأكد من وجودها ، لكنني في الواقع لا أستطيع التفكير في مثال واحد بأي لغة أعرفها من الكلمات الرئيسية التي تعمل مثل هذا). وأعتقد أن القراءة مربكة للغاية كما لو أن فترة الانتظار تنطبق على qux() وليس foo.bar() .
  • foo.bar()@await أو قد تعمل بعض علامات الترقيم الأخرى. لكنها ليست لطيفة بشكل خاص ، وتشعر بأنها مخصصة جدًا. لا أعتقد في الواقع أن هناك أي مشكلات مهمة في بناء الجملة هذا (على عكس الخيارات المذكورة أعلاه). أشعر فقط أنه بمجرد أن نبدأ في إضافة سيجيلز ، يبدأ التوازن في الميل نحو التخلي عن بناء الجملة المخصص ، ونحوها إلى كونها ماكرو.

سأكرر فكرة سابقة مرة أخرى ، على الرغم من تعديلها بأفكار واعتبارات جديدة ، ثم أحاول التزام الصمت.

await ككلمة رئيسية صالحة فقط داخل دالة async أي حال ، لذا يجب أن نسمح بالمعرف await أي مكان آخر. إذا تم تسمية متغير باسم await في نطاق خارجي ، فلا يمكن الوصول إليه من داخل كتلة async إلا إذا تم استخدامه كمعرف أولي. أو ، على سبيل المثال ، الماكرو await! .

علاوة على ذلك ، فإن تكييف الكلمة الرئيسية مثل هذا سيسمح بنقطتي الرئيسية: يجب أن نسمح بخلط ومطابقة المولدات والوظائف غير المتزامنة ، مثل:

// 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);
}

أعتقد أن هذا شفاف بدرجة كافية للمستخدمين المتقدمين للغاية الذين يرغبون في القيام بأشياء غير تقليدية ، ولكنه يسمح للمستخدمين الجدد والمعتدلين باستخدام ما هو ضروري فقط 2 و 3 ، إذا تم استخدامها بدون أي بيانات yield ، فهي متطابقة بشكل فعال.

أعتقد أيضًا أن البادئة await? هي اختصار رائع لإضافة ? إلى نتيجة العملية غير المتزامنة ، لكن هذا ليس ضروريًا تمامًا.

إذا أصبحت وحدات ماكرو postfix شيئًا رسميًا (وهو ما آمل أن يفعلوه) ، فيمكن أن يتواجد كل من await!(...) و .await!() معًا ، مما يسمح بثلاث طرق مكافئة للقيام بالانتظار ، لحالات وأنماط استخدام محددة . لا أعتقد أن هذا من شأنه أن يضيف أي عبء معرفي ، لكنه سيسمح بمزيد من المرونة.

من الناحية المثالية ، يمكن لـ async fn و #[async] fn* مشاركة رمز التنفيذ لتحويل المولد الأساسي إلى آلة حالة غير متزامنة.

لقد أوضحت هذه المشكلات أنه لا يوجد أسلوب مفضل واحد حقيقي ، لذا فإن أفضل ما يمكنني أن أتمناه هو توفير مستويات من التعقيد تتسم بالنظافة والمرونة وسهولة القراءة. أعتقد أن المخطط أعلاه هو حل وسط جيد للمستقبل.

await ككلمة رئيسية صالحة فقط داخل دالة async أي حال ، لذا يجب أن نسمح للمعرف بالانتظار في مكان آخر.

لا أعرف ما إذا كان ذلك عمليًا في Rust ، بالنظر إلى وحدات الماكرو الصحية. إذا اتصلت بـ foo!(x.await) أو foo!(await { x }) عندما تريد expr ، أعتقد أنه يجب أن يكون واضحًا أن الكلمة الرئيسية await مطلوبة - وليس await الحقل أو التعبير الحرفي الهيكل await مع اختصار الحرف الأول - حتى في الطريقة المتزامنة.

السماح بثلاث طرق مكافئة لعمل ينتظر

من فضلك لا. (على الأقل في core . من الواضح أن الأشخاص يمكنهم إنشاء وحدات ماكرو في التعليمات البرمجية الخاصة بهم. إذا أرادوا)

في الوقت الحالي ، يتطلب ماكرو postfix .await! () سحر المترجم. ومع ذلك ، إذا تم تثبيت متغير البادئة للكلمة الأساسية في انتظار ، فلن يكون هذا صحيحًا بعد الآن: يمكن تنفيذ الماكرو postfix .await! () بشكل تافه كماكرو يسبق وسيطته (تعبير) بالكلمة الأساسية انتظار ويتم لفها كل شيء بين قوسين.

سألاحظ أن هذا صحيح بنفس القدر - لكنه أسهل! - في الاتجاه الآخر: إذا قمنا بتثبيت بنية الكلمة الرئيسية .await ، فيمكن للأشخاص بالفعل إنشاء ماكرو بادئة awaited!() إذا لم يعجبهم postfix بما يكفي.

لا أحب فكرة إضافة متغيرات متعددة مسموح بها (مثل البادئة و postfix) ولكن لسبب مختلف قليلاً عما سيطلبه المستخدمون. أنا أعمل في شركة كبيرة نوعا ما والمعارك على أسلوب الكود حقيقية. أرغب في استخدام شكل واحد واضح وصحيح. بعد كل شيء ، قد يكون Zen of python محقًا فيما يتعلق بهذه النقطة.

لا أحب فكرة إضافة متغيرات متعددة مسموح بها (مثل البادئة و postfix) ولكن لسبب مختلف قليلاً عما سيطلبه المستخدمون. أنا أعمل في شركة كبيرة نوعا ما والمعارك على أسلوب الكود حقيقية. أرغب في استخدام شكل واحد واضح وصحيح. بعد كل شيء ، قد يكون Zen of python محقًا فيما يتعلق بهذه النقطة.

أنا أعرف ما تعنيه. ومع ذلك ، لا توجد طريقة لمنع المبرمجين من تحديد وحدات الماكرو الخاصة بهم للقيام بأشياء مثل await!() . ستكون الهياكل المتشابهة ممكنة دائمًا. لذلك سيكون هناك بالفعل متغيرات مختلفة على أي حال.

حسنًا ، ليس await!(...) على الأقل لأن ذلك سيكون خطأ ؛ ولكن إذا قام المستخدم بتعريف macro_rules! wait { ($e:expr) => { e.await }; } ثم استخدم ذلك كـ wait!(expr) عندها سيبدو غير رسمي بشكل واضح ومن المحتمل أن يخرج عن الموضة بسرعة. هذا يقلل بشكل كبير من احتمالية التباين في النظام البيئي ويسمح للمستخدمين بتعلم أنماط أقل. وبالتالي ، أعتقد أن نقطة yasammez مناسبة.

Centril إذا أراد شخص ما القيام بأشياء سيئة فإنه بالكاد يمكن إيقافه. ماذا عن _await!() أو awai!() ؟

أو عند تمكين معرف unicode ، شيء مثل àwait!() .

...

earthengine الهدف هو وضع معايير للمجتمع (على غرار ما نفعله مع أسلوب الوبر و rustfmt ) ، وليس منع مختلف الأشخاص من القيام بأشياء غريبة عن قصد. نحن نتعامل مع الاحتمالات هنا ، وليس الضمانات المطلقة لعدم رؤية _await!() .

دعنا نلخص ونراجع الحجج لكل بناء جملة postfix:

  • __ expr await (الكلمة الأساسية postfix) __: تستخدم بنية الكلمة الأساسية Postfix الكلمة الأساسية await حجزناها بالفعل. انتظار هو تحول سحري ، واستخدام كلمة رئيسية يساعد في التميز بشكل مناسب. لا يبدو أنه وصول إلى حقل أو طريقة أو استدعاء ماكرو ، ولن نضطر إلى توضيح ذلك كاستثناء. يتلاءم جيدًا مع قواعد التحليل الحالية ومع عامل التشغيل ? . يمكن القول أن المساحة في تسلسل الأسلوب ليست أسوأ من مع وجود نوع معمم ، وهو RFC الذي يبدو أنه مقبول بشكل ما. على الجانب السلبي ، قد تحتاج IDEs إلى القيام بمزيد من السحر لتوفير إكمال تلقائي لـ await . قد يجد الأشخاص المساحة في تسلسل الطريقة مزعجة للغاية (على الرغم من أن withoutboats قد جادلت في أن ذلك قد يكون ميزة) ، خاصةً إذا لم يتم تنسيق الكود بحيث ينتهي await كل سطر. يستخدم كلمة رئيسية ربما يمكننا تجنب استخدامها إذا اتبعنا طريقة أخرى.

  • __ expr.await (حقل postfix) __: يستفيد بناء جملة حقل Postfix من "قوة النقطة" - يبدو طبيعيًا في التسلسل ويسمح لـ IDEs بالإكمال التلقائي await بدون إجراء عمليات أخرى (مثل كإزالة النقطة تلقائيًا ). إنها موجزة تمامًا مثل الكلمة الأساسية postfix. قد تسهل النقطة الموجودة أمام الانتظار العثور على مثيلاتها باستخدام grep. على الجانب السلبي ، يبدو وكأنه حق الوصول. كوصول مجال واضح ، لا يوجد دليل مرئي على أن "هذا يفعل شيئًا ما."

  • __ expr.await() (طريقة postfix) __: بناء جملة طريقة Postfix يشبه حقل postfix. على الجانب العلوي ، تشير صيغة المكالمة () للقارئ ، "هذا يفعل شيئًا." عند النظر إليه محليًا ، يكون من المنطقي تقريبًا بنفس الطريقة التي يؤدي بها استدعاء طريقة الحظر إلى التنفيذ في برنامج متعدد الخيوط. على الجانب السلبي ، إنها أطول قليلاً وأكثر ضوضاءً ، وقد يكون إخفاء السلوك السحري await كطريقة محيرًا. قد نقول أن await هي طريقة بالمعنى المحدود نفسه حيث أن call/cc في المخطط هو دالة. كطريقة ، سنحتاج إلى التفكير فيما إذا كان يجب أن يعمل Future::await(expr) باستمرار مع UFCS .

  • __ expr.await!() (ماكرو postfix) __: تستفيد بنية الماكرو Postfix بالمثل من "قوة النقطة". يشير الماكرو ! إلى "هذا قد يفعل شيئًا سحريًا". على الجانب السلبي ، يعد هذا أكثر ضجيجًا ، وبينما تقوم وحدات الماكرو بالسحر ، فإنها لا تفعل سحرًا للشفرة المحيطة كما يفعل await . على الجانب السلبي أيضًا ، بافتراض أننا قمنا بتوحيد بناء جملة ماكرو لما بعد الإصلاح للأغراض العامة ، فقد تكون هناك مشكلات في الاستمرار في التعامل مع await ككلمة رئيسية.

  • __ expr@ ، expr# ، expr~ ورموز أخرى أحادية الحرف__: يؤدي استخدام حرف واحد كما نفعل مع ? زيادة الإيجاز وربما إنشاء بناء جملة postfix يبدو أكثر طبيعية. كما هو الحال مع ? ، قد نجد أننا نقدر هذا الإيجاز إذا بدأ await في اختراق الكود الخاص بنا. على الجانب السلبي ، حتى وما لم يتحول الألم الناتج عن تناثر الكود الخاص بنا إلى await إلى مشكلة ، فمن الصعب أن نرى إجماعًا يتشكل حول المقايضات المتأصلة في تبني مثل هذه البنية.

أردت أن أتولى منشورًا لأقول شكراً لـ المنشورات الموجزة! لقد كانت مكتوبة بشكل جيد ومتشابكة باستمرار. إنه محل تقدير كبير. :قلب:

لدي فكرة أن إضافة "عوامل تشغيل الأنابيب" مثل F # ، ثم يمكن للمستخدمين استخدام البادئة أو postfix (مع اختلاف واضح في بناء الجملة).

// 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 ملخص ممتاز. كان هناك أيضًا بعض النقاش حول الجمع بين sigil والكلمة الرئيسية ، على سبيل المثال fut@await ، لذلك سأضيف هذا هنا فقط للأشخاص القادمين إلى هذا الموضوع.

لقد جندت إيجابيات وسلبيات بناء الجملة هذا هنا . يقول earthengine أن سيجيلات أخرى غير @ ممكنة ، مثل ~ . BenoitZugmeyer يفضل @await ، ويسأل عما إذا كانت وحدات ماكرو postfix ala expr!await ستكون فكرة جيدة. تجادل dpc بأن @await مخصص للغاية ولا يتكامل جيدًا مع ما لدينا بالفعل ، أيضًا ، أن Rust ثقيل بالفعل ؛ يوافق cenwangumass على أنه مخصص للغاية. يقول newpavlov أن الجزء await يبدو زائدًا عن الحاجة ، خاصةً إذا لم نضف كلمات رئيسية أخرى مماثلة في المستقبل. تقول nicoburns أن بناء الجملة يمكن أن يعمل وأنه لا توجد مشكلات كثيرة به ، لكنه مخصص جدًا لحل.

traviscross ملخص عظيم!

0.02 دولار بالترتيب من الأسوأ إلى الأفضل في رأيي:

  • 3 هو أمر محظور بشكل قاطع لأنه ليس حتى استدعاء طريقة.
  • 2 ليس مجالًا ، إنه مربك جدًا ، خاصة للقادمين الجدد. وجود await في قائمة الإكمال ليس مفيدًا حقًا. بعد أن تكتب الكثير منهم ، تكتبه على أنه fn أو extern . الإكمال الإضافي أسوأ من لا شيء لأنه بدلاً من الأساليب / الحقول المفيدة ، سيتم اقتراح كلمة رئيسية.
  • 4 ماكرو هو شيء مناسب هنا ، لكنه لا يناسبني. أعني عدم تناسق مع كلمة رئيسية async ، كما ذكرت أعلاه
  • قد يكون 5 Sigil مقتضبًا جدًا ويصعب تحديده ، لكنه كيان منفصل ويمكن معاملته على هذا النحو. لا يشبه أي شيء آخر وبالتالي لا يسبب أي إرباك للمستخدمين
  • 1 أفضل نهج IMHO ، إنه مجرد علامة سيجيل يسهل اكتشافها وهي بالفعل كلمة رئيسية محجوزة. فصل الفضاء ، كما ذكر أعلاه ، هو ميزة وليس عيب. من الجيد أن تكون موجودًا عند عدم إجراء أي تنسيق ، ولكن مع rustfmt يكون الأمر أقل أهمية.

بصفتي شخصًا ينتظر بفارغ الصبر هذه اللحظة القادمة ، فإليك 0.02 دولار أيضًا.

بالنسبة للجزء الأكبر ، أتفق مع Pzixel ، باستثناء ما يتعلق بالنقطتين الأخيرتين ، بمعنى أنني سأقوم أعطيهما كخيار ، كما هو الحال مع try!() / زوج ? . ما زلت أرى أشخاصًا يجادلون لصالح try!() على ? لأغراض التوضيح ، وأعتقد أن وجود وكالة على مظهر الكود الخاص بك في هذه الحالة بالذات هو مؤيد وليس خداع ، حتى على حساب وجود صيغتين مختلفتين.

على وجه الخصوص ، تعتبر الكلمة الأساسية postfix await رائعة إذا كتبت رمز غير متزامن كسلسلة واضحة من الخطوات ، على سبيل المثال

let val1 = my_async() await;
...
let val2 = another_async(val1) await;
...
let val3 = yet_another_async(val2) await;

من ناحية أخرى ، قد تفضل القيام بشيء أكثر تعقيدًا بدلاً من ذلك ، في أسلوب تسلسل أسلوب Rust-y النموذجي ، على سبيل المثال

let my_final_value = commit(get_some_data()
                        .and_then(|s| get_another_data(s))
                        .or_else(|s| report_error(s))~);

أعتقد في هذه الحالة أنه من الواضح بالفعل من السياق أننا نتعامل مع المستقبل ، وإسهاب لوحة المفاتيح await زائدة عن الحاجة. قارن:

let my_final_value = commit(get_some_data()
                        .and_then(|s| get_another_data(s))
                        .or_else(|s| report_error(s)) await);

هناك شيء آخر أحبه في بناء جملة postfix بالرمز الفردي وهو أنه يوضح أن الرمز ينتمي إلى التعبير ، بينما (بالنسبة لي شخصيًا) يبدو await القائم بذاته قليلًا ... ؟ :)

أعتقد أن ما أحاول قوله هو أن await يبدو أفضل في العبارات ، في حين أن postfix برمز واحد يبدو أفضل في التعبيرات .

هذه مجرد أفكاري ، على أي حال.

نظرًا لأن هذه هي بالفعل أم جميع خيوط فصل الدراجات ، فقد أردت إضافة علامة سيجيل أخرى لم يتم ذكرها حتى الآن AFAICT: -> .

الفكرة هي أنها تعكس -> من إعلان نوع الإرجاع للوظيفة التي تتبعها ، والتي - الوظيفة هي async - هي نوع الإرجاع المنتظر.

async fn send() -> Result<Response, HttpError> {...}
async fn into_json() -> Result<Json, EncodingError> {...}

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

في ما سبق ، ما تحصل عليه من send()-> هو Result<Response, HttpError> ، تمامًا كما هو مكتوب في إعلان الوظيفة.

هذه هي 0.02 دولار بعد قراءة معظم المناقشة أعلاه والتفكير في الخيارات المقترحة لبضعة أيام. لا يوجد سبب لإعطاء رأيي أي وزن - أنا مجرد شخص عشوائي على الإنترنت. ربما لن أعلق أكثر من ذلك بكثير لأنني حذر من عدم إضافة المزيد من الضوضاء إلى المناقشة.


أنا أحب سيجيل postfix. هناك أسبقية لـ postfix sigils وأعتقد أنها ستكون متسقة مع بقية اللغة. ليس لدي أي تفضيل خاص لما يتم استخدامه من sigil - لا شيء مقنع ولكني أعتقد أن ذلك سيتلاشى مع الإلمام. يقدم sigil أقل ضوضاء مقارنة بخيارات postfix الأخرى.

لا مانع من فكرة استبدال . (عند التسلسل) بـ -> أجل الانتظار. لا تزال موجزة وغير قابلة للطرح ولكني أفضل استخدام شيء يمكن استخدامه في نفس السياقات مثل ? .

أنا لا أحب بشدة خيارات postfix الأخرى التي تم تقديمها. await ليس حقلاً أو طريقة وبالتالي يبدو غير متسق تمامًا مع بقية اللغة من أجل تقديم بناء تدفق التحكم على هذا النحو. لا توجد أي وحدات ماكرو أو كلمات رئيسية لما بعد الإصلاح في اللغة ، لذلك يبدو هذا أيضًا غير متسق.

إذا كنت جديدًا على اللغة ، ولم أكن أعرفها تمامًا ، فسأفترض أن .await أو .await() لم تكن ميزات لغة خاصة وكانت حقولًا أو طرقًا على نوع - كما هو الحال مع كل مجال وطريقة أخرى باللغة التي سيراها المستخدم. بمجرد اكتساب المزيد من الخبرة ، إذا تم اختيار .await!() ، فقد يحاول المستخدم معرفة كيفية تحديد وحدات ماكرو postfix الخاصة به (تمامًا كما تعلموا تحديد وحدات ماكرو البادئة الخاصة بهم) - لا يمكنهم ذلك. إذا رأى هذا المستخدم sigil ، فقد يحتاج إلى البحث عن الوثائق (تمامًا مثل ? ) ، لكنهم لن يخلطوا بينه وبين أي شيء آخر ويضيعون الوقت في محاولة العثور على تعريف .await() أو التوثيق للحقل .await .

تعجبني البادئة await { .. } . هذا النهج له أسبقية واضحة ولكن لديه مشكلات (تمت مناقشتها بالتفصيل بالفعل). على الرغم من ذلك ، أعتقد أنه سيكون مفيدًا لأولئك الذين يفضلون استخدام أدوات الدمج. لا أرغب في أن يكون هذا هو الخيار الوحيد الذي تم تنفيذه ، لأنه ليس مريحًا مع تسلسل الأسلوب ، لكنني أعتقد أنه سيكون مكملًا لـ postfix sigil بشكل جيد.

أنا لا أحب خيارات البادئة الأخرى ، فهي لا تشعر بأنها متسقة مع تركيبات تدفق التحكم الأخرى في اللغة. على غرار طريقة postfix ، فإن وظيفة البادئة غير متسقة ، ولا توجد أي وظائف عامة أخرى مضمنة في اللغة المستخدمة للتحكم في التدفق. لا توجد أيضًا أي وحدات ماكرو مستخدمة للتحكم في التدفق (باستثناء try!(..) ولكن هذا مهمل لأن لدينا حل أفضل - سيجيل postfix).


تتلخص جميع تفضيلاتي تقريبًا في ما يبدو طبيعيًا ومتسقًا (بالنسبة لي). يجب إعطاء أي حل يتم اختياره وقتًا للتجريب قبل التثبيت - التجربة العملية ستكون أفضل بكثير في تحديد الخيار الأفضل من المضاربة.

يجدر أيضًا التفكير في أنه قد تكون هناك أغلبية صامتة يمكن أن يكون لها آراء مختلفة تمامًا - أولئك الذين يشاركون في هذه المناقشات (بما فيهم أنا) لا يمثلون بالضرورة جميع مستخدمي Rust (لا يُقصد بهذا أن يكون طفيفًا أو بأي شكل من الأشكال مسيئة).

tl؛ dr Postfix sigils هي طريقة طبيعية للتعبير عن الانتظار (بسبب الأسبقية) وهي نهج موجز ومتسق. سأضيف بادئة await { .. } و postfix @ (يمكن استخدامها في السياقات مثل ? ). من المهم جدًا بالنسبة لي أن يظل Rust ثابتًا داخليًا.

تضمين التغريدة

أعتقد في هذه الحالة أنه من الواضح بالفعل من السياق أننا نتعامل مع المستقبل ، وإسهاب لوحة المفاتيح المنتظرة زائدة عن الحاجة. قارن:

آسف ، لكنني لم أرصد حتى ~ في نظرة سريعة. أعدت قراءة التعليق بالكامل وكانت قراءتي الثانية أكثر نجاحًا. يقدم تفسيرًا رائعًا لماذا أعتقد أن سيجيل أسوأ. إنه مجرد رأي ، لكن تم تأكيده مرة أخرى.

نقطة أخرى ضد sigils هي أن Rust أصبح J. على سبيل المثال:

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

يرمز ?@? إلى "وظيفة قد ترجع خطأ أو مستقبلًا ، يجب انتظاره ، مع نشر الخطأ ، إن وجد ، للمتصل"

أود الحصول على المزيد

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

تضمين التغريدة

نظرًا لأن هذه هي بالفعل أم جميع خيوط فصل الدراجات ، فقد أردت إضافة علامة سيجيل أخرى لم يتم ذكرها حتى الآن AFAICT: ->.

إن اعتماد القواعد النحوية على السياق لا يجعل الأمور أفضل ، بل يجعله أسوأ فقط. يؤدي إلى أخطاء أسوأ وأوقات ترجمة أبطأ.

لدينا معنى منفصل لـ -> ، لا يوجد شيء للتعامل مع async/await .

أفضل البادئة await { .. } ، وإن أمكن ، postfix ! sigil.
من شأن علامة التعجب أن تؤكد بمهارة كسل المستقبل. لن ينفذوا حتى يتم إعطاء أمر بعلامة تعجب.

سيبدو المثال أعلاه كما يلي:

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

آسف إذا لم يكن رأيي ذا صلة لأن لدي خبرة قليلة في عدم التزامن وليس لدي خبرة في النظام البيئي لوظيفة Rust غير المتزامن.

ومع ذلك ، بالنظر إلى .send()?!?.json()?!?; ومجموعات أخرى من هذا القبيل ، فأنا أفهم الأسباب الأساسية التي تجعل الاقتراح المستند إلى sigil يبدو خاطئًا بالنسبة لي.

أولاً ، أشعر أن السيجيلات المقيدة سرعان ما تصبح غير قابلة للقراءة ، أينما كانت ?!? أو ?~? أو ?->? . سيكون هذا شيئًا آخر يعثر عليه المبتدئين ، ويخمنون ما إذا كان عامل تشغيل واحد أم متعدد. المعلومات معبأة بإحكام شديد.

ثانيًا ، بشكل عام ، أشعر أن نقاط الانتظار أقل شيوعًا من نقاط انتشار الخطأ وأكثر أهمية. تعتبر نقاط الانتظار مهمة بما يكفي بالنسبة لي لأستحق أن أكون مرحلة في السلسلة بمفردها ، وليس "تحولًا ثانويًا" مرتبطًا بمرحلة أخرى ( وخاصةً ليس "مجرد تحول بسيط"). من المحتمل أن أكون بخير حتى مع نموذج الكلمة الأساسية البادئة فقط (والذي يفرض انتظارًا لكسر السلسلة) ، ولكن بشكل عام يبدو هذا مقيدًا للغاية. من المحتمل أن يكون خياري المثالي عبارة عن ماكرو يشبه الطريقة .await!() مع إمكانية مستقبلية لتوسيع نظام الماكرو للسماح بوحدات ماكرو المستخدم التي تشبه الطريقة.

الحد الأدنى من خط الأساس لتحقيق الاستقرار ، في رأيي ، هو عامل تشغيل البادئة await my_future . كل شيء آخر يمكن أن يتبع.

سيعكس expr....await سياق شيء يحدث حتى الانتظار ويتوافق مع مشغلي rustlang. كما أن الانتظار غير المتزامن هو نمط مواز .. لا يمكن التعبير عن الانتظار كطريقة أو خاصية مشابهة

لدي ميل إلى await!(foo) ، ولكن بما أن الآخرين قد أشاروا إلى أن القيام بذلك سيجبرنا على عدم الانتظار وبالتالي يمنع استخدامه في المستقبل كمشغل حتى عام 2021 ، سأذهب مع await { foo } حسب تفضيلي. أما بالنسبة إلى انتظار postfix ، فليس لدي رأي معين.

أعلم أنه قد لا يكون أفضل مكان للحديث عن الانتظار الضمني ، ولكن هل هناك شيء مثل الانتظار الضمني الصريح في انتظار العمل؟ لذلك ، لدينا أولاً بادئة في انتظار الهبوط:

await { future }?

وبعد ذلك نضيف شيئًا مشابهًا لـ

let result = implicit await { client.get("https://my_api").send()?.json()?; }

أو

let result = auto await { client.get("https://my_api").send()?.json()?; }

عند اختيار الوضع الضمني ، يتم تلقائيًا انتظار كل شيء بين {} .

لقد وحد هذا بناء الجملة await وسيوازن الحاجة إلى انتظار البادئة ، والتسلسل ، والتوضيح قدر الإمكان.

قررت rg --type csharp '[^ ]await' لمسح أمثلة للأماكن التي كانت فيها البادئة دون المستوى الأمثل. من المحتمل ألا تكون كل هذه العناصر مثالية ، لكنها كود حقيقي خضع لمراجعة الكود. (أمثلة معقمة قليلاً لإزالة أشياء معينة في نموذج المجال.)

(await response.Content.ReadAsStringAsync()).Should().Be(text);

استخدام FluentAssertions كطريقة أفضل لعمل الأشياء من MSTest العادي assert_eq! Assert.Equal .

var previous = (await branch.ListHistoryAsync(timestampUtc, null, cancellationToken, 1)).HistoryEntries.SingleOrDefault();

هذا الشيء العام من "انظر ، أنا حقًا بحاجة إلى شيء واحد فقط" هو مجموعة منهم.

id = id ?? (await this.storageCoordinator.GetDefaultWidgetAsync(cancellationToken)).Identity;

آخر "كنت بحاجة إلى خاصية واحدة فقط". (جانباً: "يا رجل أنا سعيد لأن رست لن يحتاج إلى CancellationToken s.)

var pending = (await transaction.Connection.QueryAsync<EventView>(command)).ToList();

نفس .collect() الواحد الذي ذكره الناس في Rust.

foreach (var key in changes.Keys.Intersect((await neededChangesTask).Keys))

كنت أفكر في إمكانية الإعجاب بالكلمة الرئيسية postfix ، مع وجود أسطر جديدة rustfmt بعدها (وبعد ? ، إذا كانت موجودة) ، لكن أعتقد أن مثل هذه تجعلني أفكر في أن السطر الجديد ليس جيدًا بشكل عام.

else if (!await container.ExistsAsync())

واحدة من الحالات النادرة حيث كانت البادئة مفيدة بالفعل.

var response = (HttpWebResponse)await request.GetResponseAsync();

كان هناك عدد قليل من الممثلين ، على الرغم من أن القوالب بالطبع هي مكان آخر حيث يكون Rust هو postfix لكن C # هي بادئة.

using (var response = await this.httpClient.SendAsync(requestMsg))

لا يهم هذه البادئة الواحدة مقابل postfix ، لكنني أعتقد أنه فرق آخر مثير للاهتمام: نظرًا لأن C # لا تحتوي على Drop ، ينتهي الأمر بمجموعة من الأشياء _تحتاج_ إلى الانتقال إلى متغيرات وليس بالسلاسل.

بعض الأمثلةscottmcm الصورة rustified في لواحق مختلف المتغيرات:

// 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?;

(بفضل @ Nemo157 للتصحيحات)

// 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>>();

بالنسبة لي ، بعد قراءة هذا ، @ sigil خارج الطاولة ، لأنه غير مرئي فقط خاصة أمام ? .

لم أر أي شخص في هذا الموضوع يناقش المتغير المنتظر لـ Stream. أعلم أنه خارج النطاق ولكن هل يجب أن نفكر فيه؟

سيكون من العار أن نتخذ قرارًا تبين أنه مانع لانتظار البث.

// keyword
id = id.or_else(|| self.storage_coordinator.get_default_widget_async() await?.identity);

لا يمكنك استخدام await داخل إغلاق مثل هذا ، يجب أن تكون هناك طريقة تمديد إضافية على Option تأخذ إغلاقًا غير متزامن وترجع Future نفسها (عند في اللحظة التي أعتقد فيها أنه من المستحيل تحديد توقيع طريقة الامتداد ، ولكن آمل أن نحصل على طريقة لجعل عمليات الإغلاق غير المتزامن قابلة للاستخدام في مرحلة ما) .

// keyword
id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;

(أو ترجمة مباشرة أكثر للكود باستخدام if let بدلاً من المُدمج)

@ Nemo157 Yeal ، لكن ربما يمكننا القيام بذلك بدون وظائف إضافية:

id = ok(id).transpose().or_else(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;

لكن نهج if let يبدو أكثر طبيعية بالنسبة لي حقًا.

بالنسبة لي ، بعد قراءة هذا ، @ sigil خارج الطاولة ، لأنه غير مرئي خاصة أمام؟

لا تنسَ أنظمة تمييز الكود البديل ، بالنسبة لي ، يبدو هذا الرمز كما يلي:
1

أنا شخصياً لا أعتقد أن "الاختفاء" هنا يمثل مشكلة أكبر من مشكلة قائمة بذاتها ? .

ويمكن لنظام الألوان الذكي أن يجعله أكثر وضوحًا (على سبيل المثال باستخدام لون مختلف عن لون ? ).

newpavlov لا يمكنك اختيار نظام الألوان في الأدوات الخارجية ، على سبيل المثال في علامات تبويب مراجعة gitlab / github.

يقال ، ليس من الممارسات الجيدة الاعتماد على التمييز فقط. قد يكون للآخرين تفضيلات أخرى.

مرحبًا يا شباب ، أنا متعلم جديد Rust قادم من C ++. أريد فقط التعليق الذي لا يشبه شيئًا

id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;

أو

id = id.or_else_async(async || { 
    self.storage_coordinator.get_default_widget_async() await?.identity 
}) await?;

تكون غير مفهومة في الأساس؟ يتم دفع await نحو نهاية السطر بينما يتركز تركيزنا بشكل أساسي في البداية (وهو ما يشبه استخدام محرك بحث).
شيء مثل

id =  await? id.or_else_async(async || {
    let widget = await? self.storage_coordinator.get_default_widget_async();
    widget.identity
});

أو

id = auto await {
    id.or_else_async(async || { self.storage_coordinator.get_default_widget_async()?.identity })
}?;

المقترحة في وقت سابق تبدو أفضل بكثير بالنسبة لي.

أنا موافق. الكلمات القليلة الأولى هي أول ما ينظر إليه أي شخص أثناء مسح الكود ونتائج البحث وفقرات النص وما إلى ذلك. وهذا يضع على الفور أي نوع من postfix في وضع غير مؤات.

بالنسبة إلى التنسيب ? ، فأنا مقتنع أن await? foo مميز بما فيه الكفاية وسهل التعلم.

إذا استقرنا هذا الآن وبعد عام من الاستخدام ، قررنا أننا نريد حقًا شيئًا أفضل للتسلسل ، يمكننا اعتبار وحدات ماكرو postfix كميزة عامة.

أود أن أقترح تباينًا حول فكرة انتظار كلٍّ من قبل وبعد الإصلاح بواسطة
يمكن أن يكون لدينا شيئين:

  • البادئة await الكلمة الأساسية (على سبيل المثال ، النكهة ذات الربط الأقوى من ? ، لكن هذا أقل أهمية)
  • طريقة جديدة على std::Future ، على سبيل المثال fn awaited(self) -> Self::Output { await self } . يمكن أن يكون اسمها أيضًا block_on أو blocking أو شيئًا أفضل سيأتي به شخص آخر.

سيسمح هذا باستخدام البادئة "البسيطة" ، ويسمح أيضًا بالتسلسل ، مع تجنب الاضطرار إلى انتظار كلمة أساسية سياقية.

من الناحية الفنية ، يمكن أيضًا إنجاز النقطة الثانية باستخدام ماكرو postfix ، وفي هذه الحالة سنكتب .awaited!() .

سيسمح هذا برمز يشبه هذا:

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();

وبغض النظر عن المشكلات الأخرى ، فإن النقطة الرئيسية لهذا الاقتراح هي أن يكون لدينا await باعتباره اللبنة الأساسية للآلية ، تمامًا مثل match ، ثم توفير أدوات دمج لتوفر علينا الاضطرار إلى كتابة الكثير من الكلمات الرئيسية والأقواس من جميع الأنواع. أعتقد أن هذا يستلزم أنه يمكن تعليمهم بنفس الطريقة: أولاً await البسيط ، ثم لتجنب الكثير من الأقواس ، يمكن للمرء استخدام .awaited() والتسلسل بعيدًا.


بدلاً من ذلك ، يمكن لنسخة أكثر سحرية إسقاط الكلمة الرئيسية await بالكامل ، والاعتماد على طريقة سحرية .awaited() على std :: Future ، والتي لا يمكن تنفيذها من قبل الآخرين الذين يكتبون العقود المستقبلية الخاصة بهم ، ولكن أعتقد سيكون هذا مخالفًا للحدس إلى حد ما ويشكل حالة خاصة أكثر من اللازم.

طريقة جديدة على std::Future ، على سبيل المثال fn awaited(self) -> Self::Output { await self }

أنا متأكد من أن هذا مستحيل (بدون جعل الوظيفة ساحرة) ، لأن الانتظار بداخلها يجب أن يكون async fn awaited(self) -> Self::Output { await self } ، والذي سيظل في حد ذاته بحاجة إلى await ed. وإذا كنا نفكر في جعل الوظيفة سحرية ، فقد تكون أيضًا الكلمة الرئيسية ، IMO.

يوجد بالفعل Future::wait (على الرغم من عدم وجود 0.3 على ما يبدو حتى الآن؟) ، والذي يعمل على منع سلسلة المحادثات في المستقبل.

المشكلة هي أن النقطة الكاملة لـ await هي _not_ حظر الموضوع_. إذا كانت الصيغة التي يجب انتظارها هي بادئة وأردنا خيار postfix ، فيجب أن تكون ماكرو postfix وليس طريقة.

وإذا كنت ستقول استخدم طريقة سحرية ، فما عليك سوى تسميتها .await() ، والتي تمت مناقشتها بالفعل عدة مرات في سلسلة الرسائل ، كطريقة كلمة رئيسية وسحر extern "rust-await-magic" "حقيقي "الجبهة الوطنية.

تحرير: scottmcm ninja'd لي ولم يبلغني GitHub (لأن الهاتف المحمول؟) ، وما زلت أترك هذا بالرغم من ذلك.

scottmcm هل ستتمكن أيضًا من مسح عدد الترددات حيث يبدو await موافقًا مقابل المستوى دون المستوى؟ أعتقد أن استطلاعًا لعدد مرات تكرار البادئة مقابل postfix قد يساعد في الإجابة على عدة أسئلة.

  1. لدي انطباع بأن أفضل حالة استخدام لـ postfix await كانت حتى الآن
client.get("https://my_api").send() await?.json() await?

كما تستخدم العديد من المشاركات كمثال. ربما فاتني شيء لكن هل هناك حالات أخرى؟ ألن يكون من الجيد أن تستخرج هذا السطر في دالة إذا ظهر بشكل متكرر في جميع أنحاء قاعدة الكود؟

  1. كما ناقشت سابقًا ، إذا كتب بعض الناس
id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;

ما يفعلونه هو إخفاء await بصريًا تمامًا بدلاً من جعله واضحًا بحيث يمكن رؤية كل نقطة عائد بوضوح .

  1. تتطلب الكلمة الأساسية Postfix اختراع شيء غير موجود في اللغات السائدة الأخرى. إذا لم تقدم نتيجة أفضل بكثير ، فلن يكون اختراعها مفيدًا.

يتم دفع الانتظار حتى نهاية السطر بينما يتركز تركيزنا بشكل أساسي في البداية (وهو ما يشبه عندما تستخدم محرك بحث).

من الواضح أنني لا أستطيع دحض قيام الأشخاص بفحص محتوى الويب باستخدام نمط على شكل حرف F @ dowchris97.

ومع ذلك ، ليس من المسلم به أنهم يستخدمون نفس النمط للتشفير. على سبيل المثال ، يبدو هذا الآخر على الصفحة وكأنه قد يتطابق بشكل أفضل مع الطريقة التي يبحث بها الأشخاص عن ? ، وبالتالي قد يبحث عن await :

يتكون النمط المرقط من تخطي أجزاء كبيرة من النص والمسح الضوئي كما لو كنت تبحث عن شيء محدد ، مثل رابط أو أرقام أو كلمة معينة أو مجموعة كلمات ذات شكل مميز (مثل عنوان أو توقيع).

للمقارنة ، لنأخذ نفس المثال إذا أعاد Result بدلاً من impl Future :

let id = id.try_or_else(|| Ok(self.storage_coordinator.try_get_default_widget()?.identity))?;

أعتقد أنه واضح جدًا من نمط القراءة على شكل حرف F للشفرة مثل هذا لا يساعد في العثور على ? s ، ولا يساعد إذا تم تقسيمه إلى أسطر متعددة مثل

let id = id.try_or_else(|| {
    let widget = self.storage_coordinator.try_get_default_widget()?;
    Ok(widget.identity)
})?;

أعتقد أنه يمكن استخدام وصف مشابه لوصفك للدلالة على أن موضع ما بعد الإصلاح "يخفي تمامًا ? بصريًا بدلاً من توضيحه بحيث يمكن رؤية نقطة العودة بوضوح" ، والذي أوافق أنه أحد المخاوف الأصلية حول ? ، لكن يبدو أنها لم تكن مشكلة في الممارسة العملية.

بشكل عام ، أعتقد أن وضعه في المكان الذي تم تدريب الأشخاص فيه بالفعل على البحث عن ? هو أفضل طريقة للتأكد من أن الناس يرونها. أنا بالتأكيد أفضل ألا يضطروا إلى استخدام عمليتي مسح مختلفتين في وقت واحد.

تضمين التغريدة

ومع ذلك ، ليس من المسلم به أنهم يستخدمون نفس النمط للتشفير. على سبيل المثال ، يبدو هذا الآخر على الصفحة وكأنه قد يتطابق بشكل أفضل مع الطريقة التي يبحث بها الأشخاص عن ? ، وبالتالي قد يبحث عن await :

يتكون النمط المرقط من تخطي أجزاء كبيرة من النص والمسح الضوئي كما لو كنت تبحث عن شيء محدد ، مثل رابط أو أرقام أو كلمة معينة أو مجموعة كلمات ذات شكل مميز (مثل عنوان أو توقيع).

لا يوجد أيضًا دليل على أن استخدام النمط المرقط يساعد في فهم الكود بشكل أفضل / أسرع. خاصة عندما يستخدم البشر نمط الشكل F بشكل شائع ، والذي سأذكره لاحقًا.

خذ هذا السطر كمثال ، عندما تبدأ في استخدام النمط المرقط ، افترض أنك لم تقرأ هذا من قبل.

id = id.or_else_async(async || self.storage_coordinator.get_default_widget_async() await?.identity) await?;

بالنسبة لي ، بدأت عندما رأيت async ، ثم انتقلت أولاً إلى await? ، ثم self.storage_coordinator.get_default_widget_async() ، ثم .identity ، ثم أدركت أخيرًا السطر بالكامل غير متزامن. أود أن أقول إن هذه بالتأكيد ليست تجربة القراءة التي أحبها. أحد الأسباب هو أن نظامنا اللغوي المكتوب لا يحتوي على هذا النوع من القفز المتشابك للأمام والخلف . عندما تقفز ، فإنها تقاطع بناء النموذج العقلي لما يفعله هذا الخط.

للمقارنة ، ما الذي يفعله هذا الخط ، كيف تعرف ذلك؟

id = await? id.or_else_async(async || {
    let widget = await? self.storage_coordinator.get_default_widget_async();
    widget.identity
});

بمجرد وصولي إلى await? ، تلقيت على الفور تنبيهًا بأن هذا غير متزامن. ثم قرأت let widget = await? ، مرة أخرى ، بدون أي صعوبة ، علمت أن هذا غير متزامن ، هناك شيء ما يحدث. أشعر أنني أتبع نمط الشكل F. وفقًا لـ https://thenextweb.com/dd/2015/04/10/how-to-design-websites-that-mirror-how-our-eyes-work/ ، فإن الشكل F هو النمط الأكثر استخدامًا. إذن ، هل سنصمم نظامًا يناسب الطبيعة البشرية أم نبتكر نظامًا يحتاج إلى تعليم خاص ويعمل ضد طبيعتنا ؟ أنا أفضل الأول. سيكون الفرق أقوى عندما يتم تشذير الخطوط غير المتزامنة والخطوط العادية بهذا الشكل

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);
});

هل من المفترض أن أبحث عن await? عبر الخطوط تعرف؟

دع معرف = id.try_or_else (|| موافق (self.storage_coordinator.try_get_default_widget () ؟. Identity)) ؟؛

لهذا ، لا أعتقد أن المقارنة جيدة. أولاً ، لا يغير ? النموذج العقلي كثيرًا. ثانيًا ، يكمن نجاح ? في حقيقة أنه لا يتطلب منك القفز للأمام والخلف بين الكلمات. الشعور الذي ينتابني عند القراءة إلى ? في .try_get_default_widget()? هو ، "حسنًا ، ستحصل على نتيجة جيدة منه". هذا هو. لست مضطرًا للعودة لقراءة شيء آخر لفهم هذا السطر.

لذا ، فإن استنتاجي العام هو أن postfix قد يوفر بعض الراحة المحدودة عند كتابة التعليمات البرمجية. ومع ذلك ، فقد يتسبب ذلك في مشاكل أكبر لقراءة الكود ، والتي أجادل بأنها أكثر تكرارا من الكتابة.

كيف سيبدو هذا في الواقع مع نمط rustfmt وإبراز بناء الجملة ( while -> async ، match -> await ):

while fn foo() {
    identity = identity
        .or_else_async(while || {
            self.storage_coordinator
                .get_default_widget_async().match?
                .identity
        }).match?;
}

لا أعرف شيئًا عنك ، لكني اكتشفت match es على الفور.

(مرحبًا @ CAD97 ، لقد

@ dowchris97

إذا كنت ستجادل بأن ? لا يغير النموذج العقلي ، فما الخطأ في حجتي بأن await لا يجب أن يغير نموذجك العقلي للكود؟ (هذا هو السبب في أنني فضلت خيارات الانتظار الضمنية سابقًا ، على الرغم من أنني مقتنع بأنها ليست مناسبة لراست).

على وجه التحديد ، تقرأ async في _beginning_ من رأس الوظيفة. إذا كانت الوظيفة كبيرة جدًا بحيث لا تتناسب مع شاشة واحدة ، فمن المؤكد أنها كبيرة جدًا ولديك مشاكل قراءة أخرى أكبر من العثور على نقاط await .

بمجرد أن تصبح async ، تحتاج إلى الاحتفاظ بهذا السياق. ولكن في هذه المرحلة ، كل ما ينتظرنا هو تحويل يحول الحساب المؤجل Future إلى النتيجة Output خلال إيقاف قطار التنفيذ الحالي.

لا يهم أن هذا يعني تحولًا معقدًا لآلة الدولة. هذه تفاصيل التنفيذ. الاختلاف الوحيد بين سلاسل عمليات نظام التشغيل والحظر هو أنه قد يتم تشغيل رمز آخر على مؤشر الترابط الحالي أثناء انتظار الحساب المؤجل ، لذلك لا يحميك Sync بنفس القدر. (إذا كان هناك أي شيء ، فقد قرأت ذلك كشرط لـ async fn ليكون Send + Sync بدلاً من الاستنتاج والسماح بأن يكون مؤشر الترابط غير آمن.)

بأسلوب أكثر توجهاً نحو الوظائف ، ستبدو أدواتك ونتائجك (نعم ، أعلم أن هذا لا يستخدم Monad والنقاء الحقيقي وما إلى ذلك ، سامحني):

    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));

ولكن نظرًا لأن هذا ترتيب عكسي من مسار العملية ، اخترع الحشد الوظيفي عامل "خط الأنابيب" ، |> :

    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;

وفي Rust ، يكون مشغل خطوط الأنابيب هذا هو . ، والذي يوفر نطاقًا لما يمكن أن يتم تحديده بواسطة الأنابيب والبحث الموجه من خلال تطبيق الطريقة:

    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();

عندما تفكر في . كبيانات خطوط الأنابيب بنفس الطريقة التي يتعامل بها |> ، فإن السلاسل الأطول التي تُرى غالبًا في Rust تبدأ في جعلها أكثر منطقية ، وعندما يتم تنسيقها جيدًا (كما في مثال Centril) لا يفوتك إمكانية القراءة لأن لديك فقط خط أنابيب عمودي للتحويلات على البيانات.

await لا يخبرك "مرحبًا هذا غير متزامن". async يفعل. await هي الطريقة التي تقف بها وتنتظر الحساب المؤجل ، ومن المنطقي تمامًا إتاحتها لمشغل خطوط الأنابيب في Rust.

(مرحبًا Centril ، لقد نسيت أن تجعل ذلك async fn (أو while fn ) ، مما يضعف وجهة نظري إلى حد ما 😛)

سواء تمكنا من إعادة تعريف استدعاء الماكرو أم لا

m!(item1, item2)

هو نفسه

item1.m!(item2)

لذلك يمكننا استخدام نمط انتظار البادئة و postfix

await!(future)

و

future.await!()

@ CAD97

await لا يخبرك "مرحبًا هذا غير متزامن". async يفعل. await هي الطريقة التي تقف بها وتنتظر الحساب المؤجل ، ومن المنطقي تمامًا إتاحتها لمشغل خطوط الأنابيب في Rust.
نعم ، أعتقد أنني أفهم بشكل صحيح ولكني لم أكتب بدقة.

أنا أيضا أفهم وجهة نظرك. لكني لست مقتنعًا ، ما زلت أعتقد أن وضع await في المقدمة سيكون أفضل بشكل كبير.

أعرف أن |> يُستخدم في مقاييس اللغة الأخرى ليعني شيئًا آخر ، لكنه يبدو رائعًا جدًا وواضحًا جدًا بالنسبة لي في Rust بدلاً من البادئة 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)
}

سيتم تطبيق الجدل حول ترتيب القراءة بشكل جيد على عامل التشغيل ? يحل محل try!() . بعد كل شيء ، "مهلا هذا قد يسفر عن" أمر مهم ، ولكن "قد يعود هذا مبكرًا" مهم أيضًا ، إن لم يكن أكثر. وبالفعل ، فقد أثيرت مخاوف بشأن الرؤية مرارًا وتكرارًا في مناقشة الدرجات على ? (بما في ذلك هذا الموضوع الداخلي ومسألة GitHub هذه ) لكن المجتمع استقر في النهاية على الموافقة عليه ، واعتاد الناس عليه. سيكون من الغريب الآن أن ينتهي بك الأمر مع المُعدِّلات ? و await تظهر على جوانب متقابلة من التعبير ، لمجرد أن المجتمع غيّر رأيه بشأن مدى أهمية الرؤية.

سيتم تطبيق الجدل حول ترتيب القراءة بشكل جيد على عامل التشغيل ? يحل محل try!() . بعد كل شيء ، "مهلا هذا قد يسفر عن" أمر مهم ، ولكن "قد يعود هذا مبكرًا" مهم أيضًا ، إن لم يكن أكثر. وبالفعل ، فقد أثيرت مخاوف بشأن الرؤية مرارًا وتكرارًا في مناقشة الدرجات على ? (بما في ذلك هذا الموضوع الداخلي ومسألة GitHub هذه ) لكن المجتمع استقر في النهاية على الموافقة عليه ، واعتاد الناس عليه. سيكون من الغريب الآن أن ينتهي بك الأمر مع المُعدِّلات ? و await تظهر على جوانب متقابلة من التعبير ، لمجرد أن المجتمع غيّر رأيه بشأن مدى أهمية الرؤية.

لا يتعلق الأمر حقًا بما تفعله التعليمات البرمجية الخاصة بك. يتعلق الأمر بنموذجك العقلي لما تقوم به التعليمات البرمجية الخاصة بك ، وما إذا كان يمكن بناء النموذج بسهولة أم لا ، سواء كانت هناك صدمات أم لا ، سواء كان ذلك بديهيًا أم لا. يمكن أن تختلف هذه إلى حد كبير من واحد إلى آخر.

لا أريد مناقشة هذا أكثر من ذلك. أعتقد أن المجتمع بعيد عن تسوية حل ما بعد الإصلاح على الرغم من أن الكثير من الناس هنا قد يدعمونه. لكني أعتقد أنه قد يكون هناك حل:

Mozilla يبني Firefox ، أليس كذلك؟ كل شيء عن UI / UX! ماذا عن بحث جاد HCI في هذه المشكلة؟ لذلك يمكننا حقًا إقناع بعضنا البعض باستخدام البيانات وليس التخمين.

@ dowchris97 كان هناك (فقط) مثيلين من مقارنة كود العالم الحقيقي في هذا الموضوع: المقارنة واحدة من ~ 24K خطوط مع قواعد البيانات وreqwest والمقارنة واحد أن نستنتج لماذا C # ليست مقارنة دقيقة لقاعدة الصدأ في بناء الجملة على . كلاهما يتضح أنه في العالم الحقيقي لشفرة روست ، يبدو الانتظار في مرحلة ما بعد الإصلاح أكثر طبيعية ، ولا يعاني من المشكلات التي تعاني منها اللغات الأخرى التي ليس لها قيمة طبيعية. ما لم يظهر شخص ما بمثال آخر من العالم الحقيقي بحجم لائق يميل إلى إظهار عكس ذلك ، فأنا مقتنع تمامًا بأن صياغة البادئة هي ضرورة تفرضها اللغات الأخرى على أنفسهم لأنهم يفتقرون إلى دلالات القيمة الواضحة لتسلسل روست (حيث قراءة التعليمات البرمجية الاصطلاحية من اليسار إلى اليمين دائمًا تقريبًا بالضبط ما يقترحه النموذج العقلي).

تحرير: فقط إذا لم يكن واضحًا بشكل كافٍ ، فلا يوجد في C # و Python و C ++ و Javascript طرق عضوية تأخذ self حسب القيمة بدلاً من المرجع. C ++ هي الأقرب مع مراجع rvalue لكن ترتيب التدمير لا يزال محيرًا مقارنةً بـ Rust.

أعتقد أن الحجة القائلة بأن await أفضل كبادئة لا تنبع من كيفية تغيير النموذج العقلي الخاص بك للشفرة ، ولكن المزيد من كيفية وجود كلمات رئيسية بادئة لدينا في الصدأ ، ولكن ليس لدينا كلمات رئيسية postfix (لـ postfixes ، يستخدم الصدأ sigils كما يتضح من ? ). هذا هو السبب في أن قراءة await foo() أسهل في القراءة من foo() await ولماذا يريد بعض الأشخاص @ في نهاية البيان ولا يحبون امتلاك await هناك.

لسبب مشابه ، يبدو استخدام .await غريبًا: يتم استخدام عامل التشغيل النقطي للوصول إلى حقول وأساليب البنية (وهذا أيضًا سبب عدم إمكانية عرضها على أنها مشغل خطوط أنابيب خالص) ، لذا فإن وجود .await يشبه await ، وهو ليس حقلاً أو وظيفة".

أنا شخصياً أود أن أرى إما البادئة await أو sigil postfix (لا يجب أن يكون @ ).

كلاهما يتضح أنه في كود Rust في العالم الحقيقي ، يبدو الانتظار في postfix أكثر طبيعية

هذا بيان مثير للجدل. يقدم مثال reqwest إصدارًا واحدًا فقط من صيغة postfix.

في ملاحظة مختلفة ، إذا كانت هذه المناقشة تتعلق بتصويت من يحب أكثر من ذلك ، فيرجى ذكره على موقع reddit حتى لا يشتكي الأشخاص كما فعلوا مع وسيطات الوظيفة impl Trait .

@ eugene2k للمناقشة الأساسية حول ما إذا كانت postfix ستلائم النموذج العقلي لمبرمجي Rust ، فإن معظم أو كل تركيبات postfix تكون تقريبًا على نفس المقياس مقارنة بالبادئة. لا أعتقد أن هناك فرقًا كبيرًا في قابلية القراءة بين متغيرات postfix كما هو الحال بين البادئة و postfix. راجع أيضًا المقارنة منخفضة المستوى لأسبقية المشغل التي خلصت إلى أن دلالاتها متساوية في معظم الاستخدام ، لذا فهي مسألة تحديد عامل التشغيل الذي ينقل المعنى بشكل أفضل (أفضل حاليًا بناء جملة استدعاء الوظيفة الفعلي ، لكن لا لديك تفضيل قوي على الآخرين أيضًا).

@ eugene2k لا يتم اتخاذ القرارات في Rust بالتصويت. الصدأ ليس ديمقراطية ، إنه نظام جدارة.

تنظر الفرق الأساسية / اللغوية في جميع الحجج ووجهات النظر المختلفة ثم تقرر. يتم اتخاذ هذا القرار بالإجماع (بين أعضاء الفريق) وليس بالتصويت.

على الرغم من أن فرق Rust تأخذ في الاعتبار تمامًا الرغبات العامة للمجتمع ، إلا أنها قررت في النهاية بناءً على ما يعتقدون أنه الأفضل لـ Rust على المدى الطويل.

أفضل طريقة للتأثير على Rust هي تقديم معلومات جديدة ، أو تقديم حجج جديدة ، أو إظهار وجهات نظر جديدة .

تكرار الحجج الموجودة أو قول "أنا أيضًا" (أو ما شابه) لا يزيد من فرص قبول الاقتراح. لا يتم قبول العروض على أساس الشعبية.

وهذا يعني أيضًا أن التصويتات المؤيدة / المعارضة المختلفة في هذا الموضوع لا تهم على الإطلاق أي اقتراح يتم قبوله.

(أنا لا أشير إليك على وجه التحديد ، فأنا أشرح كيف تعمل الأشياء من أجل الجميع في هذا الموضوع.)

Pauan لقد قيل من قبل أن الفرق الأساسية / lang تنظر في المقترحات المختلفة ثم تقرر. لكن قرارات "أيهما أسهل للقراءة" قرارات شخصية. لن يغير أي قدر من الحجة المنطقية المقدمة إلى صانع القرار وجهة نظره الشخصية حول ما يفضله بشكل أفضل. علاوة على ذلك ، فإن الحجج مثل "هذه هي الطريقة التي يقرأ بها الأشخاص نتائج البحث" و "تم إجراء دراسة بواسطة كذا وأظهرت مثل هذه الأبحاث أن الناس يفضلون هذا وذاك" يمكن الطعن فيها بسهولة (وهذا ليس بالأمر السيئ). ما قد يغير رأي صانع القرار هو رؤية وعدم إعجاب نتائج تطبيق قراره في سياق لم يفكر فيه. لذلك ، عندما يتم النظر إلى كل هذه السياقات ويكون صانعو القرار في الفريق مثل نهج واحد ، في حين أن معظم المستخدمين الآخرين ، الذين ليسوا في الفريق مثل نهج آخر ، ما هو القرار النهائي؟

لذلك ، عندما يتم النظر إلى كل هذه السياقات ويكون صانعو القرار في الفريق مثل نهج واحد ، في حين أن معظم المستخدمين الآخرين ، الذين ليسوا في الفريق مثل نهج آخر ، ما هو القرار النهائي؟

يتم اتخاذ القرار دائمًا من قبل الفريق. فترة. هذه هي الطريقة التي تم بها تصميم القواعد عمدا.

وغالبًا ما يتم تنفيذ الميزات من قبل أعضاء الفريق. وقد أسس أعضاء الفريق أيضًا الثقة داخل المجتمع. لذلك لديهم سلطة قانونية وفعلية.

إذا تغير الموقف (ربما بناءً على التعليقات) وغيّر أعضاء الفريق رأيهم ، فيمكنهم تغيير قرارهم. ولكن حتى ذلك الحين ، يتخذ أعضاء الفريق القرار دائمًا.

كما تقول ، غالبًا ما تنطوي القرارات على قدر من الذاتية ، وبالتالي من المستحيل إرضاء الجميع ، ولكن يجب اتخاذ القرار. من أجل الوصول إلى قرار ، يعتمد النظام الذي يستخدمه Rust على توصل أعضاء الفريق إلى توافق في الآراء.

أي نقاش حول ما إذا كان يجب حكم الصدأ بشكل مختلف هو خارج الموضوع ويجب مناقشته في مكان آخر.

(ملاحظة: أنا لست في الفريق الأساسي أو فريق lang ، لذلك ليس لدي أي سلطة في هذا القرار ، لذلك يجب أن ألتزم بهم ، تمامًا كما تفعل أنت)

تضمين التغريدة

لا أعتقد أن هناك فرقًا كبيرًا في قابلية القراءة بين متغيرات postfix

أنا أعترض. أجد أن قراءة foo().await()?.bar().await()? أسهل في القراءة من foo() await?.bar() await? أو حتى foo()@?.bar()@? الرغم من ذلك ، أشعر أن وجود طرق ليست في الحقيقة أساليب تضع سابقة سيئة.

أود أن أقترح فكرة أخرى. أوافق على أن البادئة التي تنتظر ليس من السهل ربطها مع وظائف أخرى. ماذا عن بناء جملة postfix: foo(){await}?.bar()?{await} ؟ لا يمكن الخلط بينه وبين استدعاءات الوظائف ويبدو لي أنه سهل بما يكفي للقراءة في سلسلة.

وهناك اقتراح آخر كتبه لي. لنفكر في بناء جملة استدعاء الطريقة التالية:

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)
}

ما الذي يجعلها فريدة من نوعها بين المقترحات الأخرى:

  • الأقواس المربعة تجعل الأسبقية وتحديد النطاق أكثر نظافة.
  • بناء الجملة قابل للتوسيع بدرجة كافية للسماح بإزالة الروابط المؤقتة في سياقات أخرى أيضًا.

أعتقد أن القابلية للتوسع هي أقوى ميزة هنا. سيسمح بناء الجملة هذا بتنفيذ ميزات لغوية غير ممكنة في شكلها المشترك في Rust بسبب نسبة التعقيد / الفائدة العالية. يتم توفير قائمة بنيات اللغة الممكنة أدناه:

  1. تأجيل جميع عوامل تشغيل البادئة (بما في ذلك await - إنها الطريقة التي من المفترض أن تعمل بها):
let result = api.method().[await returns_future()];
let cond = long.method().chain().[!is_empty()];
let val = something.[*returns_ref()];
  1. وظائف مشغل خطوط الأنابيب:
// 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. إرجاع وظيفة التجاوز:
let sorted_vec = iter
    .map(mapper)
    .collect::<Vec<_>>()
    .[sort(),];
  1. وظائف يذبل:
consume(&HashMap::new(). [
    insert("key1", val1),
    insert("key2", val2),
]);
  1. تقسيم السلسلة:
let sf = surface(). [
    draw_circle(ci_dimens).draw_rectangle(rect_dimens).finish()?,
    draw_something_custom().finish()?,
];
  1. وحدات ماكرو Postfix:
let x = long().method().[dbg!(it)].chain();

أعتقد أن إدخال نوع جديد من بناء الجملة (الحقول السحرية ، وحدات ماكرو postfix ، الأقواس) له تأثير أكبر على اللغة من هذه الميزة وحدها ، ويجب أن يتطلب RFC.

هل هناك أي مستودع يستخدم بالفعل بكثافة await ؟ سأعرض إعادة كتابة جزء أكبر في كل نمط مقترح ، حتى نتمكن من الحصول على شعور أفضل بكيفية ظهورها ومدى فهم الكود.

أعيد الكتابة في المحددات الإلزامية:

// 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)
}

يكاد يكون متطابقًا مثل await!() . لذا ، تبدو جميلة! بعد أن استخدمت await!() لمدة عام أو عامين ، فلماذا تقوم فجأة بعمل انتظار postfix الذي لا يظهر في أي مكان في تاريخ لغة البرمجة.

هاه. بنية @ I60R expr.[await it.foo()] مع الكلمة الرئيسية السياقية it أنيقة جدًا. لم أكن أتوقع أن تعجبني أي مقترحات بناء جملة جديدة رائعة ، ولكن هذا رائع حقًا ، وهو استخدام ذكي لمساحة بناء الجملة (لأن IIRC .[ ليس بناء جملة صالحًا حاليًا في أي مكان) ، وسيحل الكثير مشاكل من مجرد الانتظار.

متفقًا على أنه سيتطلب بالتأكيد RFC ، وقد لا يكون الخيار الأفضل. لكنني أعتقد أنها نقطة أخرى على جانب تسوية بناء جملة البادئة لـ await في الوقت الحالي ، مع العلم بأن هناك عددًا من الخيارات لحل مشكلة "عوامل البادئة محرجة في السلسلة" بطريقة أكثر عمومية تفيد أكثر من مجرد عدم التزامن / الانتظار في المستقبل.

وهناك اقتراح آخر كتبه لي. لنفكر في بناء جملة استدعاء الطريقة التالية:

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)
}

ما الذي يجعلها فريدة من نوعها بين المقترحات الأخرى:

* Square brackets makes precedence and scoping much cleaner.

* Syntax is extensible enough to allow temporary bindings removal in other contexts as well.

أعتقد أن القابلية للتوسع هي أقوى ميزة هنا. سيسمح بناء الجملة هذا بتنفيذ ميزات لغة غير ممكنة في شكلها المشترك في Rust نظرًا لارتفاع نسبة التعقيد / الفائدة. يتم توفير قائمة بنيات اللغة الممكنة أدناه:

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();

حقا غير مفهوم.

tajimaha بالنظر إلى المثال الخاص بك ، أعتقد أن await {} قد يكون في الواقع أفضل بكثير من await!() إذا أردنا استخدام المحددات الإلزامية ، لأنها تتجنب مشكلة "الكثير من الأقواس" التي يمكن أن تسبب سهولة القراءة المشكلات المتعلقة ببنية await!() .

قارن:
ج #
انتظار {foo.bar (url، false، qux.clone ())}؛

with

```c#
await!(foo.bar(url, false, qux.clone()));

(ملاحظة: يمكنك الحصول على تمييز بناء الجملة من async و await للأمثلة البسيطة عن طريق ضبط اللغة على c #.)

nicoburns يمكنك استخدام أي من () أو {} أو [] مع الماكرو.

sgrif هذه نقطة جيدة. وبما أن هذا هو الحال ، أود أن أقترح أن "الكلمات الرئيسية البادئة بمحددات إلزامية" لا معنى لها كخيار. لأنه يكسر التناسق مع وحدات الماكرو الأخرى بدون فائدة في الأساس.

(FWIW ، ما زلت أعتقد أن "بادئة بدون محددات" مع حل عام مثل وحدات ماكرو postfix أو اقتراح @ I60R لما بعد الإصلاح هو أكثر منطقية. لكن خيار "مجرد التمسك

أود أن أقترح أن "الكلمات الرئيسية البادئة بمحددات إلزامية" لا معنى لها كخيار. لأنه يكسر التناسق مع وحدات الماكرو الأخرى بدون فائدة في الأساس.

لماذا لا يكون ذلك منطقيًا ولماذا لا تحتوي الكلمة الرئيسية على نفس بناء الجملة مثل الماكرو مشكلة؟

تضمين التغريدة

أود أن أقترح أن "الكلمات الرئيسية البادئة بمحددات إلزامية" لا معنى لها كخيار. لأنه يكسر التناسق مع وحدات الماكرو الأخرى بدون فائدة في الأساس.

لماذا لا يكون ذلك منطقيًا ولماذا لا تحتوي الكلمة الرئيسية على نفس بناء الجملة مثل الماكرو مشكلة؟

حسنًا ، إذا كان await عبارة عن ماكرو ، فإن ذلك سيكون له ميزة عدم إضافة أي بناء جملة إضافي إلى اللغة. وبالتالي تقليل تعقيد اللغة. ومع ذلك ، هناك حجة جيدة لاستخدام كلمة رئيسية: return ، break ، continue ، وغيرها من التركيبات المعدلة للتحكم في التدفق هي أيضًا كلمات رئيسية. لكن هذه كلها تعمل بدون محددات ، لذلك لكي تكون متوافقة مع هذه التركيبات ، فإن await سيحتاج إلى العمل بدون محددات أيضًا.

إذا كان لديك محددات انتظار إلزامية ، فلديك:

// 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};

من المحتمل أن يكون هذا محيرًا حيث يتعين عليك الآن تذكر 3 أشكال بناء جملة بدلاً من 2. ولا تقدم رؤية الكلمات الرئيسية مع محددات إلزامية أي فوائد على بناء جملة الماكرو ، أعتقد أنه سيكون من الأفضل فقط الالتزام بالمعيار بناء جملة الماكرو إذا أردنا فرض المحددات (التي لست مقتنعًا على الإطلاق بضرورة ذلك).

سؤال لمن يستخدمون الكثير من عدم التزامن / ينتظرون في Rust اليوم: كم مرة تنتظر الوظائف / الأساليب مقابل المتغيرات / المجال؟

سياق الكلام:

أعلم أنه في C # ، من الشائع القيام بأشياء تتلخص في هذا النمط:

var fooTask = this.FooAsync();
var bar = await this.BarAsync();
var foo = await fooTask;

بهذه الطريقة كلاهما يعمل بالتوازي. (سيقول البعض أنه يجب استخدام Task.WhenAll هنا ، لكن اختلاف الأداء ضئيل ، ويجعل الكود أكثر فوضى لأنه يتطلب المرور عبر فهارس المصفوفة.)

لكني أفهم أنه في Rust ، لن يتم تشغيل ذلك فعليًا بالتوازي على الإطلاق ، نظرًا لأن poll لـ fooTask لن يتم استدعاؤه حتى يحصل bar على قيمته ، وربما يحتاج إلى استخدام مُجمع

let (foo, bar) = when_all!(
    self.foo_async(),
    self.bar_async(),
).await;

بالنظر إلى ذلك ، فأنا أشعر بالفضول فيما إذا كان الشخص ينتهي به الأمر إلى امتلاك المستقبل في متغير أو حقل تحتاج إلى انتظاره ، أو إذا كان المرء ينتظر دائمًا تعبيرات الاستدعاء. لأنه إذا كان هذا هو الأخير ، فهناك متغير تنسيق صغير للكلمة الرئيسية postfix لم نناقشه حقًا: الكلمة الأساسية postfix _ no-space_.

لم أفكر كثيرًا فيما إذا كان ذلك مفيدًا ، ولكن سيكون من الممكن كتابة التعليمات البرمجية بشكل عادل

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

(لا أريد في الواقع إجراء مناقشة حول rustfmt ، كما قلت ، لكني أتذكر أن أحد أسباب عدم الإعجاب بالكلمة الأساسية postfix _was_ هو أن الفضاء يكسر التقسيم البصري.)

إذا ذهبنا مع ذلك ، فقد نذهب أيضًا إلى بناء الجملة .await للرافعة المالية
قوة النقطة ، أليس كذلك؟

سؤال لمن يستخدمون الكثير من عدم التزامن / ينتظرون في Rust اليوم: كم مرة تنتظر الوظائف / الأساليب مقابل المتغيرات / المجال؟

لكن ما أفهمه ، في Rust ، أن ذلك لن يعمل بالتوازي على الإطلاق [...]

صيح. من نفس الكود الأساسي كما كان من قبل ، إليك هذا المثال:

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())?
};

إذا كنت سأعيد كتابة الإغلاق بإغلاق 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())?
};

إذا كنت سأنتقل إلى بناء الجملة .await (وربطها معًا):

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?;

هل هناك أي مستودع يستخدم بكثافة في انتظار؟ سأعرض إعادة كتابة جزء أكبر في كل نمط مقترح

gralpli للأسف ، لا شيء يمكنني فتح مصدر يستخدم بكثافة await! . من المؤكد أنه يفسح المجال أكثر لكود التطبيق في الوقت الحالي (خاصة كونه غير مستقر للغاية).

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?;

توضح هذه السطور بالضبط كيف يتم إفساد الكود عن طريق الإفراط في استخدام postfix والتسلسل .

دعونا نرى نسخة البادئة:

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()
}?;

كلا الإصدارين يستخدمان 7 خطوط ولكن IMO الثاني أنظف. هناك أيضًا نوعان من الوجبات السريعة لاستخدام المحددات الإلزامية:

  1. لا يبدو await { future }? صاخبًا إذا كان الجزء future طويلًا. انظر let req = await { self.request(URL, Method::GET, vec![("info".into(), id.into())]) }?;
  2. عندما يكون السطر قصيرًا ، قد يكون استخدام await(future) أفضل. انظر Ok(await(req.json())?)

IMO ، من خلال التبديل بين المتغيرين ، تكون قابلية قراءة هذا الرمز أفضل بكثير من ذي قبل.

تم تنسيق المثال الأول بشكل سيئ. لا أعتقد أن تنسيق rustfmt يعجبني
ذلك. هل يمكنك تشغيل rustfmt عليه وإعادة نشره مرة أخرى هنا؟

mehcodeivandardi هل يمكنك أن تفعل ذلك؟ لا أعرف كيف يمكنني تنسيق بناء جملة .await . لقد نسخت الرمز للتو. شكر!

أود أن أضيف أن هذا المثال يوضح:

  1. لن يكون كود الإنتاج مجرد سلاسل جميلة وبسيطة مثل:
client.get("https://my_api").send().await?.json().await?
  1. يمكن للناس الإفراط في استخدام أو إساءة استخدام السلاسل.
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?;

هنا ، يعالج الإغلاق غير المتزامن كل معرّف ، ولا علاقة له بمستوى التحكم الأعلى futures_unordered . إن تجميعها معًا يقلل بشكل كبير من قدرتك على فهمها.

كل شيء _ تم تشغيله من خلال rustfmt من مشاركتي (مع بعض التعديلات الطفيفة لجعلها مجمعة). لم يتم تحديد مكان وضع .await? بعد وحاليًا أضعه في نهاية السطر الذي أنتظره.


الآن أوافق على أن كل شيء يبدو فظيعًا جدًا. هذا رمز مكتوب في الموعد النهائي والأشياء لا بد أن تبدو مروعة عندما يكون لديك غرفة من الطهاة الذين يتنقلون للحصول على شيء ما.

أريد أن أشير (من وجهة نظرك) إلى أن إساءة استخدام البادئة يمكن أن تبدو أسوأ بكثير (في رأيي بالطبع):

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())?;

الآن دعونا نستمتع ونجعل الأمر أجمل بكثير باستخدام الإدراك المتأخر وبعض المحولات الجديدة في العقود الآجلة v0.3

بادئة بأسبقية "واضحة" (وسكر)
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();
بادئة ذات أسبقية "مفيدة"
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()?;
بادئة بالمحددات الإلزامية
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()
}?;
حقل 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?;
كلمة 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?;

الصئبان الصغرى هنا. لا يوجد TryStreamExt::and_then ، ربما يجب أن يكون هناك. يبدو وكأنه علاقات عامة سهلة لأي شخص لديه وقت يريد المساهمة.


  • أريد ، مرة أخرى ، التعبير عن كره الشديد لـ await? في السلاسل الطويلة ، أفقد تمامًا مسار ? الذي نمت للبحث عنه في نهاية التعبيرات للدلالة على أن هذا التعبير هو غير معصوم من الخطأ وقد يخرج من الوظيفة.

  • أرغب أيضًا في التعبير عن كره المتزايد لـ await .... ? (أسبقية مفيدة) مع مراعاة ما سيحدث إذا كان لدينا fn foo() -> Result<impl Future<Output = Result<_>>>

    // Is this an error? Does`await .. ?` bind outer-most to inner?
    await foo()??
    

أريد أن أشير (من وجهة نظرك) إلى أن إساءة استخدام البادئة يمكن أن تبدو أسوأ بكثير (في رأيي بالطبع):

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())?;

هذا ليس مصدر قلق في الواقع لأنه في Python javascript ، من المرجح أن يكتبها الناس في سطور منفصلة. لم أرَ في الواقع await (await f) في لغة الثعبان.

بادئة بالمحددات الإلزامية

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()
}?;

يبدو أن هذا يعود أيضًا إلى استخدام المُدمج. بينما الهدف من تقديم async / await هو تقليل استخدامه عند الاقتضاء.

حسنًا ، هذا هو بيت القصيد من انتظار postfix. نظرًا لأن هذا هو Rust ، فمن غير المرجح أن يكتبها الناس في سطور منفصلة ، لأن Rust يشجع على التسلسل. ولهذا ، فإن بناء جملة postfix إلزامية بشكل أساسي بحيث يتبع تدفق التعليمات نفس تدفق قراءة السطر. إذا لم يكن لدينا بناء جملة postfix ، فسيكون هناك الكثير من التعليمات البرمجية ذات الموقتات المقيدة أيضًا ، بينما إذا كان لدينا انتظار postfix ، فيمكن اختزالها جميعًا إلى سلسلة واحدة.

ivandardi @ mehcode نسخ من Rust RFC على غير متزامن / انتظار:

بعد اكتساب الخبرة وتعليقات المستخدمين مع النظام البيئي المستقبلي ، اكتشفنا بعض تحديات بيئة العمل. كان استخدام الحالة التي يجب مشاركتها عبر نقاط الانتظار أمرًا غير مريح للغاية - يتطلب إما أقواسًا أو ربطًا متسلسلًا - وبينما كانت أدوات الدمج غالبًا أكثر راحة من كتابة المستقبل يدويًا ، فإنها لا تزال تؤدي في كثير من الأحيان إلى مجموعات فوضوية من عمليات الاسترجاعات المتداخلة والمقيدة.

... استخدمه مع السكر النحوي الذي أصبح شائعًا في العديد من اللغات مع IO غير المتزامن - الكلمات الرئيسية غير المتزامنة والانتظار.

من وجهة نظر المستخدم ، يمكنهم استخدام غير متزامن / انتظار كما لو كان رمزًا متزامنًا ، ويحتاجون فقط إلى التعليق على وظائفهم ومكالماتهم.

لذا ، فإن بيت القصيد من تقديم الانتظار غير المتزامن هو تقليل التسلسل وجعل الشفرة غير المتزامنة كما لو كانت متزامنة . تم ذكر التسلسل مرتين فقط في طلب التعليقات هذا ، "يتطلب إما أقواس أو ربط تسلسل" و "لا يزال يؤدي غالبًا إلى مجموعات فوضوية من عمليات الاسترجاعات المتداخلة والمقيدة" لا يبدو ذلك إيجابيا جدا بالنسبة لي.

الجدال من أجل التسلسل وبالتالي الكلمة الأساسية postfix قد تحتاج إلى إعادة كتابة رئيسية لهذا RFC.

tajimaha أنت تسيء فهم RFC. إنه يتحدث تحديدًا عن الدمج المستقبلي ( map ، and_then ، إلخ.) ، لا يتحدث عن التوليف بشكل عام (على سبيل المثال ، الطرق التي ترجع impl Future ).

أعتقد أنه من الآمن أن نقول إن طرق async ستكون شائعة جدًا ، لذا فإن التسلسل مهم حقًا.

بالإضافة إلى ذلك ، أنت تسيء فهم العملية: لا يلزم إعادة كتابة RFC. RFC هي نقطة انطلاق ، لكنها ليست مواصفة. لم يتم وضع أي من طلبات التعليقات على الحجر (ولا ينبغي أن تكون كذلك!)

عملية RFC سائلة ، فهي ليست صلبة تقريبًا كما تدل على ذلك. لا شيء في RFC يمنعنا من مناقشة postfix await .

أيضًا ، سيتم وضع أي تغييرات في RFC للتثبيت ، وليس RFC الأصلي (الذي تم قبوله بالفعل ، وبالتالي لن يتم تغييره).

قد تحتاج المجادلة للتسلسل وبالتالي الكلمة الأساسية postfix إلى إعادة كتابة رئيسية لهذا RFC.

أنا أمزح هنا.

ربما يكون هناك شيء واحد صحيح ، عند كتابة RFC. أراد الأشخاص تحديدًا أداة جديدة "يمكنها استخدام عدم التزامن / الانتظار كما لو كان رمزًا متزامنًا". التقليد الذي يتبع بناء الجملة من شأنه أن يفي بهذا الوعد بشكل أفضل.
وهنا كما ترون ، ليس الأمر عبارة عن أدوات دمج مستقبلية ،

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?;

ومع ذلك ، "ما زالت تؤدي في كثير من الأحيان إلى مجموعات فوضوية من عمليات الاسترجاعات المتداخلة والمقيدة".

أراد الأشخاص تحديدًا أداة جديدة "يمكنها استخدام عدم التزامن / الانتظار كما لو كان رمزًا متزامنًا". التقليد الذي يتبع بناء الجملة من شأنه أن يفي بهذا الوعد بشكل أفضل.

لا أرى مدى صحة ذلك: كل من البادئة و postfix await تلبي هذه الرغبة.

في الواقع ، من المحتمل أن تحقق postfix await هذه الرغبة بشكل أفضل ، لأنها طبيعية جدًا مع سلاسل الطريقة (وهي شائعة جدًا في كود Rust المتزامن!)

يشجع استخدام البادئة await بشدة على الكثير من المتغيرات المؤقتة ، والتي غالبًا لا تكون نمط الصدأ الاصطلاحي.

ومع ذلك ، "ما زالت تؤدي في كثير من الأحيان إلى مجموعات فوضوية من عمليات الاسترجاعات المتداخلة والمقيدة".

أرى إغلاقًا واحدًا بالضبط ، وليس رد اتصال ، إنه مجرد طلب map على Iterator (لا شيء على الإطلاق يتعلق بالعقود الآجلة!)

من فضلك لا تحاول الالتفاف حول كلمات RFC لتبرير البادئة await .

يعد استخدام RFC لتبرير البادئة await أمرًا غريبًا للغاية ، لأن RFC نفسها تقول أن بناء الجملة ليس نهائيًا وسيتم تحديده لاحقًا. حان وقت هذا القرار الآن.

سيتم اتخاذ القرار بناءً على مزايا المقترحات المختلفة ، ويكون RFC الأصلي غير ذي صلة على الإطلاق (باستثناء كمرجع تاريخي مفيد).

لاحظ أن أحدث مثال لـ @ mehcode يستخدم في الغالب أدوات دمج _stream_ (ويمكن استبدال المندمج المستقبلي الوحيد بكتلة غير متزامنة). هذا يعادل استخدام أدوات دمج المكرر في كود متزامن ، لذلك يمكن استخدامها في بعض المواقف عندما تكون أكثر ملاءمة من الحلقات.

هذا بعيد المنال ، لكن معظم المحادثة هنا تتم بواسطة عشرات المعلقين أو نحو ذلك. من بين 383 تعليقًا في الوقت الذي ألغيت فيه هذا العدد ، كان هناك 88 ملصقًا فريدًا فقط. في محاولة لتجنب الإرهاق / إثقال كاهل أي شخص سيضطر للذهاب وقراءة هذه التعليقات ، أوصي بأن تكون دقيقًا في تعليقاتك بقدر الإمكان والتأكد من أنها ليست تكرارًا لنقطة سابقة.


الرسم البياني للتعليقات

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)*

لاحظ أن أحدث مثال لـ @ mehcode يستخدم في الغالب أدوات دمج _stream_ (ويمكن استبدال المندمج المستقبلي الوحيد بكتلة غير متزامنة). هذا يعادل استخدام أدوات دمج المكرر في كود متزامن ، لذلك يمكن استخدامها في بعض المواقف عندما تكون أكثر ملاءمة من الحلقات.

يمكن القول بالمثل هنا أنه يمكنني / ينبغي استخدام البادئة في انتظار حيث تكون أكثر ملاءمة من التسلسل.

@ باوان يبدو أنه ليس مجرد تحريف الكلمات. أعرض مشكلة رمز فعلية ، كتبها مؤيد بناء جملة postfix. وكما قلت ، يوضح رمز نمط البادئة نيتك بشكل أفضل بينما لا يحتوي بالضرورة على الكثير من الفترات المؤقتة كما يشكو مؤيدو postfix (على الأقل في هذه الحالة). أيضًا ، لنفترض أن الكود الخاص بك يحتوي على سلسلة خطية واحدة مع انتظارين ، كيف يمكنني تصحيح الخطأ الأول؟ (هذا سؤال حقيقي ولا أعلم).
ثانيًا ، أصبح مجتمع الصدأ أكبر ، ولن يتفق جميع الأشخاص من خلفيات متنوعة (مثلي ، أنا أستخدم python / c / java كثيرًا) على أن سلاسل الطريقة هي أفضل الطرق للقيام بالأشياء. آمل عند اتخاذ القرار ، لا (لا ينبغي) أن يعتمد فقط على وجهة نظر الأوائل.

tajimaha يبدو أن أكبر تغيير في الوضوح من الإصلاح اللاحق إلى البادئة هو استخدام الإغلاق المحلي لإزالة بعض وسيطات الدالة المتداخلة. لا يبدو هذا فريدًا بالنسبة لي ، فهذه الكلمات متعامدة إلى حد ما. يمكنك أن تفعل الشيء نفسه مع postfix ، وأعتقد أنه أكثر وضوحا. أوافق على أن هذا ربما يكون إساءة استخدام للتسلسل لبعض قواعد الكود ، لكنني لا أرى كيف يكون سوء الاستخدام هذا فريدًا أو مرتبطًا بـ postfix بطريقة رئيسية.

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?;

ولكن ، في postfix ، يمكن إزالة let -binding و Ok في النتيجة معًا آخر ? لتقديم النتيجة مباشرة ، ومن ثم فإن كتلة الكود غير ضرورية أيضًا اعتمادًا على الذوق الشخصي. هذا لا يعمل بشكل جيد في البادئة على الإطلاق بسبب انتظارين في نفس البيان.

لا أفهم المشاعر المعلنة بانتظام والتي تجعل الارتباطات غير ذاتية في كود Rust. إنها متكررة جدًا وشائعة في أمثلة التعليمات البرمجية ، خاصةً حول معالجة النتائج. نادرًا ما أرى أكثر من 2 ? في الكود الذي أتعامل معه.

أيضًا ، يتغير ما هو idiomatic على مدار عمر اللغة ، لذلك سأحرص بشدة على استخدامها كحجة.

لا أعرف ما إذا تم اقتراح شيء كهذا من قبل ، ولكن هل يمكن تطبيق البادئة await كلمة رئيسية على تعبير بأكمله؟ أخذ المثال الذي تم طرحه من قبل:

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

حيث get و send و json غير متزامن.

بالنسبة لي (مع خبرة قليلة في عدم التزامن في لغات البرمجة الأخرى) تبدو postfix expr await طبيعية: "افعل هذا ، ثم انتظر النتيجة."

كانت هناك بعض المخاوف من أن الأمثلة التالية تبدو غريبة:

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

ومع ذلك ، أود أن أزعم أنه يجب تقسيم هذا إلى عدة أسطر:

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

هذا أوضح بكثير وله ميزة إضافية تتمثل في أنه من السهل تحديد await ، إذا كان دائمًا في نهاية السطر.

في IDE ، تفتقر هذه البنية إلى "قوة النقطة" ، لكنها لا تزال أفضل من إصدار البادئة: عندما تكتب النقطة ثم تلاحظ أنك بحاجة إلى await ، عليك فقط حذف النقطة واكتب " await ". أي ، إذا كان IDE لا يوفر ميزة الإكمال التلقائي للكلمات الرئيسية.

بناء الجملة النقطي expr.await محير لأنه لا توجد كلمة رئيسية أخرى للتحكم في التدفق تستخدم نقطة.

أعتقد أن المشكلة تكمن في أنه على الرغم من وجود سلسلة ، والتي يمكن أن تكون جميلة في بعض الأحيان ، لا ينبغي أن نذهب إلى التطرف في القول بأن كل شيء يجب أن يتم في السلاسل. يجب أن نوفر أيضًا أدوات للبرمجة بأسلوب C أو Python. على الرغم من أن Python لا تحتوي تقريبًا على مكون تسلسلي هناك ، إلا أنه غالبًا ما يتم الإشادة برمزها ليكون قابلاً للقراءة. لا يشتكي مبرمجو بايثون من أن لدينا الكثير من المتغيرات المؤقتة.

ماذا عن postfix then ؟

لا أفهم المشاعر المعلنة بانتظام والتي تجعل الارتباطات غير ذاتية في كود Rust. إنها متكررة جدًا وشائعة في أمثلة التعليمات البرمجية ، خاصةً حول معالجة النتائج. نادرًا ما أرى أكثر من 2 ? في الكود الذي أتعامل معه.

أيضًا ، يتغير ما هو idiomatic على مدار عمر اللغة ، لذلك سأحرص بشدة على استخدامها كحجة.

ألهمني هذا الأمر لمسح كود Rust الحالي حيث يوجد ? أو أكثر في سطر واحد (قد يكون بإمكان شخص ما مسح الاستخدام متعدد الأسطر) قمت بمسح xi-editor ، alacritty ، ripgrep ، bat ، xray ، fd ، firecracker ، yew ، Rocket ، exa ، iron ، parity-ethereum ، tikv. هذه مشاريع Rust مع معظم النجوم.

ما وجدته هو أن حوالي 40 سطرًا 585562 سطرًا يستخدمان أو أكثر ? في سطر واحد. هذا هو 0.006٪ .

أريد أيضًا أن أشير إلى أن دراسة أنماط استخدام الكود الحالية لن تكشف عن تجربة المستخدم في كتابة كود جديد.

افترض أنك حصلت على وظيفة للتفاعل مع واجهة برمجة تطبيقات جديدة أو أنك جديد في استخدام الطلبات. هل من المحتمل أن تكتب

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

في طلقة واحدة فقط ؟ إذا كنت مستخدمًا جديدًا لواجهة برمجة التطبيقات ، فأنا أشك في أنك سترغب في التأكد من إنشاء الطلب بشكل صحيح ، أو الاطلاع على حالة الإرجاع ، أو التحقق من افتراضك حول ما تعيده واجهة برمجة التطبيقات هذه أو مجرد اللعب باستخدام واجهة برمجة التطبيقات مثل هذا:

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);

واجهة برمجة تطبيقات الشبكة لا تشبه بيانات الذاكرة ، فأنت لا تعرف ما هو موجود. هذا طبيعي جدًا للنماذج الأولية. وعندما تقوم بعمل النماذج الأولية ، فإنك تقلق بشأن كل شيء على ما يرام وليس الكثير من المتغيرات المؤقتة . يمكنك القول أنه يمكننا استخدام بنية postfix مثل:

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);

ولكن ، إذا كان لديك هذا بالفعل:

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);

كل ما عليك القيام به هو على الأرجح لفها في وظيفة ويتم إنجاز عملك ، حتى أن التسلسل لا يظهر في هذه العملية.

ما وجدته هو أن حوالي 40 سطرًا فقط من إجمالي 585562 سطرًا يستخدم سطرين أو أكثر؟ في سطر واحد.

أود أن أقترح أن هذا ليس قياسًا مفيدًا. ما يهم حقًا هو أكثر من عامل تدفق تحكم واحد لكل تعبير. وفقًا للنمط النموذجي (rustfmt) ، سينتهي بهم الأمر دائمًا تقريبًا في أسطر مختلفة في الملف على الرغم من أنهم ينتمون إلى نفس التعبير ، وبالتالي سيتم تقييدهم بالمقدار الذي يهم postfix (نظريًا) مقابل await .

لا ينبغي أن نذهب إلى أقصى الحدود بالقول إن كل شيء يجب أن يتم في السلاسل

هل قال أحد أن كل شيء _يجب_ أن يتم بالسلاسل؟ كل ما رأيته حتى الآن هو أنه يجب أن يكون التسلسل في الحالات التي يكون فيها منطقيًا ، كما هو الحال في الكود المتزامن.

فقط حوالي 40 سطرًا من إجمالي 585562 سطرًا يستخدم سطرين أو أكثر؟ في سطر واحد.

لست متأكدًا من أن ذلك وثيق الصلة بالبادئة مقابل postfix. سألاحظ أن _none_ من أمثلة C # الخاصة بي للرغبة في postfix تتضمن عدة await s في السطر ، ولا حتى عدة await s في بيان. ومثالCentril لتخطيط postfix المحتمل لم يضع عدة await s على سطر أيضًا.

قد تكون المقارنة الأفضل للأشياء المقيدة بالسلاسل من ? ، مثل هذه الأمثلة من المترجم:

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() {

تحرير: يبدو أنك تغلبت علي هذه المرة ، @ CAD97 : قليلاً

هذا مشابه بشكل مذهل لرمز وعد javascipt بالكثير من then s. لن أسمي هذا متزامن. يكاد يكون من المؤكد أن السلاسل تحصل على await وتتظاهر بأنها متزامنة.

بادئة بالمحددات الإلزامية

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 @ scottmcm نقطة جيدة. كنت أعرف أن هناك قيودًا على ما أقيسه:

(قد يكون أحد الأشخاص يمكنه مسح استخدام متعدد الأسطر)

أفعل ذلك لأن skade ذكر التشابه بين await و ? ، لذلك أجريت تحليلًا سريعًا. أنا أقدم فكرة لا أقوم ببحث جاد. أعتقد أنه سيكون من الصعب إجراء تحليل دقيق بالنظر إلى الكود ، أليس كذلك؟ قد يحتاج إلى تحليل وتحديد التعبيرات ، التي لست على دراية بها. آمل أن يقوم شخص ما بهذا التحليل.

هل قال أحد أن كل شيء يجب أن يتم بالسلاسل؟ كل ما رأيته حتى الآن هو أنه يجب أن يكون التسلسل مريحًا في الحالات التي يكون فيها ذلك منطقيًا ، كما يحدث في الكود المتزامن.

ما أقوله هو أنه إذا تمت إضافة postfix فقط ، فسيكون غير مريح عندما يبدو أسلوب C / Python جيدًا (بالطبع imo). أنا أيضا أتناول التسلسل قد لا تكون هناك حاجة عند النموذج الأولي .

لدي شعور بأن اتجاه هذا الخيط يركز كثيرًا على التسلسل المفرط ، وكيفية جعل كود الانتظار موجزا قدر الإمكان.

أريد أن أشجع الجميع بدلاً من ذلك على النظر في كيفية اختلاف الشفرة غير المتزامنة عن المتغيرات المتزامنة ، وكيف سيؤثر ذلك على الاستخدام واستخدام الموارد. يذكر أقل من 5 تعليقات في 400 في هذا الموضوع تلك الاختلافات. إذا لم تكن على دراية بهذه الاختلافات ، فاحصل على الإصدار الليلي الحالي وحاول كتابة جزء لائق من الشفرة غير المتزامنة. بما في ذلك محاولة الحصول على نسخة اصطلاحية (غير مجمعة) غير متزامنة / انتظار من تجميع جزء من الكود تمت مناقشته في آخر 20 مشاركة.

سيحدث فرقًا ما إذا كانت الأشياء موجودة بين نقاط العائد / الانتظار فقط ، وما إذا كانت المراجع ثابتة عبر نقاط الانتظار ، وغالبًا ما تجعل بعض المتطلبات الخاصة حول العقود الآجلة من غير الممكن كتابة رمز موجز كما هو متصور في هذا الموضوع. على سبيل المثال ، لا يمكننا وضع دوال عشوائية غير متزامنة في أدوات دمج عشوائية ، لأن هذه الدوال قد لا تعمل مع أنواع !Unpin . إذا أنشأنا عقودًا آجلة من الكتل غير المتزامنة ، فقد لا تكون متوافقة بشكل مباشر مع المجمعات مثل join! أو select! ، لأنها تحتاج إلى أنواع مثبتة ومدمجة ، لذا فإن المكالمات الإضافية إلى pin_mut! و .fuse() قد يكون مطلوبًا داخل.

بالإضافة إلى العمل مع الكتل غير المتزامنة ، تعمل الأدوات المساعدة الجديدة القائمة على الماكرو join! و select! بشكل أفضل بكثير من المتغيرات القديمة. وهذه هي الطريقة المفرطة التي يتم تقديمها هنا غالبًا كأمثلة

لا أعرف كيف يمكن أن يعمل انتظار postfix مع .unwrap() في أبسط مثال tokio هذا

let response = await!({
    client.get(uri)
        .timeout(Duration::from_secs(10))
}).unwrap();

إذا تم اعتماد البادئة ، فستصبح

let response = await {
    client.get(uri).timeout(Duration::from_secs(10))
}.unwrap();

ولكن إذا تم اعتماد postfix ،

client.get(uri).timeout(Duration::from_secs(10)).await.unwrap()
client.get(uri).timeout(Duration::from_secs(10)) await.unwrap()

هل هناك أي تفسير بديهي يمكن أن نقدمه للمستخدمين؟ تتعارض مع القواعد الحالية. await حقل؟ أو await هو ربط له طريقة تسمى unwrap() ؟ مؤسف جدا! نقوم بتفكيك الكثير عندما نبدأ المشروع. انتهكت قواعد تصميم متعددة في The Zen of Python.

الحالات الخاصة ليست خاصة بما يكفي لخرق القواعد.
إذا كان من الصعب شرح التنفيذ ، فهذه فكرة سيئة.
في مواجهة الغموض ، ارفض إغراء التخمين.

هل هناك أي تفسير بديهي يمكن أن نقدمه للمستخدمين؟ تتعارض مع القواعد الحالية. await حقل؟ أو await هو ربط له طريقة تسمى unwrap() ؟ مؤسف جدا! نقوم بتفكيك الكثير عندما نبدأ المشروع. انتهكت قواعد تصميم متعددة في The Zen of Python.

أود أن أقول ، على الرغم من وجود عدد كبير جدًا من المستندات في docs.rs تستدعي unwrap ، يجب استبدال unwrap بـ ? في العديد من حالات العالم الحقيقي. في عقد الإيجار ، هذا هو عملي.

ما وجدته هو أن حوالي 40 سطرًا فقط من إجمالي 585562 سطرًا يستخدم سطرين أو أكثر؟ في سطر واحد.

أود أن أقترح أن هذا ليس قياسًا مفيدًا. ما يهم حقًا هو أكثر من عامل تدفق تحكم واحد لكل تعبير. وفقًا للنمط النموذجي (rustfmt) ، سينتهي بهم الأمر دائمًا تقريبًا في أسطر مختلفة في الملف على الرغم من أنهم ينتمون إلى نفس التعبير ، وبالتالي سيتم تقييدهم بالمقدار الذي يهم postfix (نظريًا) مقابل await .

أنا لا أفعل يمكن أن يكون هناك حد لهذا النهج.

قمت بمسح مرة أخرى لـ xi-editor، alacritty، ripgrep، bat، xray، fd، firecracker، yew، Rocket، exa، iron، parity-ethereum، tikv. هذه مشاريع Rust مع معظم النجوم. هذه المرة بحثت عن النمط:

xxx
  .f1()
  .f2()
  .f3()
  ...

وما إذا كان هناك العديد من مشغلي التحكم في التدفق في هذه التعبيرات.

لقد حددت فقط 15 من أصل 7066 سلسلة بها عدة مشغلين للتحكم في التدفق. هذا 0.2٪ . تمتد هذه الأسطر 167 من أصل 585562 سطرًا من التعليمات البرمجية. هذا 0.03٪ .

cenwangumass نشكرك على الوقت

أحد الاعتبارات هو أنه نظرًا لأن Rust يحتوي على إعادة ربط متغيرة مع let ، فقد يكون وسيطًا مقنعًا للبادئة await ، لأنه إذا تم استخدامه باستمرار ، سيكون لديك سطر منفصل من التعليمات البرمجية لكل await تنتظر -نقطة. الميزة ذات شقين: المكدس ، حيث أن رقم السطر يعطي سياقًا أكبر فيما يتعلق بمكان حدوث المشكلة ، وسهولة تصحيح نقطة التوقف ، حيث أنه من الشائع أن ترغب في تعيين نقطة توقف على كل نقطة انتظار منفصلة لفحص المتغيرات ، قد تفوق إيجاز سطر واحد من التعليمات البرمجية.

أنا شخصياً أشعر بالحيرة بين نمط البادئة و postfix sigil رغم أنه بعد قراءة https://github.com/rust-lang/rust/issues/57640#issuecomment -457457727 ربما أؤيد في الغالب سيجيل postfix.

نمط البادئة المقدمة:

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)
}

تم تقديم نمط postfix:

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)
}

تم تقديم سيجيل postfix @:

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)
}

تم تقديم علامة سيجيل postfix:

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)
}

لم أر عددًا كافيًا من الأشخاص يتحدثون عن await مقابل Stream s. على الرغم من أنه خارج النطاق ، فإن اتخاذ القرار حول await مع القليل من التبصر من Stream قد يستحق الجهد المبذول.

سنحتاج على الأرجح إلى شيء مثل هذا:
صيغة البادئة

for await response in stream {
    let response = response?;
    ...
}

// In which case an `await?` variant might be beneficial
for await? response in stream {
    ...
}

بناء جملة 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!()? {
    ...
}

ربما يكون بناء الجملة الأكثر راحة / اتساقًا

let results: Vec<Result<_, _>> = ...;
for value in? results {
    ...
}
for response await? stream {
    ...
}

أريد فقط التأكد من مناقشة هذه الأمثلة قليلاً على الأقل ، لأنه في حين أن postfix جيد لتسلسل Future ، للوهلة الأولى يبدو أنها الأقل بديهية مقابل Stream . لست متأكدًا من الحل الصحيح هنا. ربما postfix await لـ Future ونمط مختلف await مقابل Stream ؟ لكن بالنسبة للصيغ المختلفة ، سنحتاج إلى التأكد من أن التحليل غير غامض.

هذه كلها مجرد أفكاري الأولية ، قد يكون من المفيد إعطاء حالة استخدام Stream مزيدًا من التفكير من منظور postfix. أي شخص لديه أي أفكار حول كيفية عمل postfix بشكل جيد مع Stream ، أو إذا كان يجب أن يكون لدينا بناءان؟

إن بناء الجملة لتكرار البث ليس شيئًا سيحدث لبعض الوقت الذي أتخيله. من السهل إلى حد ما استخدام حلقة while let والقيام بذلك يدويًا على الرغم من:

حقل Postfix
while let Some(value) = stream.try_next().await? {
}
بادئة بأسبقية "متسقة" وسكر
while let Some(value) = await? stream.try_next() {
}
بادئة بالمحددات الإلزامية
while let Some(value) = await { stream.try_next() }? {
}

ستكون الطريقة الحالية لعمل الأشياء (بالإضافة إلى await ).


سألاحظ أن هذا المثال يجعل "البادئة مع المحددات" تبدو سيئة بشكل خاص بالنسبة لي. بينما قد يبدو await(...)? لمسة أجمل ، إذا أردنا عمل "بادئة مع المحددات" أتمنى أن نسمح فقط بنوع واحد من المحددات (مثل try { ... } ).

ظل سريع ، لكن لن تكون عبارة "انتظار مع المحددات" مجرد ... بادئة عادية تنتظر؟ ينتظر await تعبيرًا ، لذا فإن وجود await expr و await { expr } هو نفسه بشكل أساسي. لن يكون من المنطقي الانتظار مع المحددات فقط وعدم الحصول عليها بدونها أيضًا ، خاصة وأن أي تعبير يمكن أن يُحاط بـ {} ويظل نفس التعبير.

قادني السؤال حول التدفقات إلى فكرة أنه بالنسبة إلى Rust ، قد يكون من الطبيعي جدًا أن يكون الانتظار متاحًا ليس فقط كمشغل في التعبيرات ، ولكن أيضًا كمعدِّل في الأنماط:

// These two lines mean the same - both await the future
let x = await my_future;
let async x = my_future;

والتي يمكن أن تعمل بشكل طبيعي مع for

for async x in my_stream { ... }

تضمين التغريدة

المشكلة التي تحلها "البادئة بانتظار المحددات الإلزامية" هي سؤال الأسبقية حول ? . مع محددات الإلزام ، لا يوجد سؤال أسبقية.

انظر try { .... } لبناء جملة _similar_ (ثابت). تحتوي المحاولة على محددات إلزامية للسبب نفسه تقريبًا - كيف تتفاعل مع ? - نظرًا لأن ? داخل الأقواس يختلف تمامًا عن الموجود بالخارج.

yazaddaruvala لا أعتقد أنه يجب أن تكون هناك طريقة محددة غير Iterator<Item = Result<...>> .

// postfix syntax
for response in stream await {
    ...
}

هذا يعني أن stream: impl Future<Output = Iterator> وأنت تنتظر أن يكون المكرر متاحًا ، وليس كل عنصر. لا أرى فرصة كبيرة لشيء أفضل من async for item in stream (بدون ميزة أكثر عمومية مثل إشارات tanriol ).


ivandardi ، يكون الاختلاف هو الأسبقية بمجرد أن تبدأ في

await (foo.bar()).baz()?;
await { let foo = quux(); foo.bar() }.baz()?;

التي تحلل (مع ما يكفي من المحددات بحيث لا لبس فيها لجميع المتغيرات الثلاثة)

// 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()?;

في رأيي ، تبدو أمثلة https://github.com/rust-lang/rust/issues/57640#issuecomment -457457727 جيدة تمامًا مع نقل المحددات من موضع (await foo).bar إلى المركز await(foo).bar :

await(response.Content.ReadAsStringAsync()).Should().Be(text);
var previous = await(branch.ListHistoryAsync(timestampUtc, null, cancellationToken, 1)).HistoryEntries.SingleOrDefault();

إلى آخره. هذا التخطيط مألوف من استدعاءات الوظائف العادية ، ومألوف من تدفق التحكم القائم على الكلمات الرئيسية ، ومألوف من اللغات الأخرى (بما في ذلك C #). هذا يتجنب كسر الميزانية الغريبة ، ولا يبدو أنه يسبب أي مشاكل بعد- await تسلسل.

على الرغم من أنه من المسلم به أنه يواجه نفس المشكلة مثل try! للتسلسل متعدد - await ، لا يبدو أن هذا كبير من الصفقة كما كان لـ Result ؟ أفضل بكثير الحد من غرابة النحو هنا بدلاً من استيعاب نمط غير شائع نسبيًا وغير قابل للقراءة.

ومألوف من اللغات الأخرى (بما في ذلك C #)

هذه ليست طريقة عمل الأسبقية في C # (أو Javascript ، أو Python)

await(response.Content.ReadAsStringAsync()).Should().Be(text);

يعادل

var future = (response.Content.ReadAsStringAsync()).Should().Be(text);
await future;

(عامل النقطة له أسبقية أعلى من await ، لذا فإن الروابط أكثر إحكامًا حتى إذا حاولت جعل await يبدو وكأنه استدعاء دالة).

أعلم أن هذا ليس الادعاء الذي كنت أقوله. مجرد أن الترتيب يظل كما هو ، لذا فإن الشيء الوحيد الذي يحتاج الناس إلى إضافته هو الأقواس ، وأن (بشكل منفصل) صيغة استدعاء الوظيفة الحالية لها الأسبقية الصحيحة.

هذا التخطيط مألوف [...] من تدفق التحكم الحالي المستند إلى الكلمات الرئيسية

أنا أعترض. نحن في الواقع _ نحذر _ من استخدام هذا التنسيق في تدفق التحكم الحالي المستند إلى الكلمات الرئيسية:

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

و rustfmt يغير ذلك إلى return (4); ، إضافة مسافة.

هذا لا يؤثر حقًا على النقطة التي أحاول توضيحها ، وأشعر وكأنني أقسم الشعر. return (4) ، return(4) ، إنها ليست أكثر من مشكلة تتعلق بالنمط.

قرأت العدد بالكامل وبدأت أشعر أن الأقواس والبادئة {} جيدة ، لكنها اختفت بعد ذلك

دعونا نرى:

let foo = await some_future();
let bar = await {other_future()}?.bar

تبدو جميلة ، أليس كذلك؟ ولكن ماذا لو أردنا ربط المزيد من await في سلسلة واحدة؟

let foo = await some_future();
let bar = await {
                await {other_future()}?.bar_async()
          }?;

IMHO يبدو أسوأ بكثير من

let foo = some_future() await;
let bar = other_future() await?
           .bar_async() await?;

ومع ذلك ، فأنا لا أؤمن بالتسلسل غير المتزامن ، كما هو مكتوب في المثال الخاص بي على استخدام async/await في الخادم الفائق ، من فضلك ، وضح أين يمكن أن تكون السلسلة مفيدة باستخدام هذا المثال الملموس. أنا مُغري حقًا ، لا توجد نكات.


حول الأمثلة السابقة ، معظمها "تالف" بطريقة ما بواسطة أدوات الدمج. لن تحتاجهم أبدًا تقريبًا لأنك تنتظر. على سبيل المثال

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()
}?;

انه ببساطة

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()?

إذا كانت العقود الآجلة قد ترجع خطأً بسيطًا مثل:

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 ،

لقد قمت بإنشاء سلسلة رسائل Pre-RFC مقابل بناء جملة val.[await future] على https://internals.rust-lang.org/t/pre-rfc-extended-dot-operator-as-possible-syntax-for-await السلاسل / 9304

يجب مناقشة الأسئلة الإجرائية Dowwie في مكان آخر ، على سبيل المثال https://internals.rust-lang.org

أعتقد أن لدينا معسكرين في هذه المناقشة: الأشخاص الذين يرغبون في التأكيد على نقاط العائد مقابل الأشخاص الذين يرغبون في تقليل التركيز عليهم.

يحتوي Async in Rust ، حوالي 2018 على الدعاية الدعائية التالية:

التدوين غير المتزامن / انتظار هو طريقة لجعل البرمجة غير المتزامنة أكثر شبهاً بالبرمجة المتزامنة.

أخذ مثال tokio البسيط من الأعلى (تغيير unwrap() إلى ? ):

let response = await!({
    client.get(uri).timeout(Duration::from_secs(10))
})?;

وتطبيق سيجيل postfix يؤدي إلى

let response = client.get(uri).timeout(Duration::from_secs(10))!?

التي تشبه إلى حد بعيد البرمجة المتزامنة وتفتقر إلى مشتتات مشتتة من await لكل من تدوين البادئة و postfix.

لقد استخدمت ! كـ sigil postfix في هذا المثال على الرغم من أن هذا الاقتراح حصل لي على بعض الأصوات السلبية في تعليق سابق. لقد فعلت ذلك لأن علامة التعجب لها معنى متأصل بالنسبة لي في هذا السياق الذي يفتقر إليه كل من @ (الذي يقرأه عقلي كـ "at") و # . لكن هذه مجرد مسألة ذوق وليست وجهة نظري.

أنا أفضل ببساطة أي سيجيل لاحقة مكونة من حرف واحد على جميع البدائل الأخرى على وجه التحديد لأنها غير مزعجة للغاية ، مما يجعل من السهل السيطرة على ما تفعله الشفرة التي تقرأها مقابل ما إذا كانت غير متزامنة أم لا ، والتي سأفكر في تفاصيل التنفيذ. لن أقول أنه ليس مهمًا على الإطلاق ، لكنني أزعم أنه أقل أهمية لتدفق الكود من العائد المبكر في حالة ? .

بعبارة أخرى: إنك تهتم بشكل أساسي بنقاط الإنتاجية أثناء كتابة التعليمات البرمجية غير المتزامنة ، وليس كثيرًا أثناء قراءتها. نظرًا لأن الكود يُقرأ كثيرًا أكثر من كتابته ، فإن بناء جملة غير مزعج لـ await سيكون مفيدًا.

أود تذكير تجربة فريق C # (التأكيد هو لي):

ولهذا السبب أيضًا لم نختار أي صيغة "ضمنية" لـ "انتظار". من الناحية العملية ، كان هذا شيئًا أراد الناس التفكير فيه بوضوح شديد ، وأرادوا أن يكون في المقدمة والوسط في الكود الخاص بهم حتى يتمكنوا من الانتباه إليه. ومن المثير للاهتمام ، أنه حتى بعد سنوات ، ظل هذا الاتجاه قائما. على سبيل المثال ، نأسف أحيانًا بعد سنوات عديدة على أن شيئًا ما مفرط الإسهاب . بعض الميزات جيدة بهذه الطريقة في وقت مبكر ، ولكن بمجرد أن يشعر الناس بالراحة معها ، فإنهم يكونون أكثر ملاءمة مع شيء أكثر تضييقًا. لم يكن هذا هو الحال مع "انتظار". لا يزال يبدو أن الناس يحبون الطبيعة الثقيلة لتلك الكلمة الرئيسية

شخصيات Sigil غير مرئية تقريبًا دون تمييز مناسب وتكون أقل ودية للقادمين الجدد (كما قلت من قبل).


لكن عند الحديث عن sigils ، من المحتمل ألا يربك @ المتحدثين بغير اللغة الإنجليزية (مثل أنا) لأننا لا نقرأها على أنها at . لدينا اسم آخر تمامًا له لا يتم تشغيله تلقائيًا عندما تقرأ الكود ، لذا فأنت تفهمه فقط ككلمة هيروغليفية كاملة ، بمعناها الخاص.

@ huxi لدي شعور بأن السيجيل قد تم استبعادها بالفعل بالفعل. راجع منشور Centril مرة أخرى لمعرفة الأسباب التي تجعلنا نستخدم كلمة await .


أيضًا ، بالنسبة إلى كل من لا يحب postfix ينتظر كثيرًا ، ويستخدم الحجة البراغماتية "حسنًا ، لا يوجد الكثير من التعليمات البرمجية الواقعية التي يمكن تقييدها ، لذلك لا ينبغي إضافة انتظار postfix" ، إليكم حكاية صغيرة (من المحتمل أن أكون جزارًا بسبب ذاكرة سيئة):

بالعودة إلى الحرب العالمية الثانية ، كانت إحدى الدول المقاتلة تفقد الكثير من الطائرات هناك. كان عليهم تعزيز طائراتهم بطريقة ما. لذا فإن الطريقة الواضحة لمعرفة المكان الذي يجب أن يركزوا فيه هي إلقاء نظرة على الطائرات التي عادت ومعرفة أين أصاب الرصاص أكثر. تبين أنه في الطائرة ، كان معدل 70٪ من الثقوب في الأجنحة ، و 10٪ في منطقة المحرك ، و 20٪ في مناطق أخرى من الطائرة. إذن مع هذه الإحصائيات ، سيكون من المنطقي تقوية الأجنحة ، أليس كذلك؟ خطأ! والسبب في ذلك هو أنك تنظر فقط إلى الطائرات التي عادت. وفي تلك الطائرات ، يبدو أن ضرر الرصاص على الأجنحة ليس بهذا السوء. ومع ذلك ، فإن جميع الطائرات التي عادت تعرضت لأضرار طفيفة في منطقة المحرك ، مما قد يؤدي إلى استنتاج مفاده أن الأضرار الجسيمة التي لحقت بمنطقة المحرك قاتلة. لذلك يجب تعزيز منطقة المحرك بدلاً من ذلك.

نقطتي في هذه الحكاية هي: ربما لا يوجد الكثير من أمثلة التعليمات البرمجية الواقعية التي يمكن أن تستفيد من انتظار postfix لأنه لا يوجد postfix في انتظار اللغات الأخرى. لذلك اعتاد الجميع على كتابة البادئة التي تنتظر الشفرة وهي معتادة جدًا عليها ، لكن لا يمكننا أبدًا معرفة ما إذا كان الناس سيبدأون في الانتظار أكثر إذا كان لدينا انتظار postfix.

لذلك أعتقد أن أفضل مسار للعمل هو الاستفادة من المرونة التي توفرها لنا الإنشاءات الليلية واختيار بنية بادئة واحدة وبناء جملة بعد الإصلاح لإضافتها إلى اللغة. أنا أصوت لبادئة "الأسبقية الواضحة" المنتظرة ، و postfix .await انتظارك. هذه ليست اختيارات بناء الجملة النهائية ، لكننا نحتاج إلى اختيار واحد لنبدأ به ، وأعتقد أن هذين الخيارين في بناء الجملة سيوفران تجربة أحدث مقارنة بخيارات بناء الجملة الأخرى. بعد أن ننفذها ليلاً ، يمكننا الحصول على إحصاءات الاستخدام وآراء العمل باستخدام كود حقيقي باستخدام كلا الخيارين ويمكننا بعد ذلك متابعة مناقشة بناء الجملة ، هذه المرة مدعومة بحجة عملية أفضل.

ivandardi الحكاية ثقيلة جدًا ولا تناسب IMHO: إنها حكاية محفزة في بداية الرحلة ، لتذكير الناس بالبحث عن ما هو غير واضح وتغطية جميع الزوايا ، فهي _لا _ تستخدم ضد المعارضة في نقاش. كان مناسبًا لـ Rust 2018 ، حيث تم طرحه وليس ملزماً بقضية محددة. لاستخدامها ضد جانب آخر في المناظرة هو أمر غير مهذب ، ستحتاج إلى تأكيد موقف الشخص الذي يرى المزيد أو لديه رؤية أكثر لجعله يعمل. لا أعتقد أن هذا ما تريده. أيضًا ، بالبقاء في الصورة ، ربما لا يكون postfix بأي من اللغات لأن postfix لم يصل أبدًا إلى المنزل ؛).

قام الأشخاص بالفعل بالقياس والنظر: لدينا مشغل قابل للتسلسل في Rust ( ? ) والذي نادرًا ما يستخدم للتسلسل. https://github.com/rust-lang/rust/issues/57640#issuecomment -458022676

لقد تم أيضًا تغطية أن هذا _فقط _ قياس للحالة الحالية ، كما وضع cenwangumass بشكل جيد هنا: https://github.com/rust-lang/rust/issues/57640#issuecomment -457962730. لذلك ليس الأمر كما لو أن الناس يستخدمون هذا كأرقام نهائية.

أريد قصة على الرغم من أن التسلسل يصبح أسلوبًا مهيمنًا في منتصف الطريق إذا كان postfix حقًا هو السبيل للذهاب. أنا لست مقتنعًا بذلك ، رغم ذلك. لقد ذكرت أيضًا أعلاه أن المثال السائد الذي تم استخدامه هنا ( reqwest ) لا يتطلب سوى انتظارين لأن واجهة برمجة التطبيقات اختارت ذلك ، ويمكن بسهولة إنشاء واجهة برمجة تطبيقات سلسلة مريحة دون الحاجة إلى انتظارين اليوم.

بينما أنا أقدر الرغبة في مرحلة البحث ، أود أن أشير إلى أن الانتظار قد تأخر بالفعل بشكل خطير ، فإن أي مرحلة بحث ستجعل الأمر أسوأ. ليس لدينا أيضًا طريقة لجمع الإحصاءات حول العديد من قواعد الرموز ، فسيتعين علينا تجميع مجموعة التحدث بأنفسنا. أرغب في إجراء المزيد من البحث عن المستخدمين هنا ، لكن هذا يستغرق وقتًا ، الإعداد غير موجود بعد والأشخاص لتشغيله.

@ Pzixel بسبب السماح

let foo = await some_future();
let bar = await {
                await {other_future()}?.bar_async()
          }?;

مثل

""
let foo = انتظار some_future () ؛
دع البار = انتظار {other_future ()} ؟. bar_async ()؛
دع البار = انتظر {bar} ؟؛

Pzixel هل لديك أي مصدر لاقتباس تجربة فريق C #؟ لأن الوحيد الذي وجدته هو هذا التعليق . هذا ليس معناه اتهام أو شيء من هذا القبيل. أود فقط قراءة النص الكامل.

يترجم عقلي @ إلى "at" بسبب استخدامه في عناوين البريد الإلكتروني. هذا الرمز يسمى "Klammeraffe" في لغتي الأم والتي تُترجم تقريبًا إلى "القرد المتشبث". أنا في الواقع أقدر أن عقلي استقر على "في" بدلاً من ذلك.

سنتي 2 ، كمستخدم Rust جديد نسبيًا (مع خلفية C ++ ، لكن هذا لا يساعد حقًا).

ذكر البعض منكم الوافدين الجدد ، إليكم ما أعتقده:

  • بادئ ذي بدء ، يبدو أن الماكرو await!( ... ) لا غنى عنه بالنسبة لي ، لأنه سهل الاستخدام ولا يمكن إساءة فهمه. يبدو مشابهًا لـ println!() ، panic!() ، ... وهذا ما حدث لـ try! بعد كل شيء.
  • المحددات الإلزامية هي أيضًا بسيطة ولا لبس فيها.
  • لن يكون من الصعب قراءة أو كتابة إصدار postfix سواء كان حقلًا أو وظيفة أو ماكرو IMHO ، لأن الوافدين الجدد سيقولون فقط "حسنًا ، هكذا تفعل ذلك". تنطبق هذه الحجة أيضًا على الكلمات الرئيسية postfix ، وهي "غير عادية" ولكن "لماذا لا".
  • فيما يتعلق بتدوين البادئة بأسبقية مفيدة ، أعتقد أنها ستبدو مربكة. حقيقة أن await يربط تشديد سوف تهب بعض العقول، وأعتقد أن بعض المستخدمين فقط سوف يفضلون وضع الكثير من الأقواس لجعله مسح (تسلسلي أو لا).
  • الأسبقية الواضحة بدون سكر سهلة الفهم والتدريس. ثم ، لتقديم السكر حوله ، فقط اتصل بـ await? كلمة رئيسية مفيدة. مفيد لأنه يزيل الحاجة إلى الأقواس المرهقة:
    " c# let response = (await http::get("https://www.rust-lang.org/"))?; // see kids? انتظر ... unwraps the future, so you have to use ؟ عامل انتظار to unwrap the Result // but there is some sugar if you want, thanks to the ؟ `
    دع الرد = انتظر؟ http :: get ("https://www.rust-lang.org/") ؛
    // ولكن لا يجب أن تترابط ، لأن بناء الجملة هذا لا يؤدي إلى تعليمات برمجية متسلسلة يمكن قراءتها
- 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")
};

كيف تعلم ذلك:

  • ( await!() ممكن ماكرو)
  • يوصى بالبادئة عندما لا يحدث تسلسل ، مع السكر (انظر أعلاه)
  • أوصت postfix مع التسلسل
  • من الممكن مزجها ، لكن لا ينصح بذلك
  • من الممكن استخدام البادئة عند التسلسل باستخدام أدوات التجميع

من خلال تجربتي الشخصية ، أود أن أقول إن البادئة المنتظرة ليست مشكلة في التسلسل.
يُلحق التسلسل الكثير في كود Javascript مع البادئة في انتظار ومجمعات مثل f.then(x => ...) دون فقدان أي قابلية للقراءة في رأيي ولا يبدو أنهم يشعرون بأي حاجة لتداول المجمعات من أجل postfix.

لكى يفعل:

let ret = response.await!().json().await!().to_string();

بالضبط مثل:

let ret = await future.then(|x| x.json()).map(|x| x.to_string());

لا أرى حقًا فوائد postfix تنتظر سلاسل التجميع أعلاه.
أجد أنه من الأسهل فهم ما يحدث في المثال الثاني.

لا أرى أي مشاكل في قابلية القراءة أو التسلسل أو الأولوية في الكود التالي:

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)
}

سأؤيد هذه البادئة في انتظارك للأسباب التالية:

  • الإلمام باللغات الأخرى.
  • من المحاذاة مع الكلمات الرئيسية الأخرى (عودة ، كسر ، متابعة ، العائد ، إلخ ...).
  • لا يزال يبدو وكأنه Rust (مع الدمج كما نفعل مع التكرارات).

سيكون Async / انتظار إضافة رائعة للغة وأعتقد أن المزيد من الأشخاص سيستخدمون المزيد من الأشياء ذات الصلة بالمستقبل بعد هذه الإضافة واستقرارها.
قد يكتشف البعض البرمجة غير المتزامنة لأول مرة مع Rust.
لذلك قد يكون من المفيد إبقاء تعقيد اللغة منخفضًا بينما يصعب تعلم المفاهيم الكامنة وراء عدم التزامن بالفعل.

ولا أعتقد أن شحن كل من انتظار البادئة و postfix فكرة جيدة ولكن هذا مجرد رأي شخصي.

huxi Yep ، لقد ربطته بالفعل أعلاه ، لكن بالطبع يمكنني إعادة ذلك: https://github.com/rust-lang/rust/issues/50547#issuecomment -388939886

يترجم عقلي @ إلى "في" بسبب استخدامه في عناوين البريد الإلكتروني. هذا الرمز يسمى "Klammeraffe" في لغتي الأم والتي تُترجم تقريبًا إلى "القرد المتشبث". أنا في الواقع أقدر أن عقلي استقر على "في" بدلاً من ذلك.

لدي قصة مشابهة: إنها "كلب" في لغتي ، لكنها لا تؤثر على قراءة البريد الإلكتروني.
من الجميل أن أرى تجربتك رغم ذلك.

llambda كان السؤال حول await { foo }? بدلاً من await? foo أو foo await? يبدو غير صحيح

تضمين التغريدة
منشور لطيف. لكنني لا أعتقد أن اقتراحك الثاني هو طريقة جيدة. عندما تقدم طريقتين للقيام بشيء ما ، فإنك لا تنتج سوى الارتباك والمتاعب ، على سبيل المثال تحتاج إلى خيار rustfmt أو تصبح التعليمات البرمجية الخاصة بك في حالة من الفوضى.

Hirevo async/await من المفترض أن تزيل الحاجة في المجمعات . دعنا لا نعود إلى "ولكن يمكنك أن تفعل ذلك مع الدمج وحدها". الرمز الخاص بك هو نفس

future.then(|x| x.json()).map(|x| x.to_string()).map(|ret| ... );

فلنتخلص من async/await ؟

ومع ذلك ، فإن أدوات الدمج أقل تعبيرًا ، وأقل ملاءمة ، وأحيانًا لا يمكنك التعبير عما يمكن أن await ، على سبيل المثال الاقتراض بين نقاط الانتظار (ما تم تصميمه من أجل Pin ).

لا أرى أي مشاكل في قابلية القراءة أو التسلسل أو الأسبقية في الكود التالي

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)
}

أنا افعل:

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)
}

شيء غريب يجري؟ ليس هناك أى مشكلة:

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)
}

من الصعب جعلها تعمل مع الدمج. ناهيك عن أن وظائفك ? في then / map لن تعمل كما هو متوقع ، ولن تعمل الكود الخاص بك بدون into_future() وبعض الدوال الأخرى أشياء غريبة لا تحتاجها في تدفق async/await .

أوافق على أن اقتراحي ليس هو الأمثل لأنه يقدم الكثير من البنية القانونية باستخدام الكلمة الرئيسية async . لكنني أعتقد أن القواعد سهلة الفهم وأنها تلبي جميع حالات الاستخدام لدينا.

ولكن مرة أخرى ، قد يعني ذلك الكثير من الاختلافات المسموح بها:

  • await!(...) ، للتوافق مع try!() ووحدات الماكرو المعروفة مثل println!()
  • await ، await(...) ، await { ... } ، ie. تركيب البادئة بدون سكر
  • await? ، await?() ، await? {} ، على سبيل المثال. بناء الجملة مع السكر
  • ... await ، أي. صيغة postfix (زائد ... await? ، لكن هذا ليس سكرًا)
  • كل المجموعات من هؤلاء ، ولكن لا يتم تشجيعها (انظر رسالتي السابقة )

لكن في الممارسة العملية ، نتوقع فقط أن نرى:

  • بادئة بدون Result s: await أو await { ... } للتوضيح باستخدام التعبيرات الطويلة
  • بادئة بـ Result s: await? أو await? {} للتوضيح باستخدام التعبيرات الطويلة
  • postfix عند التسلسل: ... await ، ... await?

+1 لشحن الانتظار! () ماكرو إلى مستقر. غدا إن أمكن 😄

هناك الكثير من التكهنات حول كيفية استخدام هذا النمط ومخاوف بشأن بيئة العمل في مثل هذه الحالات. أقدر هذه المخاوف ، لكنني لا أرى سببًا مقنعًا لعدم إمكانية أن يكون هذا تغييرًا تكراريًا سيسمح ذلك بتوليد مقاييس استخدام حقيقية يمكنها لاحقًا إبلاغ التحسين (بافتراض الحاجة إليها).

إذا كان اعتماد عدم التزامن Rust يمثل جهدًا أكبر للصناديق / المشاريع الرئيسية أكثر من أي جهد لاحق لإعادة البناء _ اختياري_ للانتقال إلى بناء جملة أكثر إيجازًا / تعبيرًا ، فأنا أؤيد بشدة السماح بجهود التبني هذه للبدء الآن. استمرار عدم اليقين يسبب الألم.

تضمين التغريدة
(أولاً ، لقد ارتكبت خطأ في استدعاء الوظيفة fetch_user ، وسأصلحها في هذا المنشور)

أنا لا أقول أن عدم التزامن / الانتظار لا طائل منه وأنه يجب علينا إزالته من أجل المجمعات.
يسمح Await بربط قيمة من المستقبل بمتغير ضمن نفس النطاق بعد أن يتم حلها وهو أمر مفيد حقًا ولا يمكن حتى باستخدام أدوات الدمج وحدها.
إزالة الانتظار لم تكن وجهة نظري.
لقد قلت للتو أن أدوات الدمج يمكن أن تلعب بشكل جيد للغاية مع الانتظار ، وأنه لا يمكن معالجة مشكلة التسلسل إلا من خلال انتظار التعبير الكامل الذي تم إنشاؤه بواسطة المجمعات (إزالة الحاجة إلى await (await fetch("test")).json() أو await { await { fetch("test") }.json() } ).

في مثال الكود الخاص بي ، يتصرف ? كما هو مقصود من خلال قصر الدائرة ، مما يجعل الإغلاق يعيد Err(...) ، وليس الوظيفة بأكملها (يتم التعامل مع هذا الجزء بواسطة await? على السلسلة بأكملها).

لقد قمت بإعادة كتابة مثال الكود الخاص بي بشكل فعال ولكنك قمت بإزالة جزء التسلسل منه (عن طريق القيام بعمليات الربط).
على سبيل المثال ، بالنسبة لجزء تصحيح الأخطاء ، فإن ما يلي له نفس السلوك تمامًا مع dbg الضروري فقط! ولا أكثر (ولا حتى الأقواس الزائدة):

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)
}

لا أعرف كيف أفعل الشيء نفسه بدون أدوات دمج ولا توجد روابط أو أقواس إضافية.
يقوم بعض الأشخاص بالتسلسل لتجنب ملء النطاق بالمتغيرات المؤقتة.
لذلك أردت فقط أن أقول إن أدوات الدمج يمكن أن تكون مفيدة ولا ينبغي تجاهلها عند اتخاذ قرار بشأن بناء جملة معين فيما يتعلق بقدرتها على التسلسل.

وأخيرًا ، بدافع الفضول ، لماذا لا تعمل الشفرة بدون .into_future() ، أليست هذه العقود مستقبلية بالفعل (لست خبيرًا في هذا الأمر ، لكنني أتوقع أن تكون مستقبلية بالفعل)؟

أود تسليط الضوء على مشكلة كبيرة في fut await : إنها تعبث بشكل خطير بكيفية قراءة الناس للكود. هناك قيمة معينة في تعبيرات البرمجة تشبه العبارات المستخدمة في لغة طبيعية ، وهذا أحد أسباب وجود تراكيب مثل for value in collection {..} ، لماذا نكتب في معظم اللغات a + b ("a plus b") بدلاً من a b + ، وكتابة / قراءة "انتظار شيء ما" يعد أمرًا طبيعيًا بالنسبة للغة الإنجليزية (ولغات عمليات SVO الأخرى) أكثر من "شيء ينتظر". فقط تخيل أنه بدلاً من ? كنا قد استخدمنا الكلمة الأساسية postfix try : let val = foo() try; .

لا توجد هذه المشكلة في fut.await!() و fut.await() لأنها تبدو وكأنها مكالمات حظر مألوفة (لكن "الماكرو" يؤكد بالإضافة إلى ذلك على "السحر" المرتبط) ، لذلك سيتم إدراكهم بشكل مختلف عن المسافة المنفصلة كلمة رئيسية. سوف يُنظر إلى Sigil أيضًا بشكل مختلف ، بطريقة أكثر تجريدية ، دون أي موازيات مباشرة مع عبارات اللغة الطبيعية ، ولهذا السبب أعتقد أنه ليس من المهم كيف يقرأ الناس سيجيلات المقترحة.

في الختام: إذا تقرر الالتزام بالكلمة الرئيسية ، فأنا أؤمن بشدة أنه يجب علينا الاختيار من بين متغيرات البادئة فقط.

rpjohnst لست متأكدًا من وجهة نظرك ، إذن. actual_fun(a + b)? و break (a + b)? مهمان بالنسبة لي بسبب الأسبقية المختلفة ، لذلك لا أعرف ما هو await(a + b)? المفترض أن يكون.

لقد كنت أتابع هذه المناقشة من بعيد ولدي بعض الأسئلة والتعليقات.

تعليقي الأول هو أنني أعتقد أن الماكرو postfix .await!() يلبي جميع أهداف Centril الرئيسية باستثناء النقطة الأولى:

"يجب أن تظل await كلمة رئيسية لتمكين تصميم اللغة في المستقبل."

ما الاستخدامات الأخرى للكلمة الرئيسية await التي نراها في المستقبل؟


تحرير : لقد أساءت فهم كيفية عمل وظائف await بالضبط. لقد تخطيت بياناتي غير الصحيحة وقمت بتحديث الأمثلة.

تعليقي الثاني هو أن الكلمة الرئيسية await في Rust تقوم بأشياء مختلفة تمامًا عن await في لغة أخرى وهذا لديه القدرة على إحداث ارتباك وظروف عرقية غير متوقعة.

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;
}

أعتقد أن البادئة async تؤدي وظيفة أكثر وضوحًا تشير إلى أن القيم غير المتزامنة هي أجزاء من آلة الحالة التي تم إنشاؤها بواسطة مترجم:

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;
}

من البديهي أن تتيح لك وظائف async استخدام قيم async . يؤدي هذا أيضًا إلى بناء حدس بوجود آلية غير متزامنة تعمل في الخلفية وكيف يمكن أن تعمل.

يحتوي بناء الجملة هذا على بعض الأسئلة التي لم يتم حلها ومشكلات التركيب الواضحة ، ولكنه يوضح مكان حدوث العمل غير المتزامن ويعمل كمكمل على غرار متزامن لما هو أكثر قابلية للتكوين والتسلسل .await!() .

واجهت صعوبة في ملاحظة postfix await في نهاية بعض التعبيرات في أمثلة الأسلوب الإجرائي السابقة ، لذا إليك ما سيبدو عليه هذا البناء المقترح:

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)
}

(هناك أيضًا حجة يجب إجراؤها من أجل إنشاء ماكرو .dbg!() سهل التركيب والتسلسل ، ولكن هذا لمنتدى مختلف.)

في الثلاثاء ، 29 يناير 2019 الساعة 11:31:32 مساءً -0800 ، كتب Sphericon:

لقد كنت أتابع هذه المناقشة من بعيد ولدي بعض الأسئلة والتعليقات.

تعليقي الأول هو أنني أعتقد أن الماكرو postfix .await!() يلبي جميع أهداف Centril الرئيسية باستثناء النقطة الأولى:

"يجب أن تظل await كلمة رئيسية لتمكين تصميم اللغة في المستقبل."

كواحد من المؤيدين الأساسيين لبناء جملة .await!() ، أنا
أعتقد تمامًا أن await يجب أن يظل كلمة رئيسية. بالنظر إلى أننا سوف
بحاجة إلى .await!() ليتم تضمينها في المترجم على أي حال ، على ما يبدو
تافهة.

تضمين التغريدة

لقد قمت بإعادة كتابة مثال الكود الخاص بي بشكل فعال ولكنك قمت بإزالة جزء التسلسل منه (عن طريق القيام بعمليات الربط).

لا أحصل على نهج "التسلسل من أجل التسلسل". يكون التسلسل جيدًا عندما يكون جيدًا ، على سبيل المثال لا يؤدي إلى إنشاء متغيرات درجة الحرارة غير المجدية. أنت تقول "أزلت التسلسل" ، لكن يمكنك أن ترى أنه لا يقدم أي قيمة هنا.

على سبيل المثال ، بالنسبة لجزء تصحيح الأخطاء ، فإن ما يلي له نفس السلوك تمامًا مع dbg الضروري فقط! ولا أكثر (ولا حتى الأقواس الزائدة)

لا ، لقد فعلت هذا

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)
}

إنه ليس نفس الشيء.

لقد قلت للتو أن أدوات الدمج يمكنها اللعب بشكل جيد للغاية مع الانتظار ، وأنه لا يمكن معالجة مشكلة التسلسل إلا من خلال انتظار التعبير الكامل الذي تم إنشاؤه بواسطة أدوات الدمج (إزالة الحاجة إلى الانتظار (انتظار الجلب ("الاختبار")). json () أو انتظار {wait {fetch ("test")} .json ()}).

إنها مشكلة البادئة await ، فهي غير موجودة لأشكال أخرى منها.

لا أعرف كيف أفعل الشيء نفسه بدون أدوات دمج ولا توجد روابط أو أقواس إضافية.

لماذا لا تنشئ هذه الارتباطات؟ تكون الارتباطات دائمًا أفضل للقارئ ، على سبيل المثال ، وإذا كان لديك مصحح أخطاء يمكنه طباعة قيمة ربط ، لكنه لا يمكنه تقييم تعبير.

أخيرًا ، لم تقم بالفعل بإزالة أي ارتباطات. لقد استخدمت للتو تركيب labmda |user| حيث فعلت let user = ... . لم يوفر لك أي شيء ، ولكن الآن يصعب قراءة هذا الرمز ، ويصعب تصحيحه ، وليس لديه إمكانية الوصول إلى الوالد (على سبيل المثال ، يجب عليك التفاف الأخطاء خارجيًا في سلسلة الطريقة بدلاً من القيام بذلك يطلق عليه نفسه ، المحتمل).


باختصار: التسلسل لا يوفر قيمة بحد ذاته. قد يكون مفيدًا في بعض السيناريوهات ، لكنه ليس واحداً منها. وبما أنني أكتب رمز انتظار / غير متزامن لأكثر من ست سنوات ، أعتقد أنك لا تريد كتابة رمز غير متزامن بالسلاسل على الإطلاق . ليس لأنك لا تستطيع ذلك ، ولكن لأنه غير مريح ، ومن الأفضل دائمًا القراءة والكتابة. كي لا نقول أنك لست بحاجة إلى أدوات دمج على الإطلاق. إذا كان لديك async/await ، فأنت لست بحاجة إلى هذه المئات من الطرق على futures / streams ، فأنت تحتاج فقط إلى اثنين: join و select . كل شيء آخر يمكن أن يتم عن طريق التكرارات / الروابط / ... ، أي أدوات اللغة الشائعة ، التي لا تجعلك تتعلم بنية تحتية أخرى.

وافق مجتمع Sphericon AFAIK على استخدام await إما ككلمة رئيسية أو سيجيل ، لذا فأفكارك حول async تتطلب RFC آخر.

تعليقي الثاني هو أن انتظار الكلمة الرئيسية في Rust يقوم بأشياء مختلفة تمامًا عن الانتظار بلغة أخرى وهذا من شأنه أن يتسبب في حدوث ارتباك وظروف سباق غير متوقعة.

هل يمكنك أن تكون أكثر تفصيلا؟ لم أر أي فرق بين JS / C # wait's ، باستثناء كون العقود الآجلة قائمة على الاستطلاع ، لكن ليس لديها حقًا ما يمكن التعامل معه مع async/await .

فيما يتعلق ببادئة await? المقترحة في عدة أماكن هنا:
ج #
دع foo = تنتظر؟ 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 ، البادئة await? تبدو وكأنها بناء جملة خاص جدًا بالنسبة لي.

) * تم تحريره.

كيف سيبدو هذا مع العقود الآجلة؟ أي ، هل يمكن توسيعه بشكل تعسفي:

let foo = await?? double_trouble();

IOW ، await? يبدو وكأنه بناء جملة خاص للغاية بالنسبة لي.

rolandsteiner حسب "العقود الآجلة" هل تعني impl Future<Output = Result<Result<_, _>, _>> (واحد await + اثنان ? يعني "فك" مستقبل واحد ونتيجتين بالنسبة لي ، وليس انتظار العقود الآجلة المتداخلة ).

await? _ هي حالة خاصة ، لكنها حالة خاصة من المحتمل أن تنطبق على 90٪ + استخدامات await . النقطة الكاملة للعقود الآجلة هي كطريقة لانتظار عمليات _IO_ غير المتزامنة ، يكون الإدخال / الإخراج غير معصوم ، لذا من المحتمل أن يؤدي 90٪ + من async fn إلى إرجاع io::Result<_> (أو نوع خطأ آخر يتضمن متغير IO ). الوظائف التي تُرجع Result<Result<_, _>, _> نادرة جدًا حاليًا ، لذلك لا أتوقع أنها تتطلب صياغة حالة خاصة.

@ Nemo157 أنت محق بالطبع: نتيجة النتائج. تحديث تعليقي.

اليوم نكتب

  1. await!(future?) مقابل future: Result<Future<Output=T>,E>
  2. await!(future)? مقابل future: Future<Output=Result<T,E>>

وإذا كتبنا await future? علينا تحديد أي واحد يعني.

ولكن هل هذه الحالة يمكن أن تتحول دائمًا إلى الحالة 2؟ في الحالة 1 ، ينتج التعبير عن مستقبل أو خطأ. لكن يمكن تأجيل الخطأ ونقله في المستقبل. لذلك يمكننا فقط التعامل مع الحالة 2 وإجراء تحويل تلقائي يحدث هنا.

من وجهة نظر المبرمج ، يضمن Result<Future<Output=T>,E> العودة المبكرة لحالة الخطأ ، لكن باستثناء أن كلاهما لهما نفس الحرف. أستطيع أن أتخيل أن المترجم يمكنه العمل وتجنب المكالمة الإضافية poll إذا كانت حالة الخطأ غير صالحة.

لذا فإن الاقتراح هو:

await exp? يمكن تفسيره على أنه await (exp?) إذا كان exp هو Result<Future<Output=T>,E> ، ويتم تفسيره على أنه (await exp)? إذا كان exp هو Future<Output=Result<T,E>> . في كلتا الحالتين ، سيعود في وقت مبكر بالخطأ ، ويحل إلى النتيجة الحقيقية إذا كان يعمل بشكل جيد.

بالنسبة للحالات الأكثر تعقيدًا ، يمكننا تطبيق شيء مثل dereference مستقبل الطريقة التلقائية:

> عند التداخل بين await exp???? نتحقق أولاً من exp وإذا كان Result ، try ، نستمر عندما تكون النتيجة Result حتى نفاد ? أو لديك شيء ليس Result . ثم يجب أن يكون المستقبل وأن نطبق عليه await ونطبق الباقي ? s.

كنت مؤيدًا للكلمات الرئيسية postfix / sigil وما زلت. ومع ذلك ، أريد فقط أن أوضح أن أسبقية البادئة قد لا تكون مشكلة كبيرة في الممارسة ولديها حلول بديلة.

أعلم أن أعضاء فريق Rust لا يحبون الأشياء الضمنية ، ولكن في مثل هذه الحالة ، يكون هناك فرق ضئيل للغاية بين الأدوات المنطقية المحتملة ولدينا طريقة جيدة لضمان قيامنا بالشيء الصحيح.

await? حالة خاصة ، لكنها حالة خاصة من المحتمل أن تنطبق على 90٪ + استخدامات await . النقطة الكاملة للعقود الآجلة هي كطريقة للانتظار في عمليات الإدخال / الإخراج غير المتزامنة ، الإدخال / الإخراج غير معصوم ، لذا من المحتمل أن يؤدي 90٪ + من async fn إلى إرجاع io::Result<_> (أو نوع خطأ آخر يتضمن متغير IO ). الوظائف التي تُرجع Result<Result<_, _>, _> نادرة جدًا حاليًا ، لذلك لا أتوقع أنها تتطلب صياغة حالة خاصة.

من السيء تكوين الحالات الخاصة أو التوسع فيها أو التعلم ، وفي النهاية تتحول إلى متاع. ليس من التنازل الجيد إجراء استثناءات لقواعد اللغة لحالة استخدام نظرية واحدة.

هل سيكون من الممكن تطبيق Future مقابل Result<T, E> where T: Future ؟ بهذه الطريقة يمكنك فقط await result_of_future دون الحاجة إلى فكها بـ ? . وهذا بالطبع سيعيد نتيجة ، لذا يمكنك تسميتها await result_of_future ، وهو ما يعني (await result_of_future)? . بهذه الطريقة لن نحتاج إلى بناء جملة await? وستكون صيغة البادئة أكثر اتساقًا. اسمحوا لي أن أعرف إذا كان هناك أي خطأ في هذا.

تتضمن الوسائط الإضافية لـ await مع المحددات الإلزامية (شخصيًا لست متأكدًا من بناء الجملة الذي أفضله بشكل عام):

  • لا يوجد غلاف خاص للعامل ? ، لا await? أو await??
  • يتوافق مع عوامل التحكم في التدفق الحالية مثل loop و while و for ، والتي تتطلب أيضًا محددات إلزامية
  • يشعر أكثر في المنزل لهياكل الصدأ الموجودة المماثلة
  • يساعد التخلص من الغلاف الخاص في تجنب المشاكل عند كتابة وحدات الماكرو
  • لا تستخدم sigils أو postfix ، وتجنب الإنفاق من الميزانية الغريبة

مثال:

let p = if y > 0 { op1() } else { op2() };
let p = await { p }?;

ومع ذلك ، بعد التلاعب بهذا في المحرر ، يبدو الأمر مرهقًا. أعتقد أنني أفضل الحصول على await و await? بدون محددات ، مثل break و return .

هل سيكون من الممكن تنفيذ Future for Resultأين تي: المستقبل؟

هل تريد العكس. الأكثر شيوعًا هو المستقبل حيث يكون نوع الإخراج هو النتيجة.

ثم هناك الوسيطة الصريحة التي تمنع إخفاء أو استيعاب ? في انتظار فقط. ماذا لو كنت تريد أن تطابق النتيجة ، إلخ.

إذا كان لديك Result<Future<Result<T, E2>>, E1> ، فإن انتظاره سيعيد Result<Result<T, E2>, E1> .

إذا كان لديك Future<Result<T, E1>> ، فإن انتظاره سيعيد Result<T, E1> .

ليس هناك إخفاء أو استيعاب ? في الانتظار ، ويمكنك فعل كل ما هو مطلوب بالنتيجة بعد ذلك.

يا. يجب أن أكون قد أسأت فهمك بعد ذلك. لا أرى كيف يساعد ذلك على الرغم من أننا لا نزال بحاجة إلى دمج ? مع انتظار 99٪ من الوقت.


يا. من المفترض أن تتضمن بنية await? (await future)? والتي ستكون الحالة الشائعة.

بالضبط. لذلك سنجعل رابط await أكثر إحكامًا في await expr? ، وإذا كان هذا التعبير هو Result<Future<Result<T, E2>>, E1> ، فسيتم تقييمه إلى شيء من النوع Result<T, E2> . هذا يعني أنه لا يوجد غلاف خاص لانتظار أنواع النتائج. إنه يتبع فقط تطبيقات السمات العادية.

ivandardi ماذا عن Result<Future<Item=i32, Error=SomeError>, FutCreationError> ؟

Pzixel لاحظ ، هذا الشكل من المستقبل قد ولت. يوجد الآن نوع مرتبط واحد ، الإخراج (والذي من المحتمل أن يكون نتيجة).


ivandardi حسنًا. أرى الآن. الشيء الوحيد الذي لديك ضدك هو أن الأسبقية لكونك شيئًا غريبًا عليك أن تتعلمه هناك لأنه انحراف ، لكن هذا هو الحال بالنسبة لأي شيء مع await أفترض.

على الرغم من أن النتيجة التي تعود إلى المستقبل نادرة جدًا ، إلا أنني لم أجد حالة واحدة بخلاف شيء ما في tokio core تمت إزالته ، لذا لا أعتقد أننا بحاجة إلى أي إشارات ضمنية / سمة للمساعدة في هذه الحالة.

ivandardi ماذا عن Result<Future<Item=i32, Error=SomeError>, FutCreationError> ؟

حسنًا ، أفترض أن هذا غير ممكن ، مع ملاحظة أن السمة Future لها فقط النوع المرتبط Output .


mehcode حسنًا ، إنه يعالج بعض المخاوف التي أثيرت سابقًا ، على ما أعتقد. كما أنه يساعد في تحديد بناء جملة البادئة ، لأنه سيكون هناك بادئة واحدة فقط في انتظار بناء الجملة بدلاً من اختيارات "الأسبقية الواضحة" و "الأسبقية المفيدة".

حسنًا ، أفترض أن هذا غير ممكن ، لأن سمة المستقبل لها فقط نوع مرتبط بالإخراج.

لما لا؟

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 انظر https://doc.rust-lang.org/std/future/trait.Future.html

أنت تتحدث عن السمة القديمة التي كانت في الصندوق futures .

بصراحة ، لا أعتقد أن وجود كلمة رئيسية في موضع البادئة كان من المفترض أن تكون:

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)
}

لديه أي ميزة قوية على وجود سيجيل في نفس الموقف:

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)
}

أعتقد أنه لا يبدو إلا بشكل غير متسق مع عوامل تشغيل البادئة الأخرى ، ويضيف مسافة بيضاء زائدة عن الحاجة قبل التعبير ، وينقل الكود إلى الجانب الأيمن على مسافة ملحوظة.


يمكننا محاولة استخدام sigil مع بناء جملة النقاط الممتد ( Pre-RFC ) الذي يحل المشكلات المتعلقة بالنطاقات المتداخلة بعمق:

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)]?
        .res.[*json::<UserResponse>()]?
        .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)
}
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)
}

ما يعجبني هنا هو أن @ ينعكس على await والذي يتم وضعه في LHS لإعلان الوظيفة ، بينما ? يعكس Result<User> الذي يتم وضعه على RHS من إعلان الوظيفة. هذا يجعل @ متسقًا للغاية مع ? .


اي افكار في هذا؟

اي افكار في هذا؟

أي مشابك إضافية هي مجرد وسيلة محظورة

mehcode Yep ، لم أكن أدرك أن العقود الآجلة تفتقر الآن إلى Error type. لكن النقطة لا تزال صالحة: يمكنك الحصول على وظيفة ، والتي من المحتمل أن ترجع ميزة ، والتي ، عند اكتمالها ، قد تعرض نتيجة أو خطأ.

اي افكار في هذا؟

نعم! وفقًا لتعليق Centril ، فإن sigils ليست قابلة للإمساك كثيرًا. سأبدأ فقط في التفكير في sigils إذا كان بإمكان المرء إنشاء regex يحدد جميع نقاط الانتظار.

أما بالنسبة لاقتراح بناء الجملة الخاص بالنقاط الموسعة ، فسيتعين عليك شرحه بشكل أعمق بكثير ، مع توفير الدلالات والتشويش لكل استخدام له. اعتبارًا من الوقت الحالي ، لا يمكنني فهم معنى مقتطفات الشفرة التي نشرتها باستخدام بناء الجملة هذا.


لكن النقطة لا تزال صالحة: يمكنك الحصول على وظيفة ، والتي من المحتمل أن ترجع ميزة ، والتي ، عند اكتمالها ، قد تعرض نتيجة أو خطأ.

لذا سيكون لديك Future<Item=Result<T, E>> ، أليس كذلك؟ حسنًا ، في هذه الحالة ، أنت فقط ... تنتظر المستقبل وتتعامل مع النتيجة 😐

كما في ، افترض أن foo من النوع Future<Item=Result<T, E>> . ثم await foo سيكون Result<T, E> ويمكنك استخدام ما يلي للتعامل مع الأخطاء:

await foo?;
await foo.unwrap();
match await foo { ... }
await foo.and_then(|x| x.bar())

تضمين التغريدة

لذلك سيكون لديك مستقبل> صحيح؟ حسنًا ، في هذه الحالة ، أنت فقط ... تنتظر المستقبل وتتعامل مع النتيجة 😐

لا ، أعني Result<Future<Item=Result<T, E>>, OtherE>

مع متغير postfix ما عليك سوى القيام به

let bar = foo()? await?.bar();

melodicstream لقد قمت بتحديث

يا عزيزتي ... أنا فقط أعدت قراءة كل التعليقات للمرة الثالثة ...

الشعور الوحيد المتسق هو عدم إعجابي بتدوين postfix .await في جميع أشكاله لأنني أتوقع بجدية أن يكون await جزءًا من Future مع هذا التركيب. قد تعمل "قوة النقطة" مع IDE ولكنها لا تعمل معي على الإطلاق. يمكنني بالتأكيد التكيف معها إذا كانت هي البنية المستقرة ولكني أشك في أنها ستشعر حقًا بأنها "مناسبة" بالنسبة لي.

سأختار البادئة الأساسية الفائقة await بدون أي أقواس إلزامية ، ويرجع ذلك أساسًا إلى مبدأ KISS ولأن القيام بذلك على غرار معظم اللغات الأخرى يستحق الكثير.

إزالة السكر من await? future إلى (await future)? سيكون أمرًا جيدًا ومقدرًا ولكن أي شيء يتجاوز ذلك يبدو أكثر فأكثر كحل بدون مشكلة بالنسبة لي. حسنت عملية إعادة الربط البسيطة let من قابلية قراءة الكود في معظم الأمثلة ، وأنا شخصياً من المحتمل أن أسلك هذا الطريق أثناء كتابة الكود حتى لو كان التسلسل السهل خيارًا.

مع ما يقال ، أنا سعيد جدًا لأنني لست في وضع يسمح لي باتخاذ قرار بشأن ذلك.

سأترك الآن هذه المشكلة بمفردها بدلاً من إضافة المزيد من الضوضاء وانتظر (يقصد التورية) الحكم النهائي.

... على الأقل سأحاول بصدق القيام بذلك ...

Pzixel بافتراض أن foo من النوع Result<Future<Item=Result<T, E1>>, E2> ، فإن await foo سيكون من النوع Result<Result<T, E1>, E2> وبعد ذلك يمكنك التعامل مع هذه النتيجة وفقًا لذلك.

await foo?;
await foo.and_then(|x| x.and_then(|y| y.bar()));
await foo.unwrap().unwrap();

melodicstream لا ، لن. لا يمكنك انتظار Result, يمكنك انتظار Future. So you have to do foo ()؟ to unwrap Future from النتيجة , then do انتظار to get a result, then again ؟ `لإلغاء نتيجة المستقبل.

في طريقة postfix سيكون foo? await? بالبادئة ... لست متأكدًا.

لذا فإن الأمثلة الخاصة بك لا تعمل ، خاصةً آخرها ، لأنها يجب أن تكون كذلك

(await foo.unwrap()).unwrap()

ومع ذلك ، قد يكون huxi محقًا في أننا async/await .

Pzixel لهذا السبب Future على جميع الأنواع Result<Future<Item=T>, E> . القيام بذلك سيسمح بما أقوله.

على الرغم من أنني موافق على await foo?? مقابل Result<Future<Output=Result<T, E1>>, E2> ، إلا أنني لست سعيدًا بـ await foo.unwrap().unwrap() . في نموذج عقلي الأول يجب أن يكون هذا

(await foo.unwrap()).unwrap()

وإلا سأكون محيرا حقا. السبب هو أن ? عامل تشغيل عام ، و unwrap طريقة. يمكن للمترجم أن يفعل شيئًا خاصًا للمشغلين مثل . ، لكن إذا كانت طريقة عادية ، سأفترض أنها ترتبط دائمًا بأقرب تعبير في جانبها الأيسر فقط.

صيغة postfix foo.unwrap() await.unwrap() مناسبة لي أيضًا ، لأنني أعرف أن await مجرد كلمة مفتاحية وليست كائنًا ، لذا يجب أن تكون جزءًا من التعبير قبل unwrap() .

ماكرو نمط postfix يحل الكثير من هذه المشاكل بشكل جيد ، ولكن فقط مسألة هل نريد الاحتفاظ بالألفة مع اللغات الحالية وإبقائها مسبوقة. كنت سأصوت لأسلوب postfix.

هل أنا محق في أن الكود التالي ليس متساويًا:

fn foo(n: u32) -> impl Future<Item = u32> {
   if n == 0 {
      panic!("Can't be zero");
   } else {
      do_async_call().map(|_| 10)
   }
}

و

async fn foo(n: u32) -> u32 {
   if n == 0 {
      panic!("Can't be zero");
   } else {
      await!(do_async_call());
      10
   }
}

الأول يملأ الذعر عند محاولة إنشاء مستقبل ، بينما يصاب الثاني بالذعر في الاستطلاع الأول. إذا كان الأمر كذلك ، فهل يجب أن نفعل شيئًا به؟ يمكن حلها مثل

fn foo(n: u32) -> impl Future<Item = u32> {
   if n == 0 {
      panic!("Can't be zero");
   } else {
      async {
         await!(do_async_call());
         10 
      }
   }
}

لكنها أقل ملاءمة بكثير.

Pzixel : هذا قرار تم اتخاذه. async fn كسول تمامًا ولا يعمل على الإطلاق حتى يتم الاقتراع ، وبالتالي يلتقط جميع فترات الإدخال طوال العمر المستقبلي بأكمله. الحل هو دالة التفاف تُرجع impl Future كونها صريحة ، وتستخدم كتلة async (أو fn) للحساب المؤجل. هذا _ مطلوب _ لتكديس أدوات دمج Future بدون تخصيص بين كل واحدة ، حيث لا يمكن نقلها بعد الاستطلاع الأول.

هذا قرار تم تنفيذه ولماذا لا تعمل أنظمة الانتظار الضمني (جيدًا) مع Rust.

C # هنا ، وسأدافع عن أسلوب البادئة ، لذا استعد للتصويت ضده.

صيغة Postfix مع نقطة ( read().await أو read().await() ) مضللة للغاية وتقترح وصول حقل أو استدعاء طريقة ، و await ليس أيًا من هذه الأشياء ؛ إنها ميزة لغوية. أنا لست Rustacean مخضرمًا بأي وسيلة ، لكنني لست على علم بأي حالات أخرى .something التي هي بشكل فعال كلمة رئيسية سيتم إعادة كتابتها بواسطة المترجم. أقرب ما يمكنني التفكير فيه هو اللاحقة ? .

بعد أن كان غير متزامن / ينتظر في C # لبضع سنوات حتى الآن ، فإن الصيغة القياسية للبادئة مع الفضاء ( await foo() ) لم تسبب لي أي مشاكل في كل ذلك الوقت. في الحالات التي يُطلب فيها await متداخلة ، فليس من الصعب استخدام الأقواس ، وهو بناء جملة قياسي لجميع أسبقية المشغل الصريحة وبالتالي يسهل التملص منه ، على سبيل المثال

ج #
var list = (await 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()

هذا يبدو غريبا.

أعتقد أن الاتساق مع اللغات الأخرى هو أيضًا شيء يجب إعطاؤه وزنًا. في الوقت الحالي ، الكلمة الأساسية await قبل التعبير غير المتزامن هي بناء جملة C # و VB.NET و JavaScript و Python ، وهي البنية المقترحة لـ C ++ أيضًا. هذه خمس من أفضل سبع لغات في العالم ؛ C و Java ليس لديهما متزامن / ينتظران حتى الآن ، لكنني سأفاجأ إذا ذهبوا بطريقة أخرى.

ما لم تكن هناك أسباب وجيهة حقًا للذهاب بطريقة مختلفة ، أو بعبارة أخرى ، إذا كانت كل من البادئة و postfix تزنان نفس الشيء من حيث المزايا والتنازلات ، فأنا أقترح أن هناك الكثير مما يمكن قوله عن غير الرسمي المعيار ".

markrendle أنا C # dev أيضًا ، أكتب async/await منذ 2013. وأنا أحب البادئة أيضًا. لكن الصدأ يختلف كثيرًا.

ربما تعلم أن (await Foo()).ToList() الخاص بك هو حالة نادرة. في معظم الحالات ، تكتب فقط await Foo() ، وإذا كنت بحاجة إلى نوع آخر ، فربما تريد إصلاح التوقيع Foo .

لكن الصدأ شيء آخر. لدينا ? هنا وعلينا نشر الأخطاء. في C # async / wait يلف الاستثناءات تلقائيًا ، ويرميها عبر نقاط الانتظار وما إلى ذلك. ليس لديك في الصدأ. ضع في اعتبارك كل مرة تكتب فيها await في C # عليك الكتابة

var response = (await client.GetAsync("www.google.com")).HandleException();
var json =  (await response.ReadAsStreamAsync()).HandleException();
var somethingElse = (await DoMoreAsyncStuff(json)).HandleException();
...

إنه أمر ممل للغاية أن يكون لديك كل هذه الأقواس.

OTOH مع postfix لديك

var response = client.GetAsync("www.google.com") await.HandleException();
var json =  response.ReadAsStreamAsync() await.HandleException();
var somethingElse = DoMoreAsyncStuff(json) await.HandleException();
...

أو من حيث الصدأ

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?;
...

و imaine سيكون لدينا بعض المحولات الأخرى ، بالإضافة إلى ? و await ، دعنا نسميها @ . إذا اخترعنا تركيبًا إضافيًا لـ await? ، فسنحتاج إلى await@ ، @? ، await@? ، ... لجعلها متسقة.


أنا أحب البادئة await أيضًا ، لكنني لست متأكدًا تمامًا مما إذا كان Rust مناسبًا لها. قد يكون إضافة قواعد إضافية إلى اللغة أمرًا جيدًا ، لكنه عبء ربما لا يستحق سهولة القراءة.

أعد قراءة جميع التعليقات بما في ذلك تتبع موضوع المشكلة (قضى ما يقرب من 3 ساعات في هذا). وملخصي هو: يبدو أن postfix @ هو الحل الأفضل ، ولسوء الحظ فهو أكثر الحلول التي لا تحظى بالتقدير الكافي.

هناك الكثير من الآراء (المتحيزة) حول هذا الموضوع ، وفيما يلي تعليقاتي لهم:


async-await سيكون غير مألوف لـ postfix @

في الواقع ، سيكون نصفها غير مألوف فقط لأنه سيتم توفير نفس بناء الجملة async fn وستظل الوظيفة نفسها كما في await متاحة.

يحتوي Rust على الكثير من السيجات ويجب ألا نقدم سيجيلًا آخر

لكن sigils جيدة حتى تصبح قابلة للقراءة ومتسقة مع بناء الجملة الأخرى. وإدخال علامة سيجيل جديدة لا يعني بشكل صارم أنها ستجعل الأمور أسوأ بطريقة ما. في منظور الاتساق ، تعد postfix @ طريقة أفضل لبناء جملة من أي بادئة / postfix await .

المقتطفات مثل ?!@# تذكرني بـ Perl

في الواقع ، نادرًا ما نرى أي تركيبة أخرى غير @? والتي تبدو IMO بسيطة جدًا ومريحة. أنواع الإرجاع مثل impl Try<impl Future<T>> ، impl Future<impl Try<impl Try<T>>> ، وحتى impl Future<T> ، تحدث على الأكثر في 10٪ من الاستخدامات كما يمكن أن نرى في أمثلة العالم الحقيقي ITT. علاوة على ذلك ، فإن تكتل sigil في 90٪ من هذه الـ 10٪ يشير إلى رائحة رمز عندما يتعين عليك إما إصلاح API الخاص بك أو تقديم ارتباط مؤقت بدلاً من ذلك لتوضيحها.

من الصعب جدًا ملاحظة Postfix @ !

وهذه في الواقع ميزة كبيرة في سياق async-await . يقدم بناء الجملة هذا القدرة على التعبير عن التعليمات البرمجية غير المتزامنة كما لو كانت متزامنة ، لذا فإن أكثر تدخلاً await يتداخل مع غرضه الأصلي فقط. توضح الأمثلة أعلاه أن حدوث نقاط العائد قد يكون كثيفًا بدرجة كافية لتضخيم الشفرة عندما تكون واضحة جدًا. بالطبع يجب أن يكون لدينا في الأفق ، ولكن @ sigil سيكون جيدًا تمامًا لذلك بالإضافة إلى await بدون تضخيم الكود.

انتظار المستقبل عملية مكلفة ويجب أن نقول ذلك صراحة

إنه أمر منطقي حقًا ، لكنني لا أعتقد أنه يجب علينا قول ذلك كثيرًا وبصوت عالٍ. أعتقد أنه يمكن توفير تشبيه مع بناء جملة الطفرات: mut مطلوب صراحة مرة واحدة فقط ، وبعد ذلك يمكننا استخدام الربط القابل للتغيير ضمنيًا. ويمكن تقديم القياس آخر مع تركيب غير آمنة: صريح unsafe غير المطلوبة مرة واحدة فقط والمزيد من يمكننا dereference مؤشرات الخام، واستدعاء وظائف غير آمنة، وغيرها، وبعد ويمكن تقديم القياس آخر مع مشغل فقاعة: صريح impl Try return أو الكتلة القادمة try مطلوبة مرة واحدة فقط ، وبعد ذلك يمكننا استخدام عامل التشغيل ? بثقة. لذا ، وبنفس الطريقة فإن التعليقات التوضيحية الصريحة async ربما تكون عمليات دائمة مرة واحدة فقط ، وعلاوة على ذلك يمكننا تطبيق عامل التشغيل @ الذي سيفعل ذلك بالفعل.

إنه أمر غريب لأنني قرأته بشكل مختلف عن await ، ولا يمكنني كتابته بسهولة

إذا كنت تقرأ & كـ "ref" و ! كـ "not" ، فلا أعتقد أن الطريقة التي تقرأ بها @ حاليًا هي حجة قوية لعدم استخدام هذا رمز. لا يهم أيضًا مدى سرعة كتابتك لأن قراءة الكود دائمًا ما تكون ذات أولوية أكثر أهمية. في كلتا الحالتين ، يعتبر اعتماد @ أمرًا بسيطًا للغاية.

إنه غامض لأن @ مستخدم بالفعل في مطابقة النمط

لا أعتقد أن هذه مشكلة لأن ! بالفعل يستخدم في وحدات الماكرو، & بالفعل يستخدم في الاقتراض و && المشغل، | يستخدم في الإغلاق، في أنماط، و || المشغل، + / - يمكن تطبيقها في موقف بادئة كذلك، و . يمكن استخدامها في بعد سياقات مختلفة أكثر. لا يوجد ما يثير الدهشة ولا حرج في إعادة استخدام نفس الرموز في تراكيب بناء مختلفة.

سيكون من الصعب جريب

إذا كنت تريد التحقق مما إذا كانت بعض المصادر تحتوي على أنماط غير متزامنة ، فسيكون rg async حلاً أبسط ومباشر. إذا كنت تريد حقًا العثور على جميع نقاط العائد بهذه الطريقة ، فستتمكن من كتابة شيء مثل rg "@\s*[;.?|%^&*-+)}\]]" وهو أمر غير مباشر ، ولكنه أيضًا ليس شيئًا مذهلًا. بالنظر إلى عدد المرات المطلوبة ، فإن IMO هذا التعبير العادي جيد تمامًا.

@ ليس صديقًا للمبتدئين ، ومن الصعب استخدام Google

أعتقد أنه من الأسهل فهمه للمبتدئين لأنه يعمل باستمرار مع عامل تشغيل ? . على العكس من ذلك ، فإن البادئة / postfix await لن تكون متسقة مع بنية Rust الأخرى التي تقدم أيضًا الصداع مع الترابط / التنسيق / المعنى. لا أعتقد أيضًا أن await سيضيف بعض قيمة التوثيق الذاتي لأن كلمة رئيسية واحدة لا يمكنها وصف المفهوم الأساسي بالكامل والمبتدئين سوف يتعلمونه من الكتاب على أي حال. ولم تكن Rust أبدًا لغة تمنحك تلميحات نصية بسيطة لكل من التركيبات النحوية الممكنة. إذا نسي شخص ما ما يعنيه رمز @ (أشك حقًا في أنه سيكون مشكلة لأن هناك أيضًا العديد من الجمعيات التي يجب أن تتذكرها بشكل صحيح) - فيجب أن يكون البحث على Google ناجحًا أيضًا لأنه حاليًا rust @ symbol إرجاع كافة المعلومات ذات الصلة حول @ في مطابقة النمط.

لقد حجزنا بالفعل كلمة رئيسية await ، لذا يجب علينا استخدامها على أي حال

لماذا ا؟ لا بأس في وجود كلمة رئيسية محجوزة غير مستخدمة عندما يتضح أن تنفيذ الميزة النهائية أفضل بدونها. أيضًا ، لم يتم إعطاء أي وعد بأنه سيتم تقديم الكلمة الرئيسية await بالضبط. وهذا المعرف ليس شائعًا جدًا في كود العالم الحقيقي ، لذلك لن نفقد شيئًا عندما يظل محجوزًا حتى الإصدار التالي.

? كان خطأ و @ سيكون الخطأ الثاني

من المحتمل أنك تفكر بطريقة معجمية أكثر ، ولهذا تفضل العمل بالكلمات بدلاً من الرموز. وبسبب ذلك لا ترى أي قيمة في بناء الجملة الرقيق. لكن ضع في اعتبارك أن المزيد من الناس يفكرون بشكل أكثر بصريًا ، وبالنسبة لهم ، يصعب استخدام الكلمات المتطفلة بدلاً من ذلك. من المحتمل أن يكون هناك حل وسط جيد هنا هو توفير ماكرو await!() جنبًا إلى جنب ، مثل try!() تم تقديمه جنبًا إلى جنب مع ? ، لذلك ستكون قادرًا على استخدامه بدلاً من @ إذا كنت غير مرتاح حقًا لـ @ .

^ ^ ^ ^ ^ ^ ^ هل اعدت قراءة تعليقي ايضا؟

كان لدي إعلان كبير يقرأ تعليق @ I60R الذي جعلني أؤيد سيجيل postfix ، ولم أحب استخدام علامة sigil حتى الآن (بدلاً من ذلك أفضل شكلًا من أشكال انتظار postfix).

عملية الانتظار الخاصة بنا ليست await . على الأقل ليس بالطريقة التي يتم بها استخدام await في C # و JavaScript ، وهما أكبر مصدرين لألفة async / await .

في هذه اللغات ، الدلالة لبدء Task (C #) / Promise (JS) هي أن المهمة توضع على الفور في قائمة انتظار المهام ليتم تنفيذها. في C # ، هذا سياق متعدد الخيوط ، في JS هو سياق مفرد.

ولكن في أي من هاتين اللغتين ، يعني await foo دلالة "إيقاف هذه المهمة وانتظار اكتمال المهمة foo ، وفي غضون ذلك ، يمكن استخدام الموارد الحسابية التي تستخدمها هذه المهمة بواسطة أشخاص آخرين مهام".

هذا هو سبب حصولك على التوازي مع await s منفصلة بعد إنشاء عدة Task / Promise s (حتى على منفذي الخيط الفردي): دلالات await aren لا "قم بتشغيل هذه المهمة" بل "قم بتشغيل بعض المهام" حتى تنتهي من الانتظار ويمكننا استئنافها.

هذا منفصل تمامًا عن الكسول - أو الشغوف - حتى الانتظار أولاً لبدء المهام عند إنشائها ، على الرغم من ارتباطها الشديد. أنا بصدد ترتيب باقي المهمة في قائمة الانتظار بعد الانتهاء من العمل المتزامن.

لدينا بالفعل ارتباك على طول هذا الخط في المستخدمين من التكرارات البطيئة والعقود الآجلة الحالية. أفترض أن بناء الجملة await لا يقدم لنا أي خدمات هنا.

وبالتالي فإن await!(foo) ليس await foo بالطريقة التقليدية C # أو JS. إذن ما هي اللغة الأكثر ارتباطًا بدلالاتنا؟ أنا الآن أعتقد اعتقادًا راسخًا أنه ( matklad صححني إذا كانت لدي تفاصيل خاطئة) Kotlin suspend fun . أو كما تمت الإشارة إليه في المناقشات المجاورة لـ Rust ، "غير متزامن صريح ، انتظار ضمني".

مراجعة موجزة: في Kotlin ، يمكنك فقط استدعاء suspend fun من داخل سياق suspend . يؤدي هذا على الفور إلى تشغيل الوظيفة التي تم استدعاؤها حتى الإكمال ، مع تعليق السياق المكدس مؤقتًا كما هو مطلوب بواسطة الوظيفة الفرعية. إذا كنت ترغب في تشغيل الأطفال suspend fun في موازاة ذلك، تقوم بإنشاء ما يعادل الإغلاق المتزامن، والجمع بين تلك التي لديها suspend fun combinator لتشغيل متعددة suspend الإغلاق في نفس الوقت. (إنه أكثر تعقيدًا من ذلك بقليل.) إذا كنت تريد تشغيل طفل suspend fun في الخلفية ، يمكنك إنشاء نوع مهمة يضع هذه المهمة على المنفذ ويكشف .await() suspend fun طريقة

هذه البنية ، على الرغم من وصفها بأفعال مختلفة عن Rust's Future ، تبدو مألوفة للغاية.

لقد أوضحت سابقًا سبب عدم نجاح الانتظار الضمني مع Rust ، والوقوف بجانب هذا الموقف. نحتاج إلى async fn() -> T و fn() -> Future<T> ليكونا متكافئين في الاستخدام بحيث يكون نمط "القيام ببعض الأعمال الأولية" ممكنًا (لجعل الحياة تعمل في حالات صعبة) مع العقود الآجلة البطيئة. نحن بحاجة إلى عقود مستقبلية كسولة حتى نتمكن من تكديس العقود الآجلة دون طبقة من المراوغة بين كل منها للتثبيت.

لكنني الآن مقتنع بأن الصيغة "الصريحة ولكن الهادئة" لـ foo@ (أو بعض سيجيل أخرى) منطقية. بقدر ما أكره أن أقول الكلمة هنا ، فإن @ في هذه الحالة هي عملية أحادية في async مثل ? هي العملية أحادية في try .

هناك رأي مفاده أن رش ? في الكود الخاص بك هو فقط لجعل المترجم يتوقف عن الشكوى عندما تعيد Result . وبمعنى ما ، أنت تفعل هذا. ولكن من أجل الحفاظ على المثل الأعلى للوضوح المحلي للكود. أحتاج إلى معرفة ما إذا كانت هذه العملية قد تم تفريغها من خلال وحدة أحادية أم يتم إجراؤها محليًا حرفيًا.

(إذا أخطأت في التفاصيل الأحادية ، فأنا متأكد من أن شخصًا ما سوف يصححني ، لكنني أعتقد أن وجهة نظري لا تزال قائمة.)

إذا كان @ يتصرف بنفس الطريقة ، أفترض أن هذا أمر جيد. لا يتعلق الأمر بتدوير عدد من المهام ثم await لإنهائها ، بل يتعلق ببناء آلة دولة يحتاج شخص آخر إلى ضخها حتى اكتمالها ، سواء كان ذلك عن طريق بنائها في آلة الحالة الخاصة بهم أو وضعه في سياق التنفيذ.

TL ؛ DR: إذا أخذت شيئًا واحدًا من منشور المدونة الصغير هذا ، فليكن هذا: Rust's await!() ليس await كما هو الحال في اللغات الأخرى. كانت النتائج متشابهة ولكن الدلالات الخاصة بكيفية التعامل مع المهام تختلف اختلافًا كبيرًا. من هذا نستفيد من عدم استخدام الصيغة الشائعة ، لأننا نفتقر إلى السلوك الشائع.

(تم ذكره في مناقشة سابقة لغة تغيرت من كسول إلى عقود آجلة شغوفة. أعتقد أن هذا بسبب await foo يشير إلى أن foo يحدث بالفعل ، ونحن ننتظر فقط إنتاج نتيجة.)

تحرير: إذا كنت تريد مناقشة هذا بطريقة أكثر تزامنًا ، فقم بـ ping me @ CAD على تطوير الصدأ Discord (الرابط 24 ساعة) أو الداخلي أو المستخدمين (@ CAD97) أود أن أضع موقفي في مواجهة بعض التدقيق المباشر.

تحرير 2: آسف GitHub @ cad لأمر ping العرضي ؛ حاولت الهروب من علامة @ ولم أقصد جذبك إلى هذا.

@ I60R

المقتطفات مثل ?!@# تذكرني بـ Perl

في الواقع ، نادرًا ما نرى أي تركيبة أخرى غير @? والتي تبدو IMO واضحة ومباشرة

لا يمكنك معرفة ذلك حتى يظهر. إذا كان لدينا محول كود آخر - yield sigil ، على سبيل المثال - فسيكون نص برل حقيقي.

من الصعب ملاحظة Postfix @ !

وهذه في الواقع ميزة كبيرة في سياق async-await .

لا يمكن أن تكون الصعوبة في ملاحظة نقاط await ميزة.

انتظار المستقبل عملية مكلفة ويجب أن نقول ذلك صراحة

إنه أمر منطقي حقًا ، لكنني لا أعتقد أنه يجب علينا قول ذلك كثيرًا وبصوت عالٍ.

أعد قراءة الروابط الخاصة بي حول تجربة C # ، من فضلك. انها في الواقع هذا مهم أن أقول ذلك بصوت عال وهذا هذا في كثير من الأحيان.


النقاط الأخرى صحيحة تمامًا ، لكن تلك التي أجبت عليها هي ثغرات غير قابلة للإصلاح.

@ CAD97 بصفتك مطور C # يمكنه أن يقول كيف يختلف async في Rust .. حسنًا ، الأمر ليس مختلفًا كما تصف. IMHO الخاصة بك تستند إلى فرضية خاطئة

وبالتالي فإن await!(foo) ليس await foo بالطريقة "التقليدية" C # أو JS.

إنه نفس الشيء تمامًا في عقل C # dev. بالطبع ، يجب أن تتذكر أنه لن يتم تنفيذ العقود الآجلة حتى يتم الاقتراع ، ولكن في معظم الحالات لا يهم. في معظم الحالات ، يعني await "أريد الحصول على قيمة غير مغلفة من المستقبل F إلى المتغير x ". إن عدم تشغيل المستقبل حتى الاستطلاع هو في الواقع ارتياح بعد عالم C # لذا سيكون الناس سعداء لحسن الحظ. إلى جانب ذلك ، لدى C # مفاهيم مماثلة مع التكرارات / Genatos ، وهي كسولة أيضًا ، لذا فإن C # الحشود مألوفة تمامًا مع الأمر برمته.

باختصار ، فإن الحصول على await يعمل بشكل مختلف قليلاً أسهل من عمل sigil تمامًا مثل await بلغتهم٪ younameit٪ ، لكن ليس تمامًا. لن يتم الخلط بين الناس بسبب الاختلاف لأنه ليس صفقة بقدر ما قد يظنونه. المواقف التي تكون فيها أمور التنفيذ الشغوف / البطيء نادرة حقًا ، وبالتالي لا ينبغي أن تكون مصدر قلق التصميم الأساسي.

Pzixel إنه نفس الشيء تمامًا في عقل C # dev. بالطبع ، يجب أن تتذكر أنه لن يتم تنفيذ العقود الآجلة حتى يتم الاقتراع ، ولكن في معظم الحالات لا يهم.

المواقف التي تكون فيها مسائل التنفيذ المتعطش / البطيء نادرة جدًا وبالتالي لا ينبغي أن تكون مصدر قلق التصميم الأساسي.

للأسف ، هذا ليس صحيحًا: نمط "إنتاج بعض الوعود ثم لاحقًا await هم" شائع جدًا ومرغوب فيه ، لكنه لا يعمل مع Rust .

هذا فرق كبير للغاية ، حتى أن بعض أعضاء Rust فاجأوا!

عدم التزامن / انتظار في Rust هو بالفعل أقرب إلى نظام أحادي ، فهو لا يشترك في أي شيء مشترك مع JavaScript / C #:

  • التنفيذ مختلف تمامًا (بناء آلة الدولة ثم تنفيذها في مهمة ، والتي تتوافق مع monads).

  • واجهة برمجة التطبيقات مختلفة تمامًا (سحب مقابل دفع).

  • السلوك الافتراضي لكونك كسولًا مختلف تمامًا (وهو ما يتوافق مع monads).

  • يمكن أن يكون هناك مستهلك واحد فقط ، وليس كثير (وهو ما يتوافق مع monads).

  • يختلف فصل الأخطاء واستخدام ? للتعامل معها تمامًا (وهو ما يتوافق مع محولات monad).

  • نموذج الذاكرة مختلف تمامًا ، والذي له تأثير كبير على كل من تنفيذ Rust Futures ، وكذلك كيفية استخدام المستخدمين لـ Futures فعليًا.

الشيء الوحيد المشترك هو أن جميع الأنظمة مصممة لجعل التعليمات البرمجية غير المتزامنة أسهل. هذا هو. هذا ليس كثيرًا من القواسم المشتركة على الإطلاق.

ذكر الكثير من الناس C #. نعلم.

نحن نعلم ما فعله C # ، ونعرف ما فعله JavaScript. نحن نعلم لماذا اتخذت هذه اللغات تلك القرارات.

لقد تحدث إلينا أعضاء فريق C # الرسميين وشرحوا بالتفصيل لماذا اتخذوا هذه القرارات بدون التزامن / الانتظار.

لا أحد يتجاهل تلك اللغات ، أو نقاط البيانات تلك ، أو حالات الاستخدام تلك. يتم أخذها في الاعتبار.

لكن Rust يختلف حقًا عن C # أو JavaScript (أو أي لغة أخرى). لذلك لا يمكننا نسخ ما تفعله اللغات الأخرى بشكل أعمى.

ولكن في أي من هاتين اللغتين ، تعني كلمة "كلمة انتظار" foo معنيًا "توقف هذه المهمة وانتظر إكمال المهمة foo

إنه بالضبط نفس الشيء في Rust. دلالات الوظائف غير المتزامنة هي نفسها. إذا قمت باستدعاء غير متزامن fn (مما يؤدي إلى إنشاء المستقبل) ، فلن يبدأ التنفيذ بعد. هذا يختلف بالفعل عن JS و C # world.

انتظاره سيقود المستقبل إلى الاكتمال ، وهو نفس الشيء في أي مكان آخر.

للأسف ، هذا ليس صحيحًا: نمط "إنتاج بعض الوعود ثم لاحقًا await هم" شائع جدًا ومرغوب فيه ، لكنه لا يعمل مع Rust .

هل يمكن أن تكون في مزيد من التفاصيل؟ لقد قرأت المنشور ولكن لم أجد ما هو الخطأ فيه

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

بالطبع ، لن يتم إطلاقهم حتى السطر 2 ، لكن في معظم الحالات يكون هذا سلوكًا مرغوبًا فيه ، وما زلت مقتنعًا بأن هذا فرق كبير لا يسمح باستخدام الكلمة الرئيسية await بعد الآن. يشير اسم الموضوع نفسه إلى أن الكلمة await تبدو منطقية هنا.

لا أحد يتجاهل تلك اللغات ، أو نقاط البيانات تلك ، أو حالات الاستخدام تلك. يتم أخذها في الاعتبار.

لن أكون مزعجًا بتكرار نفس الحجج مرارًا وتكرارًا. أعتقد أنني قدمت ما كان من المفترض أن أفعله ، لذا لن أفعله بعد الآن.


ملاحظة

  • واجهة برمجة التطبيقات مختلفة تمامًا (سحب مقابل دفع).

يحدث فرقًا عند تنفيذ العقود الآجلة الخاصة بك فقط. لكن في 99 (100؟) من الحالات ، تستخدم فقط أدوات دمج للعقود الآجلة التي تخفي هذا الاختلاف.

  • نموذج الذاكرة مختلف تمامًا ، والذي له تأثير كبير على كل من تنفيذ Rust Futures ، وكذلك كيفية استخدام المستخدمين لـ Futures فعليًا.

هل يمكن أن تكون في مزيد من التفاصيل؟ لقد لعبت بالفعل باستخدام رمز غير متزامن بالفعل عند كتابة خادم ويب بسيط على Hyper ولم ألاحظ أي فرق. ضع async هنا ، await هناك ، انتهى .

الحجة القائلة بأن rust's async / wait لها تطبيق مختلف عن اللغات الأخرى ومن ثم يجب علينا القيام بشيء مختلف غير مقنع إلى حد كبير. ج

for (int i = 0; i < n; i++)

و Python

for i in range(n)

بالتأكيد لا تشارك نفس الآلية الأساسية ولكن لماذا تختار Python استخدام for ؟ يجب أن تستخدم i in range(n) @ أو i in range(n) --->> أو أيًا كان لإظهار هذا الاختلاف المهم !

السؤال هنا هو ما إذا كان الاختلاف مهمًا لوظيفة المستخدم اليومية!

تدور الحياة اليومية لمستخدم الصدأ النموذجي حول:

  1. التفاعل مع بعض واجهات برمجة تطبيقات HTTP
  2. اكتب بعض رموز الشبكة

وهو يهتم لأمره حقًا

  1. يمكنه إنهاء العمل بطريقة سريعة ومريحة.
  2. الصدأ يؤدي وظيفته.
  3. لديه مستوى منخفض من التحكم إذا أراد

يحتوي NOT rust على مليون تفاصيل تنفيذ تختلف عن C # و JS . تتمثل مهمة مصمم اللغة في إخفاء الاختلافات غير ذات الصلة ، وإظهار الاختلافات المفيدة فقط للمستخدمين .

بالإضافة إلى ذلك ، لا أحد يشكو حاليًا من await!() لأسباب مثل "لا أعرف أنها آلة دولة" ، "لا أعرف أنها تعتمد على السحب".

لا يهتم المستخدمون بهذه الاختلافات ولن يهتموا بها.

انتظاره سيقود المستقبل إلى الاكتمال ، وهو نفس الشيء في أي مكان آخر.

حسنًا ، ليس تمامًا ، أليس كذلك؟ إذا كتبت للتو let result = await!(future) في async fn واستدعيت هذه الوظيفة ، فلن يحدث شيء حتى يتم وضعها على المنفذ واستقصائها بواسطته.

@ ben0x539 نعم ، لقد أعدت قراءته ... لكن ليس لدي حاليًا أي شيء لأضيفه.

@ CAD97 سعيد لرؤية val lazy = async(start=LAZY) { deferred_operation() }; ). وحول الدلالات المماثلة: IMO يتم توفير الدلالات الأقرب في البرمجة التفاعلية ، نظرًا لأن الملاحظات التفاعلية باردة (كسولة) افتراضيًا (على سبيل المثال val lazy = Observable.fromCallable { deferred_operation() }; ). يؤدي الاشتراك فيها إلى جدولة العمل الفعلي بنفس طريقة await!() في Rust ، ولكن هناك أيضًا اختلاف كبير في أن الاشتراك افتراضيًا هو عملية غير محظورة ويتم التعامل مع النتائج المحسوبة بشكل غير متزامن دائمًا في عمليات الإغلاق بشكل منفصل عن تدفق التحكم الحالي. وهناك فرق كبير في كيفية عمل الإلغاء. لذلك ، أعتقد أن سلوك Rust async فريد من نوعه ، وأنا أؤيد تمامًا حجتك بأن await محير وأن البنية المختلفة هي ميزة فقط هنا!

Pzixel أنا متأكد من أنه لن يظهر أي سيجيل جديد. تنفيذ yield كـ sigil لا معنى له لأنه بناء تدفق التحكم مثل if / match / loop / return . من الواضح أن جميع بنيات تدفق التحكم يجب أن تستخدم الكلمات الرئيسية لأنها تصف منطق الأعمال ومنطق الأعمال دائمًا ما يكون له الأولوية. على النقيض من ذلك ، فإن @ بالإضافة إلى ? عبارة عن تركيبات معالجة استثناءات ، كما أن منطق معالجة الاستثناءات أقل أهمية ، لذلك يجب أن يكون دقيقًا. لم أجد أي حجج قوية مفادها أن امتلاك كلمة رئيسية كبيرة await أمر جيد (بالنظر إلى مقدار النص ، من المحتمل أن أفتقدهم) لأنهم جميعًا يناشدون السلطة أو الخبرة (ولكن هذا لا يعني أنني رفض سلطة أو خبرة شخص ما - لا يمكن تطبيقها هنا بشكل كامل).

tajimaha ، مثال Python الذي قدمته يحتوي في الواقع على المزيد من "تفاصيل التنفيذ". يمكننا اعتبار for async ، (int i = 0; i < n; i++) await ، و i in range(n) @ وهنا من الواضح أن قدمت Python كلمة رئيسية جديدة - in (في Java إنها في الواقع sigil - : ) بالإضافة إلى أنها قدمت بنية نطاق جديدة. يمكنه إعادة استخدام شيء مألوف أكثر مثل (i=0, n=0; i<n; i++) بدلاً من تقديم الكثير من تفاصيل التنفيذ. ولكن في الوقت الحالي ، يكون التأثير على تجربة المستخدم إيجابيًا فقط ، وبناء الجملة أبسط ، ويهتم المستخدمون به حقًا.

Pzixel أنا متأكد من أنه لن يظهر أي سيجيل جديد. تنفيذ yield كـ sigil لا معنى له لأنه بناء تدفق التحكم مثل if / match / loop / return . من الواضح أن جميع بنيات تدفق التحكم يجب أن تستخدم الكلمات الرئيسية لأنها تصف منطق الأعمال ومنطق الأعمال دائمًا ما يكون له الأولوية. على العكس من ذلك ، فإن @ بالإضافة إلى ? عبارة عن تركيبات معالجة استثناءات ، كما أن منطق معالجة الاستثناءات أقل أهمية ، لذلك يجب أن يكون دقيقًا.

await ليس "بناء معالجة استثناء".
استنادًا إلى فرضية غير صحيحة ، فإن دلالاتك خاطئة أيضًا.

معالجة الاستثناءات هي عبارة عن تدفق تحكم أيضًا ، ولكن لا أحد يعتقد أن ? أمر سيء.

لم أجد أي حجج قوية مفادها أن امتلاك كلمة رئيسية كبيرة await أمر جيد (مع الأخذ في الاعتبار مقدار النص من المحتمل أن أفتقدهم) لأنهم جميعًا يناشدون السلطة أو الخبرة (ولكن هذا لا يعني أنني رفض سلطة أو خبرة شخص ما - لا يمكن تطبيقها _ هنا_ بشكل كامل).

نظرًا لأن Rust ليس لديه تجربته الخاصة ، لذلك يمكنه فقط رؤية ما تفكر فيه اللغات الأخرى ، وتجربة فرق الآخرين وما إلى ذلك. أنت واثق جدًا من sigil ، لكنني لست متأكدًا مما إذا كنت قد حاولت استخدامه بالفعل.

لا أريد أن أجادل حول بناء الجملة في Rust ، لكنني رأيت العديد من حجج postfix مقابل بادئة وربما يمكننا الحصول على أفضل ما في العالمين. وحاول شخص آخر بالفعل اقتراح هذا في C ++. تم ذكر اقتراح C ++ و Coroutine TS عدة مرات هنا ، ولكن من وجهة نظري ، فإن الاقتراح البديل المسمى Core Coroutines يستحق المزيد من الاهتمام.

يقترح مؤلفو Core Coroutines استبدال co_await برمز جديد يشبه المشغل.
باستخدام ما يسمونه بناء جملة عامل التشغيل unsrap ، يمكن للمرء استخدام كل من تدوين البادئة و postfix (بدون غموض):

future​<string>​ g​();
string​ s ​=​​ [<-]​ f​();

optional_struct​[->].​optional_sub_struct​[->].​field

اعتقدت أنه قد يكون ممتعًا ، أو على الأقل سيجعل المناقشة أكثر اكتمالًا.

لكن Rust يختلف حقًا عن C # أو JavaScript (أو أي لغة أخرى). لذلك لا يمكننا نسخ ما تفعله اللغات الأخرى بشكل أعمى.

من فضلك لا تستخدم "الاختلاف" كذريعة لميلك الشخصي نحو بناء جملة غير تقليدي.

كم عدد الاختلافات الموجودة؟ خذ أي لغة شائعة ، Java ، C ، Python ، JavaScript ، C # ، PHP ، Ruby ، ​​Go ، Swift ، إلخ ، ثابتة ، ديناميكية ، مجمعة ، مفسرة. تختلف اختلافًا كبيرًا في مجموعة الميزات ، لكن لا يزال لديهم الكثير من القواسم المشتركة في تركيبهم. هل هناك لحظة تشعر فيها أن أيًا من هذه اللغات مثل "brainf * ck"؟

أعتقد أننا يجب أن نركز على تقديم ميزات مختلفة ولكنها مفيدة ، وليس بناء جملة غريب.

بعد قراءة الموضوع ، أعتقد أيضًا أن النقاش انتهى إلى حد كبير عندما يلغي شخص ما الحاجة الملحة للتسلسل ، يقوم شخص ما بإبطال الادعاء بأن الناس منزعجون من الكثير من المتغيرات المؤقتة.

IMO ، لقد فقدت النقاش حول احتياجات بناء جملة postfix. يمكنك فقط اللجوء إلى "الاختلاف". إنها محاولة يائسة أخرى.

tensorduruk لدي شعور بأن كلماتك معادية جدًا للمستخدمين الآخرين. يرجى محاولة فحصهم.


وبصراحة ، إذا كان هناك العديد من الأشخاص الذين يعارضون بناء جملة postfix ، فعلينا فقط أن نقرر بناء جملة البادئة في الوقت الحالي ، وننتظر لنرى كيف تتم كتابة التعليمات البرمجية مع بناء جملة البادئة ، ثم إجراء تحليل لمعرفة مقدار الشفرة المكتوبة يمكن أن تستفيد من انتظار postfix. بهذه الطريقة نرضي كل شخص غير مرتاح لتغيير مبتكر مثل انتظار postfix ، بينما نحصل أيضًا على حجة عملية جيدة حول ما إذا كان يجب انتظار postfix أم لا.

السيناريو الأسوأ مع هذا هو إذا فعلنا كل ذلك وتوصلنا إلى postfix في انتظار بناء الجملة الذي هو بطريقة ما أفضل تمامًا من البادئة التي تنتظرها. سيؤدي ذلك إلى الكثير من الاضطراب في التعليمات البرمجية إذا كان الناس ينزعجون من التحول إلى شكل أفضل من الانتظار.

وأعتقد أن كل هذا النقاش النحوي يعود في الحقيقة إلى التسلسل. إذا لم يكن التسلسل شيئًا ، فسيكون انتظار postfix خارج النافذة تمامًا وسيكون من الأسهل كثيرًا الاستقرار على بادئة انتظار فقط. ومع ذلك ، فإن التسلسل يعد شيئًا كبيرًا في Rust ، وبالتالي فهو يفتح المناقشة للمواضيع التالية:

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?

أو شيء من هذا القبيل. إذا استطاع شخص ما التوصل إلى نمط قرار أفضل بنقاط أفضل مما فعلت ، فيرجى القيام بذلك! وجه ضاحك

لذا أولاً وقبل كل شيء ، بدلاً من مناقشة بناء الجملة ، يجب أن نقرر ما إذا كنا نريد postfix أو بادئة أو كلاهما postfix والبادئة ، ولماذا نريد الاختيار الذي توصلنا إليه. بمجرد أن ننتهي من ذلك ، يمكننا أن ندخل في مشاكل أصغر تتعلق باختيار بناء الجملة.

تضمين التغريدة

IMO ، لقد فقدت النقاش حول احتياجات بناء جملة postfix. يمكنك فقط اللجوء إلى "الاختلاف". إنها محاولة يائسة أخرى.

بجدية ، لماذا لا تستخدم فقط اللغات المذكورة بدلاً من العداء الذي لا مبرر له هنا؟
باستخدام منطقك ، يجب أن يكون لدى الصدأ فصول لأن اللغات التقليدية الأخرى بها. لا ينبغي أن يكون الصدأ مستعارة لأن لغة أخرى لا تملكها.

عدم التعامل مع التسلسل سيكون فرصة ضائعة. أنا ، ه ، لا أريد إنشاء ارتباطات لمتغير مؤقت ، إذا كان التسلسل يمكن أن يجعله مجهولاً.

ومع ذلك ، أشعر أننا يجب أن نلتزم فقط بالكلمات الرئيسية الحالية للماكرو للانتظار كما في الليل. اجعل وحدات ماكرو postfix كميزة ليلية ودع الناس يلعبون بها. نعم ، ستكون هناك اضطرابات بمجرد الاستقرار ، ولكن يمكن معالجة ذلك عن طريق إصلاح الصدأ.

تضمين التغريدة

IMO ، لقد فقدت النقاش حول احتياجات بناء جملة postfix. يمكنك فقط اللجوء إلى "الاختلاف". إنها محاولة يائسة أخرى.

بجدية ، لماذا لا تستخدم فقط اللغات المذكورة بدلاً من العداء الذي لا مبرر له هنا؟
باستخدام منطقك ، يجب أن يكون لدى الصدأ فصول لأن اللغات التقليدية الأخرى بها. لا ينبغي أن يكون الصدأ مستعارة لأن لغة أخرى لا تملكها.

عدم التعامل مع التسلسل سيكون فرصة ضائعة. أنا ، ه ، لا أريد إنشاء ارتباطات لمتغير مؤقت ، إذا كان التسلسل يمكن أن يجعله مجهولاً.

ومع ذلك ، أشعر أننا يجب أن نلتزم فقط بالكلمات الرئيسية الحالية للماكرو للانتظار كما في الليل. اجعل وحدات ماكرو postfix كميزة ليلية ودع الناس يلعبون بها. نعم ، ستكون هناك اضطرابات بمجرد الاستقرار ، ولكن يمكن معالجة ذلك عن طريق إصلاح الصدأ.

الرجاء قراءة بعناية. قلت أنه يجب علينا تقديم ميزات مفيدة وليست صياغة غريبة. هيكل؟ لاريتك؟ المميزات!

يرجى أيضًا حل المشكلات الموجودة بالفعل. أظهر الأشخاص ، مع أمثلة أو إحصائيات ، أن عمليات الربط المؤقتة قد لا تكون مشكلة. هل تدعمون يا رفاق f await f.await حاولوا إقناع الطرف الآخر باستخدام الأدلة؟

كما أن التصويت المعارض لي أمر عديم الفائدة. للحصول على الموافقة بعد الإصلاح ، عليك أن تجادل في مكان فائدة postfix (ربما لا تكرر جدال التسلسل ، فهذا طريق مسدود ؛ ربما مشكلة متكررة تزعج الناس ، مع بعض الأدلة ، وليست مشكلة لعبة).

هل يمكننا الحصول على بناء الجملة مثل C # أو JS؟ يستخدمه معظم المطورين في العالم ، فأنا لا أحب استخدام الصدأ بناء جملة جديد أو غير متناسق ، كما يصعب على الأشخاص الجدد تعلمه.

سيكون ما يلي ملحقًا بمنشوري حول استخدام postfix @ insead await


يجب أن نستخدم خبرة العديد من اللغات الأخرى بدلاً من ذلك

ونحن نستخدمه بالفعل. لكن هذا لا يعني أننا يجب أن نكرر نفس التجربة تمامًا. أيضًا ، عند الجدال بهذه الطريقة ضد @ المحتمل ألا تحقق أي نتيجة مفيدة لأن مناشدة التجربة السابقة غير مقنع :

قد تكون الحسابات الجينية لقضية ما صحيحة ، وقد تساعد في توضيح الأسباب التي جعلت القضية تتخذ شكلها الحالي ، لكنها ليست قاطعة في تحديد مزاياها.

يجب أن نستخدم await لأن الكثير من الناس يحبونه

قد يحب هؤلاء الأشخاص await لعدة أسباب أخرى ، ليس فقط لأنه await . قد لا توجد هذه الأسباب في Rust أيضًا. والجدل بهذه الطريقة ضد @ لن يجلب على الأرجح أي نقاط جديدة للمناقشة لأن جذب الجمهور غير مقنع :

يمكن أن تكون الوسيطة ad populum حجة صالحة في المنطق الاستقرائي. على سبيل المثال ، قد يجد استطلاع لعدد كبير من السكان أن 100٪ يفضلون علامة تجارية معينة لمنتج على أخرى. يمكن بعد ذلك تقديم حجة مقنعة (قوية) مفادها أن الشخص التالي الذي سيتم النظر فيه سيفضل على الأرجح تلك العلامة التجارية (ولكن ليس دائمًا بنسبة 100٪ نظرًا لاحتمال وجود استثناءات) ، والاستطلاع هو دليل صالح على هذا الادعاء. ومع ذلك ، فإنه من غير المناسب كحجة للتفكير الاستنتاجي كدليل ، على سبيل المثال أن نقول أن الاستطلاع يثبت أن العلامة التجارية المفضلة تتفوق على المنافسة في تكوينها أو أن الجميع يفضل تلك العلامة التجارية على الأخرى.

تضمين التغريدة

لكن الصدأ شيء آخر. لدينا ? هنا وعلينا نشر الأخطاء. في C # async / wait يلف الاستثناءات تلقائيًا ، ويرميها عبر نقاط الانتظار وما إلى ذلك. ليس لديك في الصدأ. ضع في اعتبارك كل مرة تكتب فيها await في C # عليك الكتابة

var response = (await client.GetAsync("www.google.com")).HandleException();
var json =  (await response.ReadAsStreamAsync()).HandleException();
var somethingElse = (await DoMoreAsyncStuff(json)).HandleException();
...

إنه أمر ممل للغاية أن يكون لديك كل هذه الأقواس.

في C # تكتب هذا:

try
{
  var response = await client.GetAsync("www.google.com");
  var json =  await response.ReadAsStreamAsync();
  var somethingElse = await DoMoreAsyncStuff(json);
}
catch (Exception ex)
{
  // handle exception
}

وفيما يتعلق حجة تسلسل يروجه من الأخطاء، مثل foo().await? ، هل هناك أي سبب يمنع ? لا يمكن أن تضاف إلى await المشغل في بادئة؟

let response = await? getProfile();

شيء آخر حدث لي للتو: ماذا لو كنت تريد match على Future<Result<...>> ؟ أي من هؤلاء أسهل في القراءة؟

// Prefix
let userId = match await response {
  Ok(u) => u.id,
  _ => -1
};
// Postfix
let userId = match response {
  Ok(u) => u.id,
  _ => -1
} await;

بالإضافة إلى ذلك ، هل سيكون تعبير async match شيئًا؟ كما هو الحال في ، هل تريد أن يكون نص تعبير المطابقة غير متزامن؟ إذا كان الأمر كذلك ، فسيكون هناك فرق بين match await response و await match response . نظرًا لأن match و await كلاهما عاملان وحيدان فعليًا ، و match هو بادئة بالفعل ، سيكون من الأسهل التمييز إذا كان await هو أيضًا بادئة. مع بادئة واحدة و postfix ، يصبح من الصعب تحديد ما إذا كنت تنتظر المباراة أو الاستجابة.

let userId = match response {
  Ok(u) => somethingAsync(u),
  _ => -1
} await; // Are we awaiting match or response here?

إذا كان عليك انتظار كلا الأمرين ، فستنظر إلى شيء مثل

// 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;

على الرغم من أنني أعتقد أن ذلك قد يكون

// Postfix - ... this is weirder and uglier
let userId = match response await {
  Ok(u) => somethingAsync(u),
  _ => -1
} await;

(تصميم لغة البرمجة صعب.)

بغض النظر ، سأعيد التأكيد على أن Rust لها الأسبقية بالنسبة للمشغلين الأحاديين كونها بادئة بـ match ، و await عامل تشغيل وحيد.

C# // Postfix - ... this is weirder and uglier let userId = match response await { ... } await;

الجمال هو في عين الناظر.

بغض النظر ، سأكرر أن Rust له الأسبقية بالنسبة للمشغلين الأحاديين كونها بادئة بـ match ، و await عامل تشغيل وحيد.

من ناحية أخرى ، ? أحادي لكن postfix.

بغض النظر ، أشعر أن المناقشة تدور الآن حول البالوعة. بدون إثارة أي نقاط مناقشة جديدة ، لا فائدة من إعادة تكرار المواقف نفسها مرارًا وتكرارًا.

FWIW ، يسعدني الحصول على دعم await مهما كانت الصيغة - بينما لدي تفضيلاتي الخاصة ، لا أعتقد أن أيًا من الاقتراحات الواقعية سيئ للغاية للاستخدام.

markrendle لست متأكدًا مما تجيب عليه

في C # تكتب هذا:

أعرف كيف أكتب بلغة C #. قلت "تخيل كيف يمكن أن تبدو ليس لدينا استثناءات". لأن الصدأ لا.

وفيما يتعلق حجة تسلسل يروجه من الأخطاء، مثل foo().await? ، هل هناك أي سبب لماذا ? لا يمكن أن تضاف إلى await المشغل في بادئة؟

لقد تمت مناقشته بالفعل مرتين أو ثلاث مرات ، من فضلك ، اقرأ الموضوع. باختصار: إنه بناء اصطناعي ، لن يعمل بشكل جيد إذا كان لدينا شيء إضافي ? . await? كلاحقة تعمل فقط ، عندما يتطلب await? كبادئة دعمًا إضافيًا في المترجم. ولا يزال يتطلب أقواس للتسلسل (وهو ما لا يعجبني هنا بشكل عام ، لكن الناس يذكرونه دائمًا على أنه شيء مهم) ، عندما لا ينتظر postfix.

شيء آخر حدث لي للتو: ماذا لو كنت تريد match على Future<Result<...>> ؟ أي من هؤلاء أسهل في القراءة؟

// 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
};

باعتبارك مستخدمًا آخر لـ C # ، سأقول إن بنية البادئة الخاصة بها: new ، await ، والقوالب ذات النمط C قد أخطأت حدسي كثيرًا. أنا أؤيد بشدة خيار المشغل postfix.

ومع ذلك ، فإن أي بناء جملة سيكون أفضل من تسلسل العقود الآجلة بشكل صريح ، حتى الماكرو الزائف. سوف أرحب بأي قرار.

orthoxerox أنت تثير نقطة جيدة للغاية. في عملي اليومي ، أكتب Java في الغالب وأحتقر المشغل الجديد إلى مستوى أن جميع الفصول التي تحتاج إلى إنشاء مثيل صريح (حدث نادر بشكل مفاجئ عند استخدام أنماط البناء وحقن التبعية) لديها طريقة مصنع ثابتة فقط يمكنني من خلالها إخفاء المشغل .

تضمين التغريدة

markrendle لست متأكدًا مما تجيب عليه

في C # تكتب هذا:

أعرف كيف أكتب بلغة C #. قلت "تخيل كيف يمكن أن تبدو ليس لدينا استثناءات". لأن الصدأ لا.

أعتقد أن هذا ربما يكون حاجزًا لغويًا لأن هذا ليس ما قلته على الإطلاق ، لكنني سأتقبل أنه ربما كان ما قصدته.

على أي حال ، كما قال rolandsteiner ، الشيء المهم هو أن نحصل على شكل من أشكال عدم التزامن / الانتظار ، لذلك يسعدني انتظار قرار الفريق الأساسي ، ويمكن لجميع عشاق ما بعد الإصلاح أن ينتظر قرار الفريق الأساسي. 😛 ❤️ ☮️

yasammez تعال إلى C #. في الإصدار 8.0 ، سنستخدم new () فقط بدون اسم النوع :)

سأطرح بعض الأفكار لمشغل postfix.

foo()~; // the pause operator
foo()^^; // the road bumps operator
foo()>>>; // the fast forward operator

لا أقول ما إذا كان مشغل postfix هو الطريق الصحيح أم لا ، ولكنني شخصياً أجد @ واحدًا من أكثر الخيارات صاخبة وغرابة من بين جميع الخيارات الممكنة. ~ من تعليق phaux أكثر أناقة وأقل "مشغول". أيضًا ، إذا لم يفوتني أي شيء ، فإننا لا نستخدمه لأي شيء في Rust حتى الآن.

تم اقتراح ~ قبل phaux على الرغم من أنني لا أريد المطالبة ببراءة اختراع ؛ P

اقترحت هذا لأنه يشبه الحديث بالصدى:

Hi~~~~~
Where r u~~~~~

Hay~~~~~
I am in another mountain top~~~~~

~ أحيانًا بعد الجملة للإشارة إلى التتابع ، وهو أمر مناسب للانتظار!

لا أستطيع أن أعرف ما إذا كان هذا الخيط قد وصل إلى ذروة السخرية أم أننا في شيء ما هنا.

أعتقد ~ انها ليست سهلة الإجابة على بعض لوحات المفاتيح، وخاصة بعض لوحات المفاتيح الميكانيكية الصغيرة والحساسة.

ممكن ان يكون:

let await userId = match response {
  Ok(u) => u.id,
  _ => -1
};
let await userId = match response {
  await Ok(u) => somethingAsync(u),
  _ => ok(-1)
};

يمكننا تقديم رسم ثلاثي مثل ... للمستخدمين على تخطيطات لوحة المفاتيح حيث ~ غير مريح.

في البداية ، كنت أؤيد بشدة بناء جملة بادئة مطلوبة مثل await(future) أو await{future} لأنه لا لبس فيه ويسهل تحليله بصريًا. ومع ذلك ، أفهم المقترحات المقدمة من الآخرين بأن مستقبل الصدأ ليس مثل معظم العقود الآجلة الأخرى من اللغات الأخرى ، لأنه لا يضع مهمة على المنفذ على الفور ، ولكنه بدلاً من ذلك هو هيكل تدفق تحكم يحول السياق إلى ما هو متماثل إلى سلسلة استدعاء أحادية بشكل أساسي.

يجعلني أعتقد أنه من المؤسف إلى حد ما أنه يوجد الآن ارتباك في محاولة مقارنته باللغات الأخرى بهذا الصدد. أقرب نظير هو ترميز monad do في Haskell أو فهم for في Scala (وهما الوحيدان اللذان أعرفهما من أعلى رأسي). فجأة ، أقدر النظر في اقتراح بناء جملة فريد ، لكنني أخشى أن وجود عامل التشغيل ? قد شجع على استخدام سيجيلات أخرى معه. أي عامل تشغيل آخر قائم على sigil بجوار ? يجعل الأمر يبدو صاخبًا ومربكًا ، مثل future@? ، لكن السابقة التي تم تعيينها من خلال وجود عامل تشغيل sigil postfix تعني أن عاملًا آخر ليس سخيفًا للغاية.

لذلك ، لقد اقتنعت بميزة عامل postfix sigil. الجانب السلبي لهذا ، هو أن السجيل الذي كنت أفضله يتم أخذه من خلال الكتابة مطلقًا. كنت أفضل ! لأنني أعتقد أن future!? سيجعلني أضحك ضحكة مكتومة كلما كتبته ، وهو أمر منطقي بالنسبة لي أن أراه. أفترض أن $ سيكون التالي ، إذًا لأنه يمكن تمييزه بصريًا future$? . لا تزال رؤية ~ تذكرني بالأيام الأولى من Rust عندما كان ~ عامل تشغيل البادئة لتخصيص الكومة. كل ذلك شخصي للغاية ، لذلك لا أحسد صانعي القرار النهائيين. لو كنت أنا منهم ، كنت سأختار على الأرجح الخيار غير المؤذي لعامل البادئة مع المحددات المطلوبة.

ومع ذلك ، أفهم المقترحات المقدمة من الآخرين بأن مستقبل الصدأ ليس مثل معظم العقود الآجلة الأخرى من اللغات الأخرى ، لأنه لا يضع مهمة على المنفذ على الفور ، ولكنه بدلاً من ذلك هو هيكل تدفق تحكم يحول السياق إلى ما هو متماثل إلى سلسلة استدعاء أحادية بشكل أساسي.

أنا أميل إلى الاختلاف. السلوك الذي ذكرته ليس خاصية await ، ولكن من خاصية أو نطاق async المحيط. ليس await الذي يؤخر تنفيذ الكود الذي يسبقه ، ولكن النطاق الذي يحتوي على الكود المذكور.

ربما تكون المشكلة في البحث الغريب عن رمز @ هو أن استخدامه في هذا السياق لم يكن متوقعًا من قبل ، لذلك في معظم الخطوط يتم توفيره بشكل غير مريح للغاية بالنسبة لنا.

بعد ذلك ، قد يؤدي تقديم حرف رسومي أفضل وبعض الأحرف المزدوجة لخطوط البرمجة الشائعة (أو على الأقل لـ Mozilla Fira Code ) إلى تحسين الوضع قليلاً.

في جميع الحالات الأخرى ، بالنسبة لي ، لا يبدو أن @ غريب جدًا بحيث يتسبب في أي مشاكل حقيقية عند كتابة الكود أو الحفاظ عليه.


على سبيل المثال ، يستخدم الكود التالي رمزًا مختلفًا عن رمز @ - :


// 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)
}

قم بالتوسيع للمقارنة كيف يبدو مع 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)
}

تضمين التغريدة

في البداية ، كنت أؤيد بشدة بناء جملة بادئة مطلوبة مثل await(future) أو await{future} لأنه لا لبس فيه ويسهل تحليله بصريًا.

من المحتمل إذن أنك تريد if { cond } ، while { cond } و match { expr } ...

ومع ذلك ، فأنا أفهم المقترحات المقدمة من الآخرين بأن مستقبل الصدأ ليس مثل معظم العقود المستقبلية الأخرى من اللغات الأخرى

هذا ليس صحيحا. انها في الواقع هو مثل معظم العقود الآجلة أخرى من لغات أخرى. الفرق بين "تشغيل حتى الانتظار الأول عند ظهور" مقابل "تشغيل حتى الانتظار الأول عند الاستطلاع" ليس كبيرًا. أعرف لأنني عملت مع كليهما. إذا كنت تعتقد بالفعل "عندما يتعلق الأمر بهذا الاختلاف" ، فستجد حالة زاوية فقط. على سبيل المثال ، سوف تحصل على خطأ عند إنشاء المستقبل على الاستطلاع الأول بدلاً من وقت إنشائه.

يمكن أن تكون مختلفة داخليًا ، لكنها متشابهة تمامًا من منظور المستخدم ، لذلك ليس من المنطقي بالنسبة لي التمييز هنا.

تضمين التغريدة

الفرق بين "تشغيل حتى الانتظار الأول عند ظهور" مقابل "تشغيل حتى الانتظار الأول عند الاستطلاع" ليس كبيرًا.

هذا ليس هو الاختلاف الذي نتحدث عنه. نحن لا نتحدث عن كسول مقابل حريص على الانتظار أولاً.

ما نتحدث عنه هو أن await ينضم إلى المنتظر Promise (JS) / Task (C #) على المنفذ بلغات أخرى ، والذي تم وضعه بالفعل على المنفذ عند الإنشاء (لذلك كان يعمل بالفعل في "الخلفية") ، ولكن في Rust ، فإن Future s عبارة عن أجهزة حالة خاملة حتى await! مدفوعة.

Promise / Task هو مؤشر لعملية جارية غير متزامنة. Future حساب مؤجل غير متزامن. الأشخاص ، بما في ذلك الأسماء البارزة في Rust ، ارتكبوا هذا الخطأ من قبل ، وقد تم ربط الأمثلة من قبل في منتصف هذه التعليقات البالغ عددها 500+.

أنا شخصياً أعتقد أن عدم تطابق الدلالات هذا كبير بما يكفي لمواجهة ألفة await . حقق Future الخاص بنا نفس الهدف مثل Promise / Task ، لكن من خلال آلية مختلفة.


حسب الروايات ، عندما تعلمت لأول مرة async / await في JavaScript ، كان async شيئًا كتبته "فقط" للحصول على القوة العظمى await . والطريقة التي تعلمت بها للحصول على التوازي كانت a = fa(); b = fb(); /* later */ await [a, b]; (أو أيا كان ، لقد كان عصرًا منذ أن اضطررت إلى كتابة JS). نظري هو أن وجهة نظر الآخرين لـ async تتوافق معي ، في أن دلالات Rust لا تتطابق مع async (تمنحك await قوة عظمى) ، ولكن على Future للبناء و await! .


في هذه المرحلة ، أعتقد أن المناقشة حول الاختلافات في دلالات Rust async / Future / await قد انتهت ، ولم يتم تقديم أي معلومات جديدة. ما لم يكن لديك منصب جديد و / أو نظرة ثاقبة لتقديمها ، فمن المحتمل أن يكون من الأفضل للموضوع إذا تركنا هذه المناقشة هنا. (يسعدني أن آخذه إلى Internals و / أو Discord.)

@ CAD97 نعم ، أرى موقفك ، لكنني أعتقد أن التمييز ليس بهذا الحجم.

لقد حصلت علي ، لقد حصلت عليك. لذا دع المناقشة تتدفق.

@ CAD97

الأشخاص ، بما في ذلك الأسماء البارزة في Rust ، ارتكبوا هذا الخطأ من قبل ، وقد تم ربط الأمثلة من قبل في منتصف هذه التعليقات البالغ عددها 500+.

إذا ارتكب الأشخاص الذين يعرفون Rust عن كثب هذا الخطأ ، فهل هذا خطأ حقًا؟

لذلك ، أجرينا عددًا من المناقشات حول الانتظار غير المتزامن في Rust All Hands. في سياق تلك المناقشات ، اتضحت بعض الأشياء:

أولاً ، لا يوجد إجماع (حتى الآن) في فريق lang حول صيغة الانتظار . من الواضح أن هناك الكثير من الاحتمالات والحجج القوية لصالحها جميعًا. لقد أمضينا وقتًا طويلاً في استكشاف البدائل وأنتجنا قدرًا كبيرًا جدًا من تبادل الأفكار. أعتقد أن الخطوة التالية الفورية لهذه المناقشة هي تحويل تلك الملاحظات (مع التعليقات الأخرى من هذا الموضوع) إلى نوع من التعليقات الموجزة التي تحدد حالة كل متغير ، ثم المتابعة من هناك. أنا أعمل مع withoutboats و cramertj على ذلك.

بالتراجع عن السؤال النحوي ، هناك شيء آخر نخطط للقيام به وهو فرز شامل لحالة التنفيذ. هناك عدد من القيود الحالية (على سبيل المثال ، يتطلب التنفيذ TLS تحت الغطاء حاليًا لترابط المعلومات حول المستيقظ). قد تكون هذه أو لا تكون مانعة للاستقرار ، ولكن بغض النظر عن كونها مشكلات تحتاج إلى المعالجة في النهاية ، وسيتطلب ذلك بعض الجهود المتضافرة (جزئيًا من فريق المترجم). الخطوة التالية الأخرى هي إجراء هذا الفرز وإنشاء تقرير. أتوقع منا إجراء هذا الفرز الأسبوع المقبل ، وسيكون لدينا تحديث بعد ذلك.

في غضون ذلك ، سوف أمضي قدمًا وأغلق هذا الموضوع حتى تتاح لنا الفرصة لإنتاج التقارير المذكورة أعلاه . أشعر أن الخيط قد خدم بالفعل غرضه المتمثل في استكشاف مساحة التصميم المحتملة ببعض العمق وأن التعليقات الإضافية لن تكون مفيدة بشكل خاص في هذه المرحلة. بمجرد حصولنا على التقارير المذكورة أعلاه في متناول اليد ، سنضع أيضًا الخطوات التالية نحو الوصول إلى قرار نهائي.

(للتوسع قليلاً في الفقرة الأخيرة ، أنا مهتم جدًا باستكشاف طرق بديلة لاستكشاف مساحة التصميم بخلاف سلاسل المناقشة الطويلة. هذا موضوع أكبر بكثير مما يمكنني تناوله في هذا التعليق ، لذلك لن أخوض في التفاصيل ، ولكن يكفي أن أقول في الوقت الحالي إنني مهتم جدًا بمحاولة إيجاد طرق أفضل لحل هذه المناقشات النحوية - والمستقبلية!).

تقرير حالة انتظار Async:

http://smallcultfollowing.com/babysteps/blog/2019/03/01/async-await-status-report/

بالنسبة لتعليقي السابق ، يحتوي هذا على نتائج الفرز وبعض الأفكار حول بناء الجملة (ولكن ليس بعد كتابة النحو الكامل).

وضع علامة على هذه المشكلة على أنها حظر غير متزامن في انتظار الاستقرار ، على الأقل في الوقت الحالي.

منذ فترة وجيزة ، وعد نيكو بأننا سنكتب ملخصًا للمناقشة في فريق اللغة والمجتمع حول الصيغة النهائية للمشغل المنتظر. نعتذر عن الانتظار الطويل. تم ربط كتابة حالة المناقشة أدناه. قبل ذلك ، اسمحوا لي أيضًا أن أقدم تحديثًا حول وضع المناقشة الآن وإلى أين سننتقل من هنا.

ملخص موجز لمكان انتظار الانتظار غير المتزامن الآن

أولاً ، نأمل أن نستقر في انتظار عدم التزامن في الإصدار 1.37 ، الذي يتفرع في 4 يوليو 2019. نظرًا لأننا لا نريد تثبيت الماكرو await! ، يتعين علينا حل مسألة بناء الجملة قبل ذلك الحين. لاحظ أن هذا التثبيت لا يمثل نهاية الطريق - بل يمثل البداية. لا يزال هناك عمل خاص يجب القيام به (على سبيل المثال ، غير متزامن في السمات) وأيضًا العمل الضمني (التحسين المستمر ، إصلاح الأخطاء ، وما شابه). ومع ذلك ، فإن استقرار حالة عدم التزامن / الانتظار سيكون علامة بارزة!

بقدر ما يذهب بناء الجملة ، فإن خطة الحل هي كما يلي:

  • للبدء ، نقوم بنشر مقالة عن مناقشة بناء الجملة حتى الآن - يرجى إلقاء نظرة.
  • نريد أن نكون متوافقين مع الامتدادات المستقبلية الواضحة للبنية: معالجة التدفقات باستخدام حلقات for وجه الخصوص (مثل حلقة JavaScript for await ). لهذا السبب كنت أعمل على سلسلة من المنشورات حول هذه المشكلة ( أول مشاركة هنا والمزيد قادم في المستقبل).
  • في اجتماع فريق lang القادم في 2 مايو ، نخطط لمناقشة التفاعل مع حلقات for وأيضًا لوضع خطة للتوصل إلى قرار نهائي بشأن بناء الجملة في الوقت المناسب لتحقيق الاستقرار في حالة عدم التزامن / الانتظار في 1.37. سنقوم بنشر تحديث بعد الاجتماع على هذا الموضوع الداخلي .

الكتابة

الكتابة عبارة عن مستند ورقي بصندوق الإسقاط ، متاح هنا . كما سترى ، إنها طويلة إلى حد ما وتعرض الكثير من الحجج ذهابًا وإيابًا. سنكون ممتنين ردود الفعل على ذلك ؛ بدلاً من إعادة فتح هذه المشكلة (التي تحتوي بالفعل على أكثر من 500 تعليق) ، قمت بإنشاء سلسلة رسائل

كما قلت من قبل ، نخطط للتوصل إلى قرار نهائي في المستقبل القريب. نشعر أيضًا أن المناقشة قد وصلت إلى حد كبير إلى حالة مستقرة: نتوقع أن تكون الأسابيع القليلة القادمة "فترة التعليق النهائية" لمناقشة بناء الجملة هذه. بعد الاجتماع ، نأمل أن يكون لدينا جدول زمني أكثر تفصيلاً لمشاركة كيفية اتخاذ هذا القرار.

قد يكون بناء الجملة Async / wait هو أكثر الميزات المتوقعة بشدة التي اكتسبها Rust منذ 1.0 ، وكان بناء الجملة للانتظار على وجه الخصوص أحد القرارات التي تلقينا بشأنها معظم التعليقات. شكراً لكل من شارك في هذه المناقشات خلال الأشهر القليلة الماضية! هذا هو الخيار الذي لدى الكثير من الناس مشاعر متباينة بشدة ؛ نريد أن نؤكد للجميع أنه يتم الاستماع إلى ملاحظاتك وأن القرار النهائي سيتم التوصل إليه بعد مداولات مدروسة ودقيقة.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات