Rust: مشكلة في تتبع عدم التزامن / انتظار (RFC 2394)

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

هذه هي مشكلة التعقب لـ RFC 2394 (rust-lang / rfcs # 2394) ، والتي تضيف عدم تزامن وتنتظر بناء الجملة للغة.

سوف أقود أعمال التنفيذ الخاصة بهذا RFC ، لكنني سأكون ممتنًا للإرشاد لأن لدي خبرة قليلة نسبيًا في العمل في Rustc.

لكى يفعل:

أسئلة لم يتم حلها:

A-async-await A-generators AsyncAwait-Triaged B-RFC-approved C-tracking-issue T-lang

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

حول بناء الجملة: أود حقًا استخدام await ككلمة رئيسية بسيطة. على سبيل المثال ، دعنا نلقي نظرة على مصدر قلق من المدونة:

لسنا متأكدين تمامًا من البنية التي نريدها للكلمة الرئيسية المنتظرة. إذا كان هناك شيء ما هو مستقبل لنتيجة - مثل أي مستقبل IO - فأنت تريد أن تكون قادرًا على انتظاره ثم تطبيق عامل التشغيل ? عليه. لكن ترتيب الأسبقية لتمكين هذا قد يبدو مفاجئًا - await io_future? سيكون await أولاً و ? ثانيًا ، على الرغم من أن ? مرتبط معجميًا بإحكام أكثر من الانتظار.

أوافق هنا ، لكن الأقواس شريرة. أعتقد أنه من الأسهل تذكر أن ? له أسبقية أقل من await وتنتهي به:

let foo = await future?

من الأسهل قراءتها ، ومن الأسهل إعادة البناء. أعتقد أن هذا هو النهج الأفضل.

let foo = await!(future)?

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

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


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

ال 308 كومينتر

يبدو أن المناقشة هنا قد تلاشت ، لذا ربطها هنا كجزء من السؤال النحوي await : https://internals.rust-lang.org/t/explicit-future-construction-implicit-await/ 7344

تم حظر التنفيذ على # 50307.

حول بناء الجملة: أود حقًا استخدام await ككلمة رئيسية بسيطة. على سبيل المثال ، دعنا نلقي نظرة على مصدر قلق من المدونة:

لسنا متأكدين تمامًا من البنية التي نريدها للكلمة الرئيسية المنتظرة. إذا كان هناك شيء ما هو مستقبل لنتيجة - مثل أي مستقبل IO - فأنت تريد أن تكون قادرًا على انتظاره ثم تطبيق عامل التشغيل ? عليه. لكن ترتيب الأسبقية لتمكين هذا قد يبدو مفاجئًا - await io_future? سيكون await أولاً و ? ثانيًا ، على الرغم من أن ? مرتبط معجميًا بإحكام أكثر من الانتظار.

أوافق هنا ، لكن الأقواس شريرة. أعتقد أنه من الأسهل تذكر أن ? له أسبقية أقل من await وتنتهي به:

let foo = await future?

من الأسهل قراءتها ، ومن الأسهل إعادة البناء. أعتقد أن هذا هو النهج الأفضل.

let foo = await!(future)?

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

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


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

لدي آراء متباينة حول أن await كلمة رئيسية ،Pzixel. في حين أن لها بالتأكيد جاذبية جمالية ، وربما تكون أكثر اتساقًا ، نظرًا لأن async هي كلمة رئيسية ، فإن كلمة "bloat" في أي لغة هي مصدر قلق حقيقي. ومع ذلك ، هل وجود async بدون await له أي معنى ، من حيث الميزات؟ إذا كان الأمر كذلك ، فربما يمكننا تركه كما هو. إذا لم يكن الأمر كذلك ، فسأميل نحو جعل await كلمة رئيسية.

أعتقد أنه من الأسهل تذكر أن أسبقية ? أقل من await وتنتهي بها

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

كما أنه لا يساعد في جميع الحالات ، على سبيل المثال الوظيفة التي تُرجع Result<impl Future, _> :

let foo = await (foo()?)?;

القلق هنا ليس ببساطة "هل يمكنك فهم أسبقية انتظار واحد + ? " ، ولكن أيضًا "كيف يبدو الأمر عند ربط عدة مرات انتظار". لذا ، حتى لو اخترنا الأولوية ، فسنظل نواجه مشكلة await (await (await first()?).second()?).third()? .

ملخص لخيارات بناء الجملة await ، بعضها من RFC والباقي من سلسلة RFC:

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

ومع ذلك ، هل وجود async بدون await له أي معنى ، من حيث الميزة؟

alexreg يفعل. يعمل Kotlin بهذه الطريقة ، على سبيل المثال. هذا هو خيار "الانتظار الضمني".

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

تعدalexreg async / await ميزة رائعة حقًا ، حيث أعمل معها على أساس يومي في C # (وهي لغتي الأساسية). rpjohnst صنف كل الاحتمالات جيدًا. أفضل الخيار الثاني ، أوافق على اعتبارات الآخرين (صاخبة / غير عادية / ...). لقد كنت أعمل مع رمز غير متزامن / انتظار على مدار السنوات الخمس الماضية أو شيء من هذا القبيل ، من المهم حقًا أن يكون لديك مثل هذه الكلمات الرئيسية للعلامة.

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

لذا حتى لو اخترنا الأسبقية ، فسنظل لدينا مشكلة الانتظار (انتظر (انتظر أولاً ()؟). ثانيًا ()؟). ثالثًا () ؟.

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

let first = await first()?;
let second = await first.second()?;
let third = await second.third()?;

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

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

في ممارستي ، لا تكتب أبدًا await في سطر واحد.

ولكن هل هذا لأنها فكرة سيئة بغض النظر عن البنية ، أو لمجرد أن بناء جملة await لـ C # يجعلها قبيحة؟ قدم الناس حججًا مماثلة حول try!() (مقدمة إلى ? ).

الإصدارات اللاحقة والإصدارات الضمنية أقل بشاعة:

first().await?.second().await?.third().await?
first()?.second()?.third()?

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

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

على سبيل المثال ، دعنا نلقي نظرة على الكود الحقيقي (لقد أخذت قطعة واحدة من مشروعي):

[Fact]
public async Task Should_UpdateTrackableStatus()
{
    var web3 = TestHelper.GetWeb3();
    var factory = await SeasonFactory.DeployAsync(web3);
    var season = await factory.CreateSeasonAsync(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(1));
    var request = await season.GetOrCreateRequestAsync("123");

    var trackableStatus = new StatusUpdate(DateTimeOffset.UtcNow, Request.TrackableStatuses.First(), "Trackable status");
    var nonTrackableStatus = new StatusUpdate(DateTimeOffset.UtcNow, 0, "Nontrackable status");

    await request.UpdateStatusAsync(trackableStatus);
    await request.UpdateStatusAsync(nonTrackableStatus);

    var statuses = await request.GetStatusesAsync();

    Assert.Single(statuses);
    Assert.Equal(trackableStatus, statuses.Single());
}

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

النسخ اللاحقة والإصدارات الضمنية أقل بشاعة بكثير

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

public async Task<StatusUpdate[]> GetStatusesAsync()
{
    int statusUpdatesCount = await Contract.GetFunction("getStatusUpdatesCount").CallAsync<int>();
    var getStatusUpdate = Contract.GetFunction("getStatusUpdate");
    var tasks = Enumerable.Range(0, statusUpdatesCount).Select(async i =>
    {
        var statusUpdate = await getStatusUpdate.CallDeserializingToObjectAsync<StatusUpdateStruct>(i);
        return new StatusUpdate(XDateTime.UtcOffsetFromTicks(statusUpdate.UpdateDate), statusUpdate.StatusCode, statusUpdate.Note);
    });

    return await Task.WhenAll(tasks);
}

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

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


لذلك أعتقد أن النسخة الضمنية ليست بأي حال من الأحوال في اللغات الضمنية مثل C #.
في Rust بقواعدها التي لا تسمح لك حتى بتحويل u8 ضمنيًا إلى i32 سيكون الأمر أكثر إرباكًا.

Pzixel نعم ، يبدو الخيار الثاني كواحد من أكثر الخيارات المفضلة. لقد استخدمت async/await في C # أيضًا ، لكن ليس كثيرًا ، نظرًا لأنني لم أبرمج بشكل أساسي في C # منذ عدة سنوات حتى الآن. بالنسبة للأسبقية ، فإن await (future?) أكثر طبيعية بالنسبة لي.

rpjohnst تعجبني فكرة عامل ما بعد الإصلاح ، لكنني قلق أيضًا بشأن إمكانية القراءة والافتراضات التي struct اسمه await .

إمكانية التمييز بين بداية المهمة والمهمة المنتظرة أمر مهم حقًا.

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

سيبدو مثالك كالتالي:

pub async fn get_statuses() -> Vec<StatusUpdate> {
    // get_status_updates is also an `async fn`, but calling it works just like any other call:
    let count = get_status_updates();

    let mut tasks = vec![];
    for i in 0..count {
        // Here is where task *construction* becomes explicit, as an async block:
        task.push(async {
            // Again, simply *calling* get_status_update looks just like a sync call:
            let status_update: StatusUpdateStruct = get_status_update(i).deserialize();
            StatusUpdate::new(utc_from_ticks(status_update.update_date), status_update.status_code, status_update.note))
        });
    }

    // And finally, launching the explicitly-constructed futures is also explicit, while awaiting the result is implicit:
    join_all(&tasks[..])
}

هذا ما قصدته "لكي ينجح هذا ، يجب أيضًا توضيح فعل بناء المستقبل". إنه مشابه جدًا للعمل مع سلاسل الرسائل في رمز المزامنة - استدعاء دالة تنتظرها دائمًا حتى تكتمل قبل استئناف المتصل ، وهناك أدوات منفصلة لإدخال التزامن. على سبيل المثال ، تتوافق عمليات الإغلاق و thread::spawn / join مع الكتل غير المتزامنة و join_all / select / إلخ.

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

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

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

var a = await fooAsync(); // awaiting first task
var b = barAsync(); //running second task
var c = await bazAsync(); // awaiting third task
if (c.IsSomeCondition && !b.Status = TaskStatus.RanToCompletion) // if some condition is true and b is still running
{
   var firstFinishedTask = await Task.Any(b, Task.Delay(5000)); // waiting for 5 more seconds;
   if (firstFinishedTask != b) // our task is timeouted
      throw new Exception(); // doing something
   // more logic here
}
else
{
   // more logic here
}

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

  1. بادئ ذي بدء ، عليك كتابة بعض التعليمات البرمجية القذرة لمحاكاة هذا السلوك.
  2. الآن يجب أن تتوقع RLS و IDEs أن تكون قيمتنا إما Future<T> أو تنتظر T نفسها. إنها ليست مشكلة في الكلمات الرئيسية - فهي موجودة ، فالنتيجة هي T ، وإلا فهي Future<T>
  3. يجعل من الصعب فهم الكود. في مثالك ، لا أرى سبب مقاطعة التنفيذ عند السطر get_status_updates ، لكنه لا يحدث على get_status_update . إنهم متشابهون تمامًا مع بعضهم البعض. لذلك إما أنها لا تعمل بالطريقة التي كانت بها الشفرة الأصلية أو أنها معقدة للغاية لدرجة أنني لا أستطيع رؤيتها حتى عندما أكون على دراية بالموضوع. كلا الخيارين لا يجعلان هذا الخيار معروفًا.

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

نعم ، هذا ما قصدته بعبارة "هذا يجعل رؤية نقاط التعليق أكثر صعوبة." إذا قرأت سلسلة الروابط الداخلية ، فقد قدمت حجة حول سبب عدم كون هذه المشكلة كبيرة. لست مضطرًا إلى كتابة أي رمز جديد ، ما عليك سوى وضع التعليقات التوضيحية في مكان مختلف (كتل async بدلاً من تعبيرات await ed). لا تواجه IDEs مشكلة في تحديد النوع (دائمًا ما يكون T للمكالمات الوظيفية و Future<Output=T> async للكتل

سأشير أيضًا إلى أن فهمك ربما يكون خاطئًا بغض النظر عن النحو. لا تقوم وظائف Rust's async بتشغيل أي رمز على الإطلاق حتى يتم انتظارهم بطريقة ما ، لذلك فإن شيكك b.Status != TaskStatus.RanToCompletion سينجح دائمًا. تمت مناقشة هذا أيضًا حتى الموت في موضوع RFC ، إذا كنت مهتمًا بالسبب في أنه يعمل بهذه الطريقة.

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

لا يقطع التنفيذ في كلا المكانين. المفتاح هو أن كتل async لا تعمل حتى يتم انتظارها ، لأن هذا ينطبق على جميع العقود الآجلة في Rust ، كما وصفت أعلاه. في المثال الخاص بي ، مكالمات get_statuses (وبالتالي تنتظر) get_status_updates ، ثم في الحلقة تقوم ببناء (ولكن لا تنتظر) count العقود الآجلة ، ثم تستدعي (وبالتالي تنتظر ) join_all ، وعند هذه النقطة قم باستدعاء (وبالتالي تنتظر) get_status_update .

الاختلاف الوحيد مع مثالك هو متى يبدأ سريان العقود الآجلة بالضبط - في عقلك ، يكون ذلك أثناء الحلقة ؛ في عملي ، يكون خلال join_all . لكن هذا جزء أساسي من كيفية عمل عقود Rust الآجلة ، وليس له علاقة بالصيغة الضمنية أو حتى بـ async / await على الإطلاق.

سأشير أيضًا إلى أن فهمك ربما يكون خاطئًا بغض النظر عن النحو. لا تعمل وظائف Rust غير المتزامنة على تشغيل أي رمز على الإطلاق حتى يتم انتظارها بطريقة ما ، لذا فإن فحص b.Status! = TaskStatus.RanToCompletion الخاص بك سوف يمر دائمًا.

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

var a = await fooAsync(); // awaiting first task
var b = Task.Run(() => barAsync()); //running background task somehow
// the rest of the method is the same

لقد خطرت لي فكرتك حول مكعبات async وكما أرى أنها نفس الوحش ، لكن مع المزيد من العيوب. في الاقتراح الأصلي ، يتم إقران كل مهمة غير متزامنة بـ await . مع كتل async سيتم إقران كل مهمة بكتلة async عند نقطة البناء ، لذلك نحن في نفس الموقف تقريبًا كما كان من قبل (علاقة 1: 1) ، ولكن حتى أسوأ قليلاً ، لأنها تشعر غير طبيعي ، ويصعب فهمه ، لأن سلوك موقع الاتصال يصبح معتمدًا على السياق. مع الانتظار يمكنني رؤية let a = foo() أو let b = await foo() وأعرف أن هذه المهمة قد تم إنشاؤها أو إنشائها للتو وانتظارها. إذا رأيت let a = foo() مع كتل async ، يجب أن أبحث عما إذا كان هناك بعض async أعلاه ، إذا فهمتك بشكل صحيح ، لأنه في هذه الحالة

pub async fn get_statuses() -> Vec<StatusUpdate> {
    // get_status_updates is also an `async fn`, but calling it works just like any other call:
    let count = get_status_updates();

    let mut tasks = vec![];
    for i in 0..count {
        // Here is where task *construction* becomes explicit, as an async block:
        task.push(async {
            // Again, simply *calling* get_status_update looks just like a sync call:
            let status_update: StatusUpdateStruct = get_status_update(i).deserialize();
            StatusUpdate::new(utc_from_ticks(status_update.update_date), status_update.status_code, status_update.note))
        });
    }

    // And finally, launching the explicitly-constructed futures is also explicit, while awaiting the result is implicit:
    join_all(&tasks[..])
}

نحن ننتظر جميع المهام مرة واحدة أثناء وجودنا هنا

pub async fn get_statuses() -> Vec<StatusUpdate> {
    // get_status_updates is also an `async fn`, but calling it works just like any other call:
    let count = get_status_updates();

    let mut tasks = vec![];
    for i in 0..count {
        // Isn't "just a construction" anymore
        task.push({
            let status_update: StatusUpdateStruct = get_status_update(i).deserialize();
            StatusUpdate::new(utc_from_ticks(status_update.update_date), status_update.status_code, status_update.note))
        });
    }
    tasks 
}

نحن ننفذهم واحدًا واحدًا.

وبالتالي لا أستطيع أن أقول ما هو السلوك الدقيق لهذا الجزء:

let status_update: StatusUpdateStruct = get_status_update(i).deserialize();
StatusUpdate::new(utc_from_ticks(status_update.update_date), status_update.status_code, status_update.note))

بدون المزيد من السياق.

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

يصبح سلوك موقع الاستدعاء يعتمد على السياق

هذا صحيح بالفعل مع رمز المزامنة العادية وعمليات الإغلاق. فمثلا:

// Construct a closure, delaying `do_something_synchronous()`:
task.push(|| {
    let data = do_something_synchronous();
    StatusUpdate { data }
});

ضد

// Execute a block, immediately running `do_something_synchronous()`:
task.push({
    let data = do_something_synchronous();
    StatusUpdate { data }
});

هناك شيء آخر يجب ملاحظته من اقتراح الانتظار الضمني الكامل وهو أنه لا يمكنك استدعاء async fn s من سياقات بخلاف async . هذا يعني أن صيغة استدعاء الوظيفة some_function(arg1, arg2, etc) تعمل دائمًا على تشغيل جسم some_function حتى اكتماله قبل أن يستمر المتصل ، بغض النظر عما إذا كان some_function هو async . لذا فإن الدخول إلى سياق async يتم تمييزه دائمًا بشكل صريح ، ويكون بناء جملة استدعاء الوظيفة في الواقع أكثر اتساقًا.

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

// Postfix await-as-a-keyword. Looks as if we were accessing a Result<_, _> field,
// unless await is syntax-highlighted
first().await?.second().await?.third().await?
// Macro with method syntax. A few more symbols, but clearly a macro invocation that
// can affect control flow
first().await!()?.second().await!()?.third().await!()?

توجد مكتبة من عالم Scala تعمل على تبسيط التراكيب الأحادية: http://monadless.io

ربما تكون بعض الأفكار مثيرة للاهتمام لراست.

اقتباس من المستندات:

تدعم معظم اللغات السائدة البرمجة غير المتزامنة باستخدام المصطلح غير المتزامن / انتظار أو يتم تنفيذها (على سبيل المثال F # ، C # / VB ، Javascript ، Python ، Swift). على الرغم من كونه مفيدًا ، إلا أنه عادةً ما يرتبط غير المتزامن / الانتظار بوحدة أحادية معينة تمثل حسابات غير متزامنة (مهمة ، مستقبل ، إلخ).

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

بالنظر إلى monad M ، يستخدم التعميم مفهوم رفع القيم العادية إلى monad ( T => M[T] ) وإلغاء رفع القيم من مثيل monad ( M[T] => T ). > مثال على الاستخدام:

lift {
  val a = unlift(callServiceA())
  val b = unlift(callServiceB(a))
  val c = unlift(callServiceC(b))
  (a, c)
}

لاحظ أن المصعد يتوافق مع عدم التزامن وعدم الارتياح في الانتظار.

هذا صحيح بالفعل مع رمز المزامنة العادية وعمليات الإغلاق. فمثلا:

أرى عدة اختلافات هنا:

  1. سياق Lambda أمر لا مفر منه ، ولكنه ليس لـ await . مع await ليس لدينا سياق ، مع async لدينا سياق. الأول يفوز ، لأنه يوفر نفس الميزات ، لكنه يتطلب معرفة أقل عن الكود.
  2. تميل Lambdas إلى أن تكون قصيرة ، عدة سطور على الأكثر لذلك نرى الجسم بالكامل في وقت واحد وبسيط. قد تكون وظائف async كبيرة جدًا (كبيرة مثل الوظائف العادية) ومعقدة.
  3. نادرًا ما يتم تداخل Lambdas (باستثناء مكالمات then ، لكنها await مقترحة) ، يتم تضمين كتل async بشكل متكرر.

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

حسنًا ، لم ألاحظ ذلك. لا يبدو الأمر جيدًا ، لأنك في ممارستي غالبًا ما تريد تشغيل غير متزامن من سياق غير متزامن. في C # async هي مجرد كلمة أساسية تسمح للمجمع بإعادة كتابة جسم الوظيفة ، ولا تؤثر على واجهة الوظيفة بأي شكل من الأشكال ، لذا فإن async Task<Foo> و Task<Foo> قابلة للتبادل تمامًا ، وهي يفصل التنفيذ وواجهة برمجة التطبيقات.

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

هناك اعتبار آخر لصالح await وهو كيفية عمله بلغة شائعة أخرى (كما لاحظ fdietze ). يسهل الترحيل من لغة أخرى مثل C # / TypeScript / JS / Python ، وبالتالي فهو نهج أفضل من حيث جذب أشخاص جدد.

أرى العديد من الاختلافات هنا

يجب أن تدرك أيضًا أن RFC الرئيسي يحتوي بالفعل على كتل async ، مع نفس دلالات الإصدار الضمني ، إذن.

لا يبدو الأمر جيدًا ، لأنك في ممارستي غالبًا ما تريد تشغيل غير متزامن من سياق غير متزامن.

هذه ليست قضية. يمكنك الاستمرار في استخدام async القطع في غير async سياقات (وهذا أمر جيد لأنها تقييم لمجرد F: Future كما هو الحال دائما)، ويمكنك تزال تفرخ أو كتلة على العقود الآجلة باستخدام نفس واجهة برمجة التطبيقات تمامًا كما كان من قبل.

لا يمكنك استدعاء async fn s ، ولكن بدلاً من ذلك ، قم بلف المكالمة إليهم في async block- كما تفعل بغض النظر عن السياق الذي تتواجد فيه ، إذا كنت تريد F: Future للخروج منه.

غير المتزامن هو مجرد كلمة رئيسية تسمح للمجمع بإعادة كتابة جسم الوظيفة ، ولا يؤثر على واجهة الوظيفة بأي شكل من الأشكال

نعم ، هذا فرق مشروع بين العروض. تم تغطيته أيضًا في الخيط الداخلي. يمكن القول أن وجود واجهات مختلفة للاثنين مفيد لأنه يوضح لك أن إصدار async fn لن يقوم بتشغيل أي كود كجزء من الإنشاء ، بينما الإصدار -> impl Future قد يبدأ طلبًا قبل إعطائك. a F: Future . كما أنه يجعل async fn s أكثر اتساقًا مع fn s العادي ، في ذلك استدعاء شيء تم إعلانه على أنه -> T سيمنحك دائمًا T ، بغض النظر عما إذا كان async .

(يجب عليك أيضا أن نلاحظ أنه في الصدأ لا يزال هناك قفزة تماما بين async fn و Future نسخة -returning، كما هو موضح في RFC، و async fn نسخة لا يذكر Future في أي مكان في توقيعه ؛ والإصدار اليدوي يتطلب impl Trait ، والذي يحمل معه بعض المشاكل المتعلقة بمدى الحياة. هذا ، في الواقع ، جزء من الدافع لـ async fn لتبدأ به.)

يسهل الترحيل من لغة أخرى مثل C # / TypeScript / JS / Python

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

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

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

fn foo(&self) -> Future<T> {
   self.myService.foo()
}

وبعد ذلك تريد فقط إضافة بعض التسجيل

async fn foo(&self) -> T {
   let result = await self.myService.foo();
   self.logger.log("foo executed with result {}.", result);
   result
}

ويصبح تغييرًا جذريًا. قف؟

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

إنها ميزة لأي بنية await ، await foo / foo await / foo@ / foo.await / ... بمجرد أن تفهم أنها نفس الشيء ، الاختلاف الوحيد هو أنك تضعه قبل / بعد أو يكون لديك علامة سيجيل بدلاً من كلمة رئيسية.

يجب أن تلاحظ أيضًا أنه في Rust لا تزال هناك قفزة كبيرة بين الإصدار غير المتزامن fn والإصدار المستقبلي ، كما هو موضح في RFC

أنا أعلم ذلك وهو يزعجني كثيرًا.

ويصبح تغييرًا جذريًا.

يمكنك التغلب على ذلك عن طريق إرجاع كتلة async . تحت اقتراح الانتظار الضمني ، يبدو مثالك كالتالي:

fn foo(&self) -> impl Future<Output = T> { // Note: you never could return `Future<T>`...
    async { self.my_service.foo() } // ...and under the proposal you couldn't call `foo` outside of `async` either.
}

ومع التسجيل:

fn foo(&self) -> impl Future<Output = T> {
    async {
        let result = self.my_service.foo();
        self.logger.log("foo executed with result {}.", result);
        result
    }
}

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

حسنًا ، هذا يبدو معقولًا.

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

يبدو هذا الرمز جيدًا تمامًا إلا أنني لا أستطيع معرفة ما إذا كان هناك طلب ما إذا كان غير متزامن أو متزامن. أعتقد أنها معلومات مهمة. فمثلا:

fn foo(&self) -> impl Future<Output = T> {
    async {
        let result = self.my_service.foo();
        self.logger.log("foo executed with result {}.", result);
        let bars: Vec<Bar> = Vec::new();
        for i in 0..100 {
           bars.push(self.my_other_service.bar(i, result));
        }
        result
    }
}

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

image

كما ترون ، لقد لاحظت بسهولة أن لدينا حلقة انتظار هنا وطلبت تغييرها. عندما تم تنفيذ التغيير ، حصلنا على تسريع تحميل الصفحة 3x. بدون await يمكنني بسهولة أن أتغاضى عن هذا السلوك السيئ.

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

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

ومع ذلك ، تم تقديم await للغات السائدة عن طريق C # ، والتي ربما تكون إحدى اللغات التي كان يُنظر فيها إلى usabilty على أنها ذات أهمية قصوى . في C # ، تتم الإشارة إلى المكالمات غير المتزامنة ليس فقط بواسطة الكلمة الأساسية await ، ولكن أيضًا بواسطة اللاحقة Async لاستدعاءات الطريقة. ميزة اللغة الأخرى الأكثر مشاركة مع await ، yield return هي أيضًا مرئية بشكل بارز في الكود.

لماذا هذا؟ رأيي هو أن المولدات والمكالمات غير المتزامنة هي تركيبات قوية للغاية للسماح لها بالمرور دون أن يلاحظها أحد في الكود. يوجد تسلسل هرمي لمشغلي التحكم في التدفق:

  • التنفيذ المتسلسل للبيانات (ضمني)
  • استدعاءات الوظيفة / الطريقة (واضحة تمامًا ، قارن مع على سبيل المثال Pascal حيث لا يوجد فرق في موقع الاستدعاء بين دالة خالية ومتغير)
  • goto (حسنًا ، إنه ليس تسلسلًا هرميًا صارمًا)
  • المولدات (يميل yield return إلى التميز)
  • لاحقة await + Async

لاحظ كيف ينتقلون أيضًا من الأقل إلى الإسهاب ، وفقًا لتعبيراتهم أو قوتهم.

بالطبع ، اتبعت اللغات الأخرى مناهج مختلفة. استمرار النظام (مثل call/cc ، والذي لا يختلف كثيرًا عن await ) أو لا تحتوي وحدات الماكرو على بناء جملة لإظهار ما تتصل به. بالنسبة لوحدات الماكرو ، اتخذ Rust منهجًا لتسهيل رؤيتها.

لذلك أود أن أزعم أن وجود بنية أقل ليس مرغوبًا في حد ذاته (هناك لغات مثل APL أو Perl لذلك) ، ولا يجب أن يكون بناء الجملة هذا مجرد نموذج معياري ، وله دور مهم في سهولة القراءة.

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


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

حفظ ثلاثة أحرف لا يستحق كل هذا العناء.

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

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


rpjohnst أعتقد أن

من الضروري معرفة ما إذا كان bar هو وظيفة المزامنة أو غير المتزامن.

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

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

لذلك لا أعتقد أن طلب await هو في الواقع كل ذلك مهم للقبض على هذه التحسينات. الحلقات هي بالفعل دائمًا أماكن جيدة للبحث عن التحسين ، و async fn s هي بالفعل مؤشر جيد على أنه يمكنك الحصول على بعض التزامن IO الرخيص. إذا وجدت نفسك تفوت هذه الفرص ، يمكنك حتى كتابة Clippy lint لـ "مكالمة غير متزامنة في حلقة" تقوم بتشغيلها من حين لآخر. سيكون من الرائع أن يكون لديك نسق مشابه للشفرة المتزامنة أيضًا!

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

أعتقد أن اقتراحك البديل قد يحظى بقبول أفضل قليلاً إذا تم تقديمه على أنه "غير متزامن صريح" بدلاً من "انتظار ضمني" :-)

يسمى الخيط "البناء المستقبلي الصريح ، الانتظار الضمني" ، ولكن يبدو أن الاسم الأخير عالق. : ص

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

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

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

جانب واحد هو اعتبار جيد. من ناحية أخرى ، يمكنك بسهولة فصل الإنشاء المستقبلي عن التشغيل المستقبلي. أعني أنه إذا أعاد لك foo بعض التجريد الذي يسمح لك بعد ذلك بالاتصال بـ run والحصول على بعض النتائج ، فإنها لا تجعل سلة المهملات عديمة الفائدة foo لا تفعل شيئًا ، شيء مفيد: يقوم ببناء كائن ما يمكنك استدعاء عمليات لاحقًا. لا يجعل الأمر مختلفًا. الطريقة foo التي نسميها هي مجرد صندوق أسود ونرى توقيعه Future<Output=T> ويعيد بالفعل مستقبلًا. لذلك نحن صراحة await عندما نريد القيام بذلك.

يسمى الخيط "البناء المستقبلي الصريح ، الانتظار الضمني" ، ولكن يبدو أن الاسم الأخير عالق. : ص

أنا شخصياً أعتقد أن البديل الأفضل هو "الانتظار الصريح غير المتزامن الصريح" :)


ملاحظة

لقد تأثرت أيضًا بفكرة الليلة: هل حاولت التواصل مع C # LDM؟ على سبيل المثال، الرجال مثلHaloFour،gafter أوCyrusNajmabadi. قد تكون فكرة جيدة حقًا أن تسألهم عن سبب أخذهم لبناء الجملة. أقترح أن أسأل الرجال من لغات أخرى أيضًا ولكني لا أعرفهم فقط :) أنا متأكد من أنهم قد خاضوا مناقشات متعددة حول البنية الحالية ويمكنهم بالفعل مناقشتها كثيرًا وقد يكون لديهم بعض الأفكار المفيدة.

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

أنا شخصياً أعتقد أن البديل الأفضل هو "الانتظار الصريح غير المتزامن الصريح" :)

الاقتراح الرئيسي ليس " ضمني " ، لأنه لا يمكنك معرفة مكان تقديم عدم التزامن في لمح البصر. قد يقوم أي استدعاء دالة غير مُعلَّق ببناء مستقبل دون انتظاره ، على الرغم من أن Future لا يظهر في أي مكان في توقيعه.

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

هل حاولت التواصل مع C # LDM؟

مؤلف RFC الرئيسي فعل. النقطة الرئيسية التي خرجت منها ، على حد ما أتذكر ، كانت قرار عدم تضمين Future في توقيع async fn s. في C # ، يمكنك استبدال Task بأنواع أخرى للحصول على بعض التحكم في كيفية تشغيل الوظيفة. لكن في Rust ، ليس لدينا (ولن نمتلك) أي آلية من هذا القبيل - كل العقود الآجلة ستمر بسمة واحدة ، لذلك ليست هناك حاجة لكتابة هذه السمة في كل مرة.

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

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

تجربة رائعة ، لم أكن على علم بها. يسعدني سماع أنك قمت بهذا العمل الضخم. أحسنت صنعًا 👍

لقد تأثرت أيضًا بفكرة الليلة: هل حاولت التواصل مع C # LDM؟ على سبيل المثال، الرجال مثلHaloFour،gafter أوCyrusNajmabadi. قد تكون فكرة جيدة حقًا أن تسألهم عن سبب أخذهم لبناء الجملة.

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

بخصوص بناء الجملة await (قد يكون هذا غبيًا تمامًا ، فلا تتردد في الصراخ في وجهي ؛ أنا مستجد في البرمجة غير المتزامن وليس لدي أي فكرة عما أتحدث عنه):

بدلاً من استخدام كلمة "انتظار" ، لا يمكننا تقديم رمز / عامل تشغيل مشابه لـ ? . على سبيل المثال ، يمكن أن يكون # أو @ أو شيئًا آخر غير مستخدم حاليًا.

على سبيل المثال ، إذا كان عامل تشغيل postfix:

let stuff = func()#?;
let chain = blah1()?.blah2()#.blah3()#?;

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

لست متأكدًا مما إذا كانت postfix هي المكان المناسب لذلك ، لكنها شعرت بهذه الطريقة بسبب الأسبقية. كبادئة:

let stuff = #func()?;

أو تحقق حتى:

let stuff = func#()?; // :-D :-D

هل تم مناقشة هذا من قبل؟

(أدرك أن هذا النوع يبدأ في الاقتراب من بناء جملة "مزيج عشوائي من الرموز" التي تشتهر بها لغة Perl ... :-D)

rayvector https://github.com/rust-lang/rust/issues/50547#issuecomment -388108875 ، البديل الخامس.

CyrusNajmabadi شكرا await الطريقة الحالية أو ربما يجب أن يطبق أسلوبه الخاص. هل بناء الجملة الحالي نوع من "الإرث" الذي ترغب في تغييره بطريقة ما أو يناسب C # الأفضل وهو الخيار الأفضل للغات الجديدة أيضًا؟

الاعتبار الرئيسي مقابل بناء جملة C # هو أسبقية عامل التشغيل await foo? يجب أن تنتظر أولاً ثم تقييم عامل التشغيل ? بالإضافة إلى الاختلاف الذي لا يتم تشغيله على عكس C # في سلسلة المتصل حتى await ، ولكن لا يبدأ على الإطلاق ، فإن samy way الحالي مقتطف الشفرة لا يقوم بتشغيل عمليات التحقق من السلبية حتى يتم استدعاء GetEnumerator للمرة الأولى:

IEnumerable<int> GetInts(int n)
{
   if (n < 0)
      throw new InvalidArgumentException(nameof(n));
   for (int i = 0; i <= n; i++)
      yield return i;
}

أكثر تفصيلاً في تعليقي الأول والمناقشة اللاحقة.

@ Pzixel أوه ، أعتقد أني فاتني ذلك عندما كنت أقفز عبر هذا الخيط سابقًا ...

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

هل هناك أي حجج جيدة مع / ضد؟

rayvector لقد جادلت قليلاً هنا لصالح المزيد من النحو المطول. ومن الأسباب الذي ذكرته:

صيغة "مزيج عشوائي من الرموز" التي تشتهر بها Perl

للتوضيح ، لا أعتقد أن await!(f)? قيد التشغيل بالفعل من أجل الصيغة النهائية ، فقد تم اختياره على وجه التحديد لأنه طريقة قوية لعدم الالتزام بأي خيار معين. فيما يلي الصيغ (بما في ذلك عامل التشغيل ? ) التي أعتقد أنها لا تزال "قيد التشغيل":

  • await f?
  • await? f
  • await { f }?
  • await(f)?
  • (await f)?
  • f.await?

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

أنا أصوت إما لعامل انتظار postfix بسيط (على سبيل المثال ~ ) أو الكلمة الأساسية بدون أقواس وأعلى أسبقية.

لقد كنت أقرأ من خلال هذا الموضوع ، وأود أن أقترح ما يلي:

  • await f? بتقييم عامل التشغيل ? أولاً ، ثم ينتظر المستقبل الناتج.
  • ينتظر (await f)? المستقبل أولاً ، ثم يقيّم عامل التشغيل ? مقابل النتيجة (بسبب أسبقية عامل Rust العادي)
  • await? f متاح كسكر نحوي لـ `(wait f) ؟. أعتقد أن عبارة "إرجاع نتيجة في المستقبل" ستكون حالة شائعة جدًا ، لذا فإن التركيب اللغوي المخصص له معنى كبير.

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

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

السؤال الرئيسي هو ما الخيار من الخيارات المدرجة التي تعتقد أنها تناسب لغة Rust الحالية بشكل أفضل ،

إن سؤال مصمم C # عن أفضل ما يناسب لغة الصدأ هو ... مثير للاهتمام :)

لا أشعر أنني مؤهل لاتخاذ مثل هذا القرار. أنا أحب الصدأ وأشتغل به. لكنها ليست لغة أستخدمها يومًا بعد يوم. كما أنني لم أجذبه بعمق في نفسي. على هذا النحو ، لا أعتقد أنني مؤهل لتقديم أي ادعاءات حول الخيارات المناسبة لهذه اللغة هنا. تريد أن تسألني عن Go / TypeScript / C # / VB / C ++. بالتأكيد ، سأشعر براحة أكبر. لكن الصدأ هو خارج نطاق خبرتي لدرجة تجعلني أشعر بالراحة مع أي أفكار من هذا القبيل.

الاعتبار الرئيسي مقابل بناء جملة C # هو أسبقية عامل التشغيل await foo?

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

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

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

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

-

بالإضافة إلى الاختلاف الذي لا يعمل على عكس تنفيذ C # في سلسلة رسائل المتصل حتى الانتظار الأول ، ولكنه لا يبدأ على الإطلاق ، لا يقوم مقتطف الشفرة الحالي بطريقة مماثلة بتشغيل فحوصات السلبية حتى يتم استدعاء GetEnumerator لأول مرة:

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

ج #
باطل SomeEnumerator (X args)
{
// تحقق من صحة Args ، قم بعمل متزامن.
إرجاع SomeEnumeratorImpl (args) ؛
}

إفراغ SomeEnumeratorImpl (X args)
{
// ...
أثمر
// ...
}

People have to write this *all the time* because of the unexpected behavior that the iterator pattern has.  I think we were worried about expensive work happening initially.  However, in practice, that doesn't seem to happen, and people def think about the work as happening when the call happens, and the yields themselves happening when you actually finally start streaming the elements.

Linq (which is the poster child for this feature) needs to do this *everywhere*, this highly diminishing this choice.

For ```await``` i think things are *much* better.  We use 'async/await' a ton ourselves, and i don't think i've ever once said "man... i wish that it wasn't running the code synchronously up to the first 'await'".  It simply makes sense given what the feature is.  The feature is literally "run the code up to await points, then 'yield', then resume once the work you're yielding on completes".  it would be super weird to not have these semantics to me since it is precisely the 'awaits' that are dictating flow, so why would anything be different prior to hitting the first await.

Also... how do things then work if you have something like this:

```c#
async Task FooAsync()
{
    if (cond)
    {
        // only await in method
        await ...
    }
} 

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

تنتظر؟ f متاح كسكر نحوي لـ "(انتظار f) ؟. أعتقد أن عبارة "إرجاع نتيجة في المستقبل" ستكون حالة شائعة جدًا ، لذا فإن التركيب اللغوي المخصص له معنى كبير.

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

شيء واحد نعرفه من C # هو أن حدس الناس حول الأسبقية مرتبط بمسافة بيضاء. لذلك إذا كان لديك "انتظر x؟" ثم يبدو فورًا أن أسبقية await أقل من ? لأن ? يحيط التعبير. إذا تم تحليل ما ورد أعلاه بالفعل على أنه (await x)? فسيكون ذلك مفاجئًا لجمهورنا.

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

إذا لم يتم تشغيل "التنفيذ في سلسلة رسائل المتصل حتى الانتظار الأول" فماذا يحدث هنا؟

لا يحدث أي شيء حتى ينتظر المتصل قيمة الإرجاع FooAsync ، وعند هذه النقطة FooAsync الصورة يدير الجسم حتى إما await أو تقوم بإرجاع.

يعمل بهذه الطريقة لأن Rust Future s مدفوعة بالاستقصاءات ، ومخصصة للمكدس ، وغير قابلة للنقل بعد الاستدعاء الأول إلى poll . يجب أن يحصل المتصل على فرصة لنقلها إلى مكانها - في كومة المستوى الأعلى Future s ، أو بالقيمة الثانوية داخل أحد الوالدين Future ، غالبًا في "إطار المكدس" من استدعاء async fn تنفيذ أي كود.

هذا يعني أننا عالقون إما مع أ) دلالات تشبه C # ، حيث لا يتم تشغيل أي رمز عند الاستدعاء ، أو ب) دلالات تشبه Kotlin coroutine ، حيث يتم استدعاء الوظيفة أيضًا على الفور وبشكل ضمني (مع إغلاق يشبه async { .. } كتل

أنا أفضل نوعًا ما هذا الأخير ، لأنه يتجنب المشكلة التي ذكرتها مع مولدات C # ، ويتجنب أيضًا سؤال أسبقية المشغل تمامًا.

CyrusNajmabadi في Rust ، لا يعمل عادةً Future حتى يتم إنتاجه على أنه Task (يشبه إلى حد كبير F # Async ):

let bar = foo();

في هذه الحالة ، يقوم foo() بإرجاع Future ، لكنه على الأرجح لا يفعل شيئًا في الواقع. يجب أن تفرخها يدويًا (وهو مشابه أيضًا لـ F # Async ):

tokio::run(bar);

عندما يتم إنتاجها ، سيتم تشغيل Future . نظرًا لأن هذا هو السلوك الافتراضي Future ، فسيكون من الأفضل عدم التزامن / الانتظار في Rust لعدم تشغيل أي رمز حتى يتم إنتاجه.

من الواضح أن الوضع مختلف في C # ، لأنه في C # عندما تتصل بـ foo() يبدأ فورًا في تشغيل Task ، لذلك من المنطقي في C # تشغيل الكود حتى أول await .

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

إذا اتصلت بـ FooAsync() فلن تفعل شيئًا ، فلن يتم تشغيل أي كود. ثم عندما تفرخه ، سيتم تشغيل الكود بشكل متزامن ، ولن يتم تشغيل await أبدًا ، وبالتالي يُرجع على الفور () (وهو إصدار Rust void )

بعبارة أخرى ، الأمر ليس "التنفيذ لا يعمل في سلسلة رسائل المتصل حتى الانتظار الأول" ، بل "التنفيذ لا يتم حتى يتم إنتاجه بشكل صريح (مثل مع tokio::run )"

لا يحدث شيء حتى ينتظر المتصل القيمة المعادة لـ FooAsync ، وعند هذه النقطة يعمل جسم FooAsync حتى يتم انتظاره أو إرجاعه.

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

هذا يعني أننا عالقون إما مع أ) دلالات C # مثل المولد ، حيث لا يتم تشغيل أي رمز عند الاستدعاء ، أو ب) دلالات تشبه Kotlin coroutine ، حيث ينتظرها استدعاء الوظيفة أيضًا فورًا وضمنيًا (مع عدم تزامن الإغلاق {. .} من الكتل عندما تحتاج إلى تنفيذ متزامن).

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

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

أشعر بالعكس تماما. من خلال تجربتي مع JavaScript ، من الشائع جدًا أن تنسى استخدام await . في هذه الحالة ، سيستمر تشغيل Promise ، ولكن سيتم ابتلاع الأخطاء (أو تحدث أشياء غريبة أخرى).

باستخدام نمط Rust / Haskell / F # ، إما أن يتم تشغيل Future (مع معالجة الأخطاء بشكل صحيح) ، أو لا يعمل على الإطلاق. ثم تلاحظ أنه لا يعمل ، لذا تتحقق منه وتصلحه. أعتقد أن هذا يؤدي إلى رمز أكثر قوة.

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

في الحالات التي لا تريد فيها "فعل أي شيء فعليًا. عليك إنتاجه يدويًا" ، وجدنا أنه من الأنظف تصميم ذلك على أنه إرجاع شيء ينتج عنه مهام عند الطلب. أي شيء بسيط مثل Func<Task> .

أشعر بالعكس تماما. في تجربتي مع JavaScript ، من الشائع جدًا نسيان استخدام الانتظار.

يعمل C # لمحاولة التأكد من أنك إما تنتظر أو تستخدم المهمة بطريقة معقولة.

ولكن سيتم ابتلاع الأخطاء

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

ثم تلاحظ أنه لا يعمل ، لذا تتحقق منه وتصلحه.

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

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

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

أستطيع أن أفهم الرغبة في ارتكاب أخطاء مبكرة ، لكنني في حيرة من أمري: في أي موقف قد "ينتهي بك الأمر إلى عدم الالتفاف على Future

الطريقة التي يعمل بها Future s في Rust هي أنك تقوم بتكوين Future s معًا بطرق مختلفة (بما في ذلك غير المتزامن / انتظار ، بما في ذلك الدمج المتوازي ، وما إلى ذلك) ، ومن خلال القيام بذلك ، فإنه ينشئ واحد مصهور Future والذي يحتوي على جميع العناصر الفرعية- Future s. ثم في المستوى الأعلى من برنامجك ( main ) ثم تستخدم tokio::run (أو ما شابه) لنشره.

بصرف النظر عن تلك المكالمة الفردية tokio::run في main ، لن تفرخ عادةً Future s يدويًا ، بدلاً من ذلك تقوم فقط بتكوينها. ويتعامل التكوين بشكل طبيعي مع التبويض / معالجة الخطأ / الإلغاء / إلخ. بشكل صحيح.

أريد أيضًا أن أوضح شيئًا ما. عندما أقول شيئًا مثل:

لكن اتضح أنه لم يكن ذلك مرغوبًا في الواقع من الناحية العملية.

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

أستطيع أن أفهم الرغبة في ارتكاب أخطاء مبكرة ، لكنني في حيرة من أمري: تحت أي موقف قد "ينتهي بك الأمر إلى عدم الالتفاف على إنتاج المستقبل"؟

طوال الوقت :)

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

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

ألا يتم التعامل مع هذا فقط عن طريق الإلغاء ، رغم ذلك؟

ويتعامل التكوين بشكل طبيعي مع التبويض / معالجة الخطأ / الإلغاء / إلخ. بشكل صحيح.

يبدو ببساطة أن لدينا نموذجين مختلفين جدًا لتمثيل الأشياء. لا بأس :) من المفترض أن تؤخذ تفسيراتي في سياق النموذج الذي نختاره. قد لا تكون منطقية بالنسبة للنموذج الذي تختاره.

يبدو ببساطة أن لدينا نموذجين مختلفين جدًا لتمثيل الأشياء. لا بأس :) من المفترض أن تؤخذ تفسيراتي في سياق النموذج الذي نختاره. قد لا تكون منطقية بالنسبة للنموذج الذي تختاره.

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

ألا يتم التعامل مع هذا فقط عن طريق الإلغاء ، رغم ذلك؟

الإلغاء هو مفهوم متعامد لعدم التزامن (بالنسبة لنا). يتم استخدامها بشكل شائع معًا. لكن لا يستلزم أي منهما الآخر.

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

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

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

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

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

أنا شخصياً أعتقد أنه من الأفضل التعامل مع هذا الأمر من خلال المنطق المعتاد if/then/else :

async fn foo() {
    if some_condition {
        await!(bar());
    }
}

لكن كما تقول ، إنها مجرد وجهة نظر مختلفة تمامًا عن C #.

أنا شخصياً أعتقد أنه من الأفضل التعامل مع ذلك من خلال منطق if / then / else المعتاد:

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

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

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

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

من حيث الأسبقية ، قدمت بعض المعلومات حول كيفية تفكير C # في الأشياء. وآمل أن يكون مفيدا. اسمحوا لي أن أعرف إذا كنت تريد المزيد من المعلومات هناك.

CyrusNajmabadi نعم ، كانت await? foo هو الطريق الصحيح (على الرغم من أنني أحب أيضًا اقتراح "الصريح async ").

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

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

ما زلت أصوت لصالح await? future حتى لو بدا الأمر غير مألوف بعض الشيء. هل هناك سلبيات حقيقية في تأليفها؟

فيما يلي تحليل شامل آخر لإيجابيات وسلبيات عدم التزامن البارد (F #) مقابل الساخن (C # ، JS): http://tomasp.net/blog/async-csharp-differences.aspx

يوجد الآن RFC جديد لوحدات الماكرو postfix التي من شأنها أن تسمح بالتجربة مع postfix await بدون تغيير بناء الجملة المخصص: https://github.com/rust-lang/rfcs/pull/2442

await {} هو المفضل لدي هنا ، يذكرنا بـ unsafe {} بالإضافة إلى أنه يظهر الأسبقية.

let value = await { future }?;

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

let value = await {
   let val1 = future1;
   future2(val1)
}

لكنهم لا يستطيعون.

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

let value = await {
    let future = create_future();
    future
};

ويجب أن يكون نمطًا محبطًا

مبسط

let value = await { create_future() };

تقترح بيانًا حيث "يجب عدم تشجيع" أكثر من تعبير واحد. ألا ترى أي خطأ في ذلك؟

هل من الأفضل عمل نمط await (بصرف النظر عن ref إلخ)؟
شيء مثل:

let await n = bar();

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

وفقًا للصفحة الحالية من https://doc.rust-lang.org/nightly/std/task/index.html ، فإن تعديل المهمة يتكون من إعادة التصدير من libcore وإعادة التصدير لـ liballoc ، مما يجعل النتيجة قليلاً ... دون المستوى الأمثل. آمل أن يتم معالجة هذا بطريقة ما قبل أن يستقر.

ألقيت نظرة على الكود. ولدي بعض الاقتراحات:

  • [x] السمة UnsafePoll والتعداد Poll لهما أسماء متشابهة جدًا ، لكنهما غير مرتبطين. أقترح إعادة تسمية UnsafePoll ، على سبيل المثال إلى UnsafeTask .
  • [x] في صندوق العقود الآجلة ، تم تقسيم الكود إلى وحدات فرعية مختلفة. الآن ، يتم تجميع معظم الأكواد معًا في task.rs مما يجعل التنقل أكثر صعوبة. أقترح تقسيمها مرة أخرى.
  • [x] TaskObj#from_poll_task() له اسم غريب. أقترح تسميته new() بدلاً من ذلك
  • [x] يمكن أن يكون poll() TaskObj#poll_task مجرد poll() . يمكن تسمية الحقل المسمى poll poll_fn مما قد يوحي أيضًا بأنه مؤشر دالة
  • قد يتمكن Waker من استخدام نفس الإستراتيجية مثل TaskObj ووضع جدول vtable في المكدس. مجرد فكرة ، لا أعرف ما إذا كنا نريد ذلك. هل سيكون أسرع لأنه أقل مراوغة؟
  • [] dyn مستقر الآن في الإصدار التجريبي. من المحتمل أن يستخدم الرمز dyn حيثما ينطبق

يمكنني تقديم العلاقات العامة لهذه الأشياء أيضًا. cramertjaturon لا تتردد في التواصل معي عبر Discord لمناقشة التفاصيل.

ماذا عن مجرد إضافة طريقة await() لكل Future ؟

    /// just like and_then method
    let x = f.and_then(....);
    let x = f.await();

    await f?     =>   f()?.await()
    await? f     =>   f().await()?

/// with chain invoke.
let x = first().await().second().await()?.third().await()?
let x = first().await()?.second().await()?.third().await()?
let x = first()?.await()?.second().await()?.third().await()?

zengsai المشكلة هي أن await لا يعمل بالطريقة المعتادة . في الواقع ، ضع في اعتبارك ما ستفعله طريقة await عندما لا تكون في كتلة / وظيفة async . الأساليب لا تعرف في أي سياق يتم تنفيذها ، لذلك لا يمكن أن تسبب خطأ في الترجمة.

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

elszben أن المترجم يمكنه فعل ما يشاء لا يعني أنه يجب أن يفعل ما يشاء.

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

تحرير: تمت إزالة الوظيفة

لقد قمت بنقل هذا المنشور إلى RFC للعقود الآجلة. وصلة

هل شاهد أي شخص التفاعل بين async fn و #[must_use] ؟

إذا كان لديك async fn ، فإن استدعائه مباشرة لا يؤدي إلى تشغيل أي كود وإرجاع Future ؛ يبدو أن كل async fn يجب أن يحتوي على #[must_use] على النوع "الخارجي" impl Future ، لذلك لا يمكنك الاتصال بهم بدون القيام بشيء باستخدام Future .

علاوة على ذلك ، إذا قمت بإرفاق #[must_use] بـ async fn بنفسك ، فيبدو أن ذلك يجب أن ينطبق على إرجاع الوظيفة الداخلية . لذا ، إذا كتبت #[must_use] async fn foo() -> T { ... } ، فلا يمكنك كتابة await!(foo()) بدون فعل شيء مع نتيجة الانتظار.

هل نظر أي شخص إلى التفاعل بين عدم التزامن fn و # [must_use]؟

بالنسبة للآخرين المهتمين بهذه المناقشة ، راجع https://github.com/rust-lang/rust/issues/51560.

كنت أفكر في كيفية تنفيذ الوظائف غير المتزامنة وأدركت أن هذه الوظائف لا تدعم العودية أو العودية المتبادلة أيضًا.

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

@ warlord500 أنت تتجاهل تمامًا await .

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

تحرير : إذا كان لديك اختلاف في الرأي ، فالرجاء المشاركة! أنا فضولي لماذا تقول
لا ينبغي أن نسمح بتسلسل الانتظار عبر طريقة مثل بناء الجملة؟

@ warlord500 لأن فريق MS شارك تجربته عبر آلاف العملاء والملايين من المطورين. أنا أعرف ذلك بنفسي لأنني أكتب رمز غير متزامن / انتظار على أساس يومي ، ولا تريد ربطها مطلقًا. هنا اقتباس دقيق ، إذا كنت ترغب في ذلك:

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

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

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

أيضًا ، يمكنك نشر الرابط حيث حصلت على عرض الأسعار ،
شكرا لكم.

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

تنتظر أحيانًا الجزء الأهم من التعبير ، إذا كان التعبير المولِّد في المستقبل كذلك
الجزء الأكثر أهمية وتريد وضعه في الأعلى ، فلا يزال بإمكانك القيام بذلك إذا سمحنا بنمط ماكرو postfix بالإضافة إلى نمط الماكرو العادي

أيضًا ، يمكنك نشر الرابط حيث حصلت على عرض الأسعار ،
شكرا لكم.

لكن ... لكنك قلت أنك قرأت الموضوع كاملاً ... 😃

لكن ليس لدي مشكلة في مشاركتها: https://github.com/rust-lang/rust/issues/50547#issuecomment -388939886. أقترح عليك قراءة جميع منشورات Cyrus ، إنها تجربة حقيقية للنظام البيئي C # /. Net بالكامل ، إنها تجربة لا تقدر بثمن يمكن إعادة استخدامها بواسطة Rust.

تنتظر أحيانًا الجزء الأهم من التعبير

من الواضح أن الاقتباس يقول عكس ذلك 😄 وأنت تعلم ، لدي نفس الشعور بنفسي ، أكتب غير متزامن / أنتظر على أساس يومي.

هل لديك تجربة مع عدم التزامن / الانتظار؟ هل يمكنك مشاركتها بعد ذلك ، من فضلك؟

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

Pzixel أقدر لك مشاركة المعلومات حول تجربتك وخبرات الآخرين باستخدام async / await ، لكن يرجى احترام المساهمين الآخرين. لست بحاجة إلى انتقاد مستويات الخبرة للآخرين من أجل جعل النقاط الفنية الخاصة بك مسموعة.

ملاحظة المنسق: Pzixel الهجمات الشخصية على أعضاء المجتمع غير مسموح بها. لقد قمت بتحريره من تعليقك. لا تفعل ذلك مرة أخرى. إذا كانت لديك أسئلة حول سياسة الإشراف الخاصة بنا ، فيرجى المتابعة معنا على [email protected].

crabtw لم أنتقد أحداً. أعتذر عن أي إزعاج قد يحدث هنا.

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

لا يسمح بالاعتداءات الشخصية على أفراد المجتمع. لقد قمت بتحريره من تعليقك.

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

شكرا للإشارة. لقد أردت أن أذكر أنك لا يجب أن تأخذ أيًا مما أقول "إنجيل" :) Rust و C # لغتان مختلفتان مع مجتمعات ونماذج وتعابير مختلفة. يجب عليك تحديد أفضل الخيارات للغتك. آمل أن تكون كلماتي مفيدة ويمكن أن تعطي نظرة ثاقبة. لكن كن دائمًا منفتحًا على طرق مختلفة للقيام بالأشياء.

آمل أن تأتي بشيء رائع لراست. ثم يمكننا أن نرى ما فعلته وسرقته بلطف من أجل C # :)

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

ومع ذلك ، لا أعتقد أن هذا يشير إلى أي إجابة معينة بخصوص قيمة التسلسل مثل x.y().await!().z() .

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

withoutboats نعم ، هذا صحيح. أود أن أقتبس هذا الجزء مرة أخرى:

الاستثناء الوحيد لذلك ، من وجهة نظري ، هو؟

إذا كان هناك استثناء فقط ، فمن المنطقي إنشاء await? foo كاختصار لـ (await foo)? والحصول على أفضل ما في العالمين.

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

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

Matching lines: 139 Matching files: 10 Total files searched: 77

لدي 139 في انتظار 2743 sloc. ربما لا تكون صفقة كبيرة ، لكن أعتقد أنه يجب علينا اعتبار البديل غير المقوى كبديل أنظف وأفضل. ومع ذلك ، فإن ? هو الاستثناء الوحيد ، لذلك يمكننا بسهولة استخدام await foo بدون أقواس ، وتقديم صيغة خاصة لهذه الحالة الخاصة فقط. إنها ليست صفقة كبيرة ، ولكن يمكن أن توفر بعض الأقواس لمشروع LISP.

لقد قمت بإنشاء منشور مدونة حول سبب اعتقادي أن الوظائف غير المتزامنة يجب أن تستخدم نهج نوع الإرجاع الخارجي لتوقيعها. استمتع بالقراءة!

https://github.com/Major breakfast/rust-blog/blob/master/posts/2018-06-19-outer-return-type-approach.md

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

فيما يلي مصدر قلق إضافي حول أسلوب نوع الإرجاع الداخلي: كيف سيبدو بناء الجملة لـ Stream s ، عندما يتم تحديده؟ أعتقد أن async fn foo() -> impl Stream<Item = T> سيبدو لطيفًا ومتسقًا مع async fn foo() -> impl Future<Output = T> ، لكنه لن يعمل مع نهج نوع الإرجاع الداخلي. ولا أعتقد أننا سنرغب في تقديم كلمة رئيسية async_stream .

سيحتاجEkleog Stream إلى استخدام كلمة رئيسية مختلفة. لا يمكن استخدام async لأن impl Trait يعمل بالعكس. يمكن أن يضمن فقط تنفيذ سمات معينة ، لكن السمات نفسها تحتاج إلى أن يتم تنفيذها بالفعل على النوع الملموس الأساسي.

ومع ذلك ، سيكون نهج نوع الإرجاع الخارجي مفيدًا إذا أردنا يومًا ما إضافة وظائف مولد غير متزامن:

async_gen fn foo() -> impl AsyncGenerator<Yield = i32, Return = ()> { yield 1; ... }

يمكن تنفيذ Stream لجميع المولدات غير المتزامنة مع Return = () . هذا يجعل هذا ممكنًا:

async_gen fn foo() -> impl Stream<Item = i32> { yield 1;  ... }

ملاحظة: المولدات تعمل ليلاً بالفعل ، لكنها لا تستخدم بناء الجملة هذا. كما أنها حاليًا ليست مدركة للتثبيت بخلاف Stream في العقود الآجلة 0.3.

تحرير: استخدم هذا الرمز سابقًا Generator . فاتني فرق بين Stream و Generator . التدفقات غير متزامنة. هذا يعني أنه قد يكون عليهم تقديم قيمة ولكن لا يتعين عليهم ذلك. يمكنهم إما الرد بـ Poll::Ready أو Poll::Pending . من ناحية أخرى ، يجب أن ينتج عن A Generator دائمًا أو يكتمل بشكل متزامن. لقد غيرته الآن إلى AsyncGenerator لعكس ذلك.

Edit2: Ekleog يستخدم التنفيذ الحالي للمولدات بناء جملة بدون علامة ويبدو أنه يكتشف أنه يجب أن يكون مولدًا من خلال البحث عن yield داخل الجسم. هذا يعني أنك ستكون محقًا في قولك إنه يمكن إعادة استخدام async . ومع ذلك ، فإن ما إذا كان هذا النهج منطقيًا أم لا ، فهذه مسألة أخرى. لكن أعتقد أن هذا لموضوع آخر ^^ '

في الواقع ، كنت أفكر في إمكانية إعادة استخدام async ، فهل سيكون ذلك فقط لأن async ، وفقًا لـ RFC هذا ، سيسمح فقط بـ Future s ، وبالتالي يمكن اكتشاف إنها تولد Stream خلال النظر إلى نوع الإرجاع (الذي يجب أن يكون إما Future أو Stream ).

السبب أنا رفع هذا الآن لأنه إذا كنا نريد أن يكون هو نفسه async الكلمة لتوليد كلا Future الصورة و Stream الصورة، ثم أعتقد أن عودة الخارجية سيكون أسلوب الكتابة أكثر وضوحًا ، لأنه سيكون واضحًا ، ولا أعتقد أن أي شخص يتوقع أن ينتج عن async fn foo() -> i32 دفقًا i32 (والذي سيكون ممكنًا إذا احتوى الجسم a yield وتم اختيار أسلوب نوع الإرجاع الداخلي).

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

rpjohnst لقد

لا نريد تعيين نوعين مرتبطين. لا يزال البث مجرد نوع واحد ، وليس impl Iterator<Item=impl Future>> أو أي شيء من هذا القبيل.

rpjohnst قصدت الأنواع المرتبطة Yield و Return من المولدات (غير المتزامنة)

gen fn foo() -> impl Generator<Yield = i32, Return = ()> { ... }

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

// generator
fn foo() -> T yields Y

// generator that implements Iterator
fn foo() yields Y

// async generator
async fn foo() -> T yields Y

// async generator that implements Stream
async fn foo() yields Y

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

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

اعتقدت أنه كان من المتوقع عدم التزامن / انتظار لـ Rust 2018 ولا آمل أن تكون المولدات غير المتزامنة جاهزة بحلول ذلك الوقت ، ولكن ...؟

(أيضًا ، كان تعليقي مقصودًا فقط أن يكون حجة إضافية على منشور مدونة Major breakfast ، ومع ذلك يبدو أنه قد محى تمامًا المناقشة حول هذا الموضوع ... لم يكن هذا هدفي على الإطلاق ، وأعتقد أن النقاش يجب أن يعيد التركيز على هذه المدونة؟)

لا تزال حالة الاستخدام الضيقة للكلمة الرئيسية الانتظار تحيرني. (Esp. Future vs Stream vs Generator)

لا Woudl الكلمة الأساسية الإنتاجية كافية لجميع حالات الاستخدام؟ كما في

{ let a = yield future; println(a) } -> Future

وهو ما يحافظ على نوع الإرجاع صريحًا وبالتالي هناك حاجة إلى كلمة رئيسية واحدة فقط لجميع الدلالات القائمة على "الاستمرارية" دون دمج الكلمات الرئيسية والمكتبة معًا بإحكام شديد.

(فعلنا هذا بلغة الطين بالمناسبة)

aep await لا ينتج عنه مستقبل من المولد - فهو يوقف تنفيذ Future ويعيد التحكم للمتصل.

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

aep السبب وراء الكلمة الأساسية المحددة yield await هو التوافق مع الكلمة الأساسية yield الخاصة بالمولد المستقبلي. نريد دعم المولدات غير المتزامنة وهذا يعني "نطاقين" استمرارين مستقلين.

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

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

sackery إنها جزء من العناصر الداخلية للغة ، ولا يمكن تنفيذها كمكتبة فقط.

لذا اجعلها كلمة رئيسية تمامًا مثل nim ، و c # يفعل!

سؤال: ما الذي يجب أن يكون توقيع الإغلاق غير المتحرك async الذي يلتقط القيم من خلال المرجع القابل للتغيير؟ في الوقت الحالي ، تم حظرهم تمامًا. يبدو أننا نريد نوعًا من نهج GAT الذي يسمح باقتراض الإغلاق حتى ينتهي المستقبل ، على سبيل المثال:

trait AsyncFnMut {
    type Output<'a>: Future;
    fn call(&'a mut self, args: ...) -> Self::Output<'a>;
}

cramertj هناك مشكلة عامة هنا تتعلق

withoutboats صحيح ، سيكون الأمر أكثر شيوعًا في مواقف async مما قد يكون عليه الحال في أي مكان آخر.

ماذا عن fn async بدلاً من async fn ؟
أحب let mut أفضل من mut let .

fn foo1() {
}
fn async foo2() {
}
pub fn foo3() {
}
pub fn async foo4() {
}

بمجرد البحث عن pub fn ، لا يزال بإمكانك العثور على كل الوظائف العامة في شفرة المصدر.ولكن بناء الجملة في الوقت الحالي ليس كذلك.

fn foo1() {
}
async fn foo2() {
}
pub fn foo3() {
}
pub async fn foo4() {
}

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

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

Pzixel أعلم أن معدِّلات الوصول يجب أن تسبق fn لأنها مهمة.
ولكن أعتقد أن async ليس كذلك على الأرجح.

xmeta لم أر هذه الفكرة المقترحة من قبل. ربما نرغب في وضع async أمام fn لنكون متسقًا ، لكنني أعتقد أنه من المهم النظر في جميع الخيارات. شكرا على نشرك!

// Status quo:
pub unsafe async fn foo() {} // #![feature(async_await, futures_api)]
pub const unsafe fn foo2() {} // #![feature(const_fn)]

Major breakfast شكرا لك على ردك ، اعتقدت مثل هذا.

{ Public, Private } ⊇ Function  → put `pub` in front of `fn`
{ Public, Private } ⊇ Struct    → put `pub` in front of `struct`
{ Public, Private } ⊇ Trait     → put `pub` in front of `trait`
{ Public, Private } ⊇ Enum      → put `pub` in front of `enum`
Function ⊇ {Async, Sync}        → put `async` in back of `fn`
Variable ⊇ {Mutable, Imutable}  → put `mut` in back of `let`

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

async fn غير قابل للتجزئة ، فهو يمثل وظيفة غير متزامنة。

async fn كامل.

أنت تبحث عن pub fn , مما يعني أنك تبحث عن وظيفة المزامنة العامة.
بالطريقة نفسها ، يمكنك البحث عن pub async fn , مما يعني أنك تبحث عن وظيفة عامة غير متزامنة.

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

  • يحدد async fn دالة عادية تقوم بإرجاع المستقبل. تعتبر كافة الدالات التي تُرجع المستقبل "غير متزامنة". مؤشرات دالة async fn s والوظائف الأخرى التي تُرجع مستقبلًا متطابقة °. هذا مثال على الملعب . البحث عن "async fn" يمكنه فقط العثور على الوظائف التي تستخدم الترميز ، ولن يعثر على جميع الوظائف غير المتزامنة.
  • البحث عن pub fn لن يجد unsafe أو const وظائف.

° النوع الملموس الذي تم إرجاعه بواسطة async fn هو بالطبع مجهول. أعني أن كلاهما يقوم بإرجاع نوع يقوم بتنفيذ Future

لاحظ xmeta أن mut لا " يلاحق " ، أو بالأحرى ، أن mut لا يقوم بتعديل let . يأخذ let نمطًا ، أي

let PATTERN = EXPRESSION;

mut هو جزء من PATTERN ، وليس من let نفسه. فمثلا:

// one is mutable one is not
let (mut a, b) = (1, 2);

steveklabnik أفهم. أردت فقط إظهار العلاقة بين الهيكل الهرمي وترتيب الكلمات. شكرا لك

ما هي أفكار الناس حول السلوك المطلوب لـ return و break داخل كتل async ؟ حاليًا يتم إرجاع return من الكتلة غير المتزامنة - إذا سمحنا بدولار return على الإطلاق ، فهذا هو الخيار الوحيد الممكن حقًا. يمكننا حظر return واستخدام شيء مثل 'label: async { .... break 'label x; } للعودة من كتلة غير متزامنة. يرتبط هذا أيضًا بالمحادثة حول ما إذا كان يجب استخدام الكلمة الرئيسية break أو return لميزة كسر إلى كتل (https://github.com/rust-lang/rust/issues/ 48594).

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

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

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

هل التحركات والإغلاقات غير المتزامنة ما زالت مخططة؟ ما يلي من RFC:

// closure which is evaluated immediately
async move {
     // asynchronous portion of the function
}

ومزيد من أسفل الصفحة

async { /* body */ }

// is equivalent to

(async || { /* body */ })()

مما يجعل return يتماشى مع عمليات الإغلاق ، ويبدو من السهل جدًا التقاطه وشرحه.

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

يجب أن تعمل memoryruins async || { ... return x; ... } تمامًا. أنا أقول أنه لا ينبغي أن يكون async { ... return x; ... } ، على وجه التحديد لأن async ليس إغلاقًا. return له معنى محدد للغاية: "العودة من دالة الحاوية". عمليات الإغلاق هي وظيفة. الكتل غير المتزامنة ليست كذلك.

memoryruins تم تنفيذ كلاهما بالفعل.

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

الكتل غير المتزامنة ليست كذلك.

أعتقد أنني ما زلت أفكر فيها كوظائف بمعنى أنها هيئة ذات سياق تنفيذ محدد بشكل منفصل من الكتلة التي تحتوي عليها ، لذلك من المنطقي بالنسبة لي أن return داخلي لـ async كتلة || و async do.

cramertj "نحوي" مهم ، بالرغم من ذلك.

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

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

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

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

كان هناك بعض النقاش المسبق حول هذا الموضوع على RFC . كما قلت هناك أنا أؤيد الكتل غير المتزامنة باستخدام break _ دون الحاجة إلى تقديم تسمية (لا توجد طريقة لكسر الكتلة غير المتزامنة إلى حلقة خارجية حتى لا تفقد أي تعبير).

withoutboats الإغلاق هو نوع آخر من الوظائف ؛ بمجرد أن تتعلم "الإغلاق هو وظيفة" ، يمكنك تطبيق كل ما تعرفه عن الوظائف على الإغلاق ، بما في ذلك " return يعود دائمًا من الوظيفة المحتوية".

@ Nemo157 حتى إذا قمت break استهدف الكتلة async ، فسيتعين عليك توفير آلية (مثل 'label: async ) للعودة مبكرًا من حلقة داخل كتلة غير متزامنة .

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

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

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

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

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

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

أعتقد أن الكتل غير المتزامنة هي أيضًا نوع من "الوظائف"

أعتقد أن هذا منظور يعتمد على معرفة العناصر الداخلية للتنفيذ.

أنا لا أراها كوظائف على الإطلاق.

أنا لا أراها كوظائف على الإطلاق.

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

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

cramertj لن أفعل ، لا ؛ بالنسبة إلى lambdas و / أو الوظائف المحددة في إحدى الوظائف ، فمن الطبيعي تمامًا أنها وظيفة. (كان تعرضي الأول لهؤلاء في Python ، FWIW ، حيث لا تستطيع lambdas استخدام return وفي الدوال المتداخلة ، إرجاع return من الوظيفة التي تحتوي على return .)

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

لا يقترح RFC هذا كيفية عمل المشغل ? وتركيبات التحكم في التدفق مثل return و break و continue داخل الكتل غير المتزامنة.

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

أتفق مع memoryruins هنا ، أعتقد أنه سيكون من المفيد إنشاء RFC آخر لمناقشة تلك التفاصيل بمزيد من التفصيل.

ما رأيك في الوظيفة التي تتيح لنا الوصول إلى السياق من داخل fn غير متزامن ، ربما تسمى core::task::context() ؟ سيكون الأمر مجرد ذعر إذا تم استدعاؤه من خارج fn غير متزامن. أعتقد أن هذا سيكون مفيدًا تمامًا ، على سبيل المثال ، للوصول إلى المنفذ لإنتاج شيء ما.

Major breakfast تسمى هذه الوظيفة lazy

async fn foo() -> i32 {
    await!(lazy(|ctx| {
        // do something with ctx
        42
    }))
}

لأي شيء أكثر تحديدًا مثل التفريخ ، من المحتمل أن تكون هناك وظائف مساعدة تجعله أكثر راحة

async fn foo() -> i32 {
    let some_task = lazy(|_| 5);
    let spawned_task = await!(spawn_with_handle(some_task));
    await!(spawned_task)
}

@ Nemo157 في الواقع spawn_with_handle هو المكان الذي أود استخدام هذا فيه. عند تحويل الكود إلى 0.3 ، لاحظت أن spawn_with_handle هو في الواقع مجرد مستقبل لأنه يحتاج إلى الوصول إلى السياق ( انظر الكود ). ما أود فعله هو إضافة طريقة spawn_with_handle إلى ContextExt وجعل spawn_with_handle وظيفة مجانية تعمل فقط داخل الدوال غير المتزامنة:

fn poll(self: PinMut<Self>, cx: &mut Context) -> Poll<Self::Output> {
     let join_handle = ctx.spawn_with_handle(future);
     ...
}
async fn foo() {
   let join_handle = spawn_with_handle(future); // This would use this function internally
   await!(join_handle);
}

سيؤدي هذا إلى إزالة كل هراء الانتظار المزدوج الذي لدينا حاليًا.

إذا أمعنا التفكير في الأمر ، فستحتاج الطريقة إلى تسمية core::task::with_current_context() وتعمل بشكل مختلف قليلاً لأنه يجب أن يكون من المستحيل تخزين مرجع.

تحرير: هذه الوظيفة موجودة بالفعل تحت الاسم get_task_cx . هو حاليا في libstd لأسباب فنية. أقترح جعلها API عامة بمجرد وضعها في libcore.

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

لذا ، من المحتمل أن يكون شيئًا مثل

fn spawn_with_handle(executor: &mut Executor, future: impl Future) { ... }

async fn foo() {
    let join_handle = spawn_with_handle(async_context!().executor(), future);
    await!(join_handle);
}

@ Nemo157 أعتقد أنك على حق: من المحتمل أن وظيفة مثل التي spawn_with_handle ماكرو يستخدم await داخليًا (مثل select! و join! ):

async fn foo() {
    let join_handle = spawn_with_handle!(future);
    await!(join_handle);
}

هذا يبدو لطيفًا ويمكن تنفيذه بسهولة عبر await!(lazy(|ctx| { ... })) داخل الماكرو.

async_context!() مشكلة لأنه لا يمكن أن يمنعني من تخزين مرجع السياق عبر نقاط الانتظار.

async_context!() مشكلة لأنه لا يمكن أن يمنعني من تخزين مرجع السياق عبر نقاط الانتظار.

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

@ Nemo157 تقصد شيئا من هذا القبيل؟

let my_arg = yield; // my_arg lives until next yield

Pzixel آسف لإيقاظ مناقشة قديمة _ربما _ ، لكني أود إضافة أفكاري.

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

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

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

شيء رائع آخر حول هذا النحو هو أن السلوك يكون واضحًا على الفور عند دمجه مع الرمز ? (على الأقل أعتقد أنه سيكون كذلك). ألق نظرة على ما يلي:

// Await, then unwrap a Result from the future
awaiting_a_result()@?;

// Unwrap a future from a result, then await
result_with_future()?@;

// The real crazy can make it as funky as they want
magic()?@@?@??@; 
// - I'm joking, of course

هذا ليس به مشكلة مثل await future? حيث أنه ليس من الواضح للوهلة الأولى ما الذي سيحدث ما لم تكن على علم بمثل هذا الموقف. ومع ذلك فإن تنفيذها يتوافق مع ? .

الآن ، هناك عدد قليل من الأشياء _ الثانوية_ التي يمكنني التفكير فيها والتي من شأنها أن تتعارض مع هذه الفكرة:

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

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

ما هي افكاركم يا رفاق؟ هل توافق على أنه مشابه إلى حد ما لكيفية الحصول على try!() _became_ ? ؟ هل تفضل await أو رمزًا ولماذا؟ هل أنا مجنون لمناقشة هذا الأمر ، أو ربما أفتقد شيئًا ما؟

آسف لأي مصطلحات غير صحيحة ربما استخدمتها.

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

وقال، وربما أفضل طريقة للذهاب هو في الواقع تماما مثل مع try! ؟ أي ، ابدأ بماكرو async!(foo) صريحًا ، وإذا دعت الحاجة ، أضف بعض السيجيل الذي سيكون سكرًا له. بالتأكيد ، هذا يؤجل المشكلة إلى وقت لاحق ، ولكن async!(foo) طريقة كافية للتكرار الأول لـ async / wait ، مع ميزة كونها غير مثيرة للجدل نسبيًا. (ولوجود سابقة try! / ? يجب أن تنشأ سيجيل)

withoutboats لم أقرأ هذا الموضوع بالكامل ، ولكن هل يساعد أحد في التنفيذ؟ أين فرع التطوير الخاص بك؟

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

BatmanAoD تم https://github.com/rust-lang/rust/pull/51580

يحتوي خيط RFC الأصلي على تعليقات من عدد من الخبراء في فضاء PLT ، خارج Rust حتى :)

أود أن أقترح رمز "$" لانتظار العقود الآجلة ، لأن الوقت هو المال ، وأريد أن أذكر المترجم بذلك.

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

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

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

steveklabnik آه ، حسنًا ، شكرًا لك. هل يمكننا (/ هل يجب علي) تحديث وصف المشكلة؟ ربما يجب تقسيم العنصر النقطي "التنفيذ الأولي" إلى "تنفيذ بدون دعم بدون move " و "تنفيذ كامل"؟

هل يتم العمل على تكرار التنفيذ التالي في بعض الفروع / الشوكات العامة؟ أو ألا يمكن الاستمرار في ذلك حتى يتم قبول RFC 2418؟

لماذا تتم مناقشة مسألة بناء الجملة غير المتزامن / انتظار هنا بدلاً من مناقشة RFC؟

@ c-edw أعتقد أن السؤال حول الكلمة الرئيسية async تتم الإجابة عليه من خلال " ما لون وظيفتك"؟

parasyte لقد تم اقتراح أن هذا

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

ولكن يبدو أن قراءتك للمنشور تشير إلى أن async / await جيدة ، لكن هناك استنتاج يمكن استخلاصه حول الكلمة الأساسية؟ هل تمانع في التوسع في ذلك؟

أنا أتفق مع وجهة نظرك هناك أيضًا. كنت أعلق على أن الكلمة الرئيسية async ضرورية ، وتوضح المقالة الأسباب الكامنة وراءها. الاستنتاجات التي توصل إليها المؤلف هي مسألة مختلفة.

@ parasyte آه ، حسنًا. لقد سألت سعيدًا - بسبب نفور المؤلف من الانقسامات بين الأحمر والأزرق ، اعتقدت أنك تقول العكس!

أود أن أوضح أكثر ، لأنني أشعر أنني لم أفعل ذلك بشكل عادل.

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

كلاهما لديه نفس المشكلة ؛ لا يزالون بحاجة إلى الفصل للتمييز بين انتظار إكمال مهمة غير متزامنة وبدء مهمة بشكل غير متزامن !

  • لم تقدم ميدوري الكلمة الأساسية await فحسب ، بل قدمت أيضًا الكلمة الرئيسية async على موقع استدعاء الوظائف .
  • يوفر gevent gevent.spawn بالإضافة إلى الانتظار الضمني لاستدعاءات الوظائف ذات المظهر الطبيعي.

كان هذا هو السبب الكامل لطرح مقالة color-a-function ، حيث إنها تجيب على السؤال ، "لماذا تستحق غير المتزامن كلمة رئيسية؟"

حسنًا ، حتى الكود المتزامن المستند إلى الخيط يمكن أن يميز "انتظار إكمال مهمة" (الانضمام) و "بدء مهمة" (تفرخ). يمكنك تخيل لغة يكون كل شيء فيها غير متزامن (من حيث التنفيذ) ، ولكن لا يوجد تعليق توضيحي على await (لأنه السلوك الافتراضي) ، و Midori's async بدلاً من ذلك تم تمرير الإغلاق إلى spawn API. يؤدي هذا إلى وضع كل عدم التزامن على نفس القاعدة النحوية / الوظيفية اللونية تمامًا مثل المزامنة الكاملة.

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

rpjohnst نعم ، لقد قرأت

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

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

يوم الخميس ، 12 تموز (يوليو) 2018 ، الساعة 3:01 مساءً ، راسل جونستون [email protected]
كتب:

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

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

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

/خارج الموضوع

Pzixel لا يوجد كائن حي لا يزال يعمل عليه ، لأنه تم إغلاق المشروع. لكن مدونة Joe Duffy بها الكثير من التفاصيل الشيقة. انظر الروابط أعلاه.

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

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

CyrusNajmabadi آسف على إرسال رسالة إليك مرة أخرى ، ولكن إليك بعض المعلومات الإضافية حول اتخاذ القرار.

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

من قناة الخلاف # wg-net :

تضمين التغريدة
مادة للتفكير: غالبًا ما أكتب Ok::<(), MyErrorType>(()) في نهاية قوالب async { ... } . ربما هناك شيء يمكننا التوصل إليه لتسهيل تقييد نوع الخطأ؟

تضمين التغريدة
[...] ربما نريده أن يكون متسقًا مع [ try

(أتذكر بعض المناقشات الحديثة نسبيًا حول كيفية قيام كتل try بالإعلان عن نوع الإرجاع ، لكن لا يمكنني العثور عليها الآن ...)

الأشكال المذكورة:

async -> io::Result<()> {
    ...
}

async: io::Result<()> {
    ...
}

async as io::Result<()> {
    ...
}

الشيء الوحيد الذي يمكن أن يفعله try وهو أقل راحة مع async هو استخدام ارتباط متغير أو كتابة ascription ، على سبيل المثال

let _: io::Result<()> = try { ... };
let _: impl Future<Output = io::Result<()>> = async { ... };

سبق لي أن ألقيت فكرة السماح بصياغة تشبه fn للسمة Future ، على سبيل المثال Future -> io::Result<()> . من شأن ذلك أن يجعل خيار تقديم الكتابة اليدوية يبدو أفضل قليلاً ، على الرغم من أنه لا يزال يحتوي على الكثير من الأحرف:

let _: impl Future -> io::Result<()> = async {
}
async -> impl Future<Output = io::Result<()>> {
    ...
}

سيكون إختياري.

إنه مشابه لبناء جملة الإغلاق الحالي:

|x: i32| -> i32 { x + 1 };

تحرير: وفي النهاية عندما يكون من الممكن أن يقوم TryFuture بتنفيذ Future :

async -> impl TryFuture<Ok = i32, Error = ()> {
    ...
}

Edit2: على وجه الدقة ، سيعمل ما سبق مع تعريفات السمات اليوم. الأمر فقط هو أن النوع TryFuture ليس مفيدًا اليوم لأنه لا يستخدم حاليًا Future

Major Fr لماذا -> impl Future<Output = io::Result<()>> بدلاً من -> io::Result<()> ؟ نحن بالفعل القيام عودة من نوع desugaring ل async fn foo() -> io::Result<()> ، لذلك IMO إذا أردنا استخدام -> جملة المستندة يبدو واضحا أن كنا نريد نفس السكر هنا.

cramertj نعم ، يجب أن يكون متسقًا. يفترض منشوري أعلاه كيندا أنه يمكنني إقناعكم جميعًا بأن نهج نوع الإرجاع الخارجي أفضل

في حالة استخدام async -> R { .. } فمن المفترض أنه يجب علينا أيضًا استخدام try -> R { .. } بالإضافة إلى استخدام expr -> TheType بشكل عام لنوع ascription. بعبارة أخرى ، يجب أن يتم تطبيق صيغة الوصفة التي نستخدمها بشكل موحد في كل مكان.

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

Major breakfast أوافق بشكل أساسي ؛ أعتقد أننا يجب أن نستخدم : لنوع ascription ، لذا async : Type { .. } ، try : Type { .. } ، و expr : Type . لقد ناقشنا الغموض المحتمل في Discord وأعتقد أننا وجدنا طريقة للمضي قدمًا مع : وهذا أمر منطقي ...

سؤال آخر حول Either enum. لدينا بالفعل Either في صندوق futures . إنه أيضًا محير لأنه يبدو تمامًا مثل صندوق Either من either عندما لا يكون كذلك.

نظرًا لأنه يبدو أن Futures قد تم دمجه في std (على الأقل الأجزاء الأساسية جدًا منه) هل يمكننا أيضًا تضمين Either هناك؟ من الضروري الحصول عليها حتى تتمكن من إرجاع impl Future من الوظيفة.

على سبيل المثال ، غالبًا ما أكتب رمزًا مثل التالي:

fn handler() -> impl Future<Item = (), Error = Bar> + Send {
    someFuture()
        .and_then(|x| {
            if condition(&x) {
                Either::A(anotherFuture(x))
            } else {
                Either::B(future::ok(()))
            }
        })
}

أود أن أكون قادرًا على كتابته مثل:

async fn handler() -> Result<(), Bar> {
    let x = await someFuture();
    if condition(&x) {
        await anotherFuture(x);
    }
}

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

_ يمكنك العثور على الكود الفعلي هنا إذا كنت ترغب في ذلك.

Pzixel ، لن تحتاج إلى Either داخل async وظائف ، طالما أنك await العقود الآجلة ، فإن تحويل الكود الذي يقوم به async سيخفي هذين الاثنين أنواع داخليًا وتقدم نوع إرجاع واحد إلى المترجم.

Pzixel أيضًا ، آمل (شخصيًا) ألا يتم تقديم Either مع RFC هذا ، لأن ذلك سيقدم نسخة مقيدة من https://github.com/rust-lang/rfcs/issues / 2414 (الذي يعمل فقط مع نوعين فقط مع Future s) ، وبالتالي من المحتمل إضافة cruft لواجهة برمجة التطبيقات إذا تم دمج حل عام - وكما ذكر @ Nemo157 ، لا يبدو أنه حالة طارئة لـ لديك Either الآن :)

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

بالطبع ، هذا لا يتعلق بـ async/await فقط ، إنه شيء أكثر عمومية ، لذا فهو يستحق RFC الخاص به. أردت فقط التأكيد على أن إما futures يجب أن يعرف عن either أو العكس (من أجل تنفيذ IntoFuture بشكل صحيح).

Pzixel إن Either تم تصديره بواسطة صندوق العقود الآجلة هو إعادة تصدير من الصندوق either . لا يمكن للصندوق futures 0.3 تنفيذ Future مقابل Either بسبب القواعد المعزولة. من المحتمل جدًا أننا سنقوم أيضًا بإزالة Stream و Sink مقابل Either للاتساق وتقديم بديل بدلاً من ذلك (نوقش هنا ). بالإضافة إلى ذلك ، يمكن للصندوق either تنفيذ Future ، Stream و Sink نفسه ، على الأرجح تحت علامة الميزة.

ومع ذلك ، كما ذكر @ Nemo157 بالفعل ، عند العمل مع العقود الآجلة ، من الأفضل فقط استخدام وظائف غير متزامنة بدلاً من Either .

العناصر async : Type { .. } مقترحة الآن في https://github.com/rust-lang/rfcs/pull/2522.

هل تم تنفيذ الوظائف غير المتزامنة / الانتظار تلقائيًا بتطبيق Send بالفعل؟

يبدو أن الوظيفة غير المتزامنة التالية ليست (بعد؟) Send :

pub async fn __receive() -> ()
{
    let mut chan: futures::channel::mpsc::Receiver<Box<Send + 'static>> = None.unwrap();

    await!(chan.next());
}

رابط إلى الناسخ الكامل (الذي لا يتم تجميعه في الملعب بسبب نقص futures-0.3 ، على ما أعتقد) هنا .

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

في ما يلي ملعب يُظهر أن الوظائف غير المتزامنة / الانتظار التي تنفذ عمل Send _should_. يؤدي إلغاء التعليق على الإصدار Rc اكتشاف هذه الوظيفة على أنها ليست - Send . يمكنني إلقاء نظرة على مثالك المحدد قليلاً (لا يوجد مترجم Rust على هذا الجهاز: قليلاً_frowning_face :) لمحاولة معرفة سبب عدم نجاحه.

Ekleog std::mpsc::Receiver ليس Sync ، و async fn الذي كتبته يحمل إشارة إليه. مراجع العناصر !Sync هي !Send .

cramertj Hmm… لكن ، هل أنا لا أمتلك mpsc::Receiver ، والذي يجب أن يكون Send iff نوعه العام هو Send ؟ (أيضًا ، إنه ليس std::mpsc::Receiver ولكنه futures::channel::mpsc::Receiver ، وهو Sync أيضًا إذا كان النوع Send ، آسف لعدم ملاحظة mpsc::Receiver الاسم المستعار كان غامضًا!)

@ Nemo157 شكرا! لقد فتحت https://github.com/rust-lang/rust/issues/53259 لتجنب الكثير من الضوضاء حول هذه المشكلة :)

قد يتطلب السؤال حول ما إذا كانت كتل async تسمح ? وكيفية تدفق التحكم الآخر ، بعض التفاعل مع كتل try (على سبيل المثال ، try async { .. } للسماح ? بدون ارتباك مشابه لـ return ؟).

هذا يعني أن آلية تحديد نوع كتلة async قد تحتاج إلى التفاعل مع آلية تحديد نوع كتلة try . لقد تركت تعليقًا على صيغة الإسناد RFC: https://github.com/rust-lang/rfcs/pull/2522#issuecomment -412577175

ما عليك سوى النقر على ما اعتقدت في البداية أنه مشكلة futures-rs ، ولكن اتضح أنها قد تكون في الواقع مشكلة غير متزامنة / انتظار ، لذلك ها هي: https://github.com/rust-lang-nursery/ الآجلة- RS / الإصدارات / 1199 # issuecomment -413089012

إصدار واحد جديد: https://github.com/rust-lang/rust/issues/53447

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

هل سيكون لواجهة برمجة تطبيقات Future / Task طريقة لإنتاج العقود الآجلة المحلية؟
أرى أن هناك SpawnLocalObjError ، لكن يبدو أنه غير مستخدم.

panicbit في مجموعة العمل ، نناقش حاليًا ما إذا كان من المنطقي تضمين وظيفة التفريخ في السياق على الإطلاق. https://github.com/rust-lang-nursery/wg-net/issues/56

( SpawnLocalObjError ليس غير مستخدم تمامًا: LocalPool من صندوق العقود الآجلة يستخدمه. أنت محق ، مع ذلك ، لا شيء يستخدمه في libcore)

withoutboats لقد لاحظت أن بعض الروابط في وصف المشكلة قديمة. على وجه التحديد، https://github.com/rust-lang/rfcs/pull/2418 مغلق و https://github.com/rust-lang-nursery/futures-rs/issues/1199 قد تم نقله إلى الشبكي: / /github.com/rust-lang/rust/issues/53548

ملحوظة. اسم مشكلة التعقب هذه غير متزامن / ينتظر ولكن تم تعيينه لواجهة برمجة تطبيقات المهمة أيضًا! تحتوي واجهة برمجة تطبيقات المهام حاليًا على RFC للتثبيت معلق: https://github.com/rust-lang/rfcs/pull/2592

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

aep من الممكن التحويل بسهولة من نظام يعتمد على الدفع إلى نظام Future القائم على السحب باستخدام oneshot::channel .

على سبيل المثال ، تعتمد وعود JavaScript على الدفع ، لذلك يستخدم stdweb oneshot::channel لبعض واجهات برمجة تطبيقات رد الاتصال المستندة إلى الدفع ، مثل setTimeout .

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

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

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

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

ومع ذلك ، في هذه الحالة ، كل ما نحتاجه هنا هو جعل غير المتزامن يصدر شيئًا مثل Generator.

async في هذه المرحلة هو حرفيا الحد الأدنى من الغلاف حول المولد. أواجه صعوبة في رؤية كيف تساعد المولدات في IO غير المتزامن المستند إلى الدفع ، ألا تحتاج إلى تحويل CPS لهؤلاء؟

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

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

ألا تحتاج إلى تحويل CPS لهؤلاء؟

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

تحتاج الموارد داخل الشيء الذي يتم سحبه؟

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

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

إن السماح بربط عمليات الاسترجاعات التعسفية بالمستقبل يتطلب شكلاً من أشكال المراوغة

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

كما يصبح الإلغاء أكثر تعقيدًا بسبب سلامة الخيط

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

async fn add (a: u32) -> u32 {
    let b = await
    a + b
}

add(3).continue(2) == 5

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

ليس لدي ، حيث تكون سرعة التنفيذ غير ذات صلة ، ولكن لدي ذاكرة إجمالية تبلغ 1 ميجابايت ، لذا لا تناسب العقود الآجلة حتى الفلاش

أنا متأكد تمامًا من أن المستقبلات الحالية مصممة للعمل في بيئات مقيدة بالذاكرة. ما الذي يشغل مساحة كبيرة بالضبط؟

تحرير: يشغل هذا البرنامج 295 كيلو بايت من مساحة القرص عند تجميعه - الإصدار على جهاز macbook الخاص بي (يأخذ عالم hello الأساسي 273 كيلو بايت):

use futures::{executor::LocalPool, future};

fn main() {
    let mut pool = LocalPool::new();
    let hello = pool.run_until(future::ready("Hello, world!"));
    println!("{}", hello);
}

ليس لدي ، حيث تكون سرعة التنفيذ غير ذات صلة ، ولكن لدي ذاكرة إجمالية تبلغ 1 ميجابايت ، لذا لا تناسب العقود الآجلة حتى الفلاش

أنا متأكد تمامًا من أن المستقبلات الحالية مصممة للعمل في بيئات مقيدة بالذاكرة. ما الذي يشغل مساحة كبيرة بالضبط؟

أيضا ماذا تقصد بالذاكرة؟ لقد قمت بتشغيل الكود الحالي غير المتزامن / الانتظار على الأجهزة المزودة بذاكرة فلاش 128 كيلو بايت / ذاكرة وصول عشوائي 16 كيلو بايت. هناك بالتأكيد مشكلات في استخدام الذاكرة مع عدم التزامن / انتظار حاليًا ، ولكن هذه مشكلات في الغالب تتعلق بالتنفيذ ويمكن تحسينها عن طريق إضافة بعض التحسينات الإضافية (مثل https://github.com/rust-lang/rust/issues/52924).

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

لماذا ا؟ لا يزال هذا لا يبدو كشيء يفرض عليك المستقبل. يمكنك الاتصال بسهولة poll كما تفعل مع الآلية القائمة على الدفع.

أيضا ماذا تقصد بالذاكرة؟

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

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

يمكنك إجراء الاستطلاع بسهولة كما تفعل مع آلية الدفع.

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

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

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

aep كيف يمكننا إعادة استخدام الكلمات الرئيسية ( async و await

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

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

لم أتابع الأشياء غير المتزامنة ، لذا أعتذر إذا تمت مناقشة ذلك من قبل / في أي مكان آخر ولكن ما هي خطة (التنفيذ) لدعم عدم التزامن / الانتظار في no_std ؟

AFAICT يستخدم التنفيذ الحالي TLS لتمرير Waker ولكن لا يوجد دعم TLS (أو مؤشر ترابط) في no_std / core . سمعت من alexcrichton أنه قد يكون من الممكن التخلص من TLS إذا / عندما يكسب Generator.resume دعمًا للحجج.

هل يتم تنفيذ خطة منع التثبيت غير المتزامن / انتظار دعم no_std ؟ أم أننا على يقين من إمكانية إضافة دعم no_std بدون تغيير أي من القطع التي سيتم تثبيتها لشحن std متزامن / في انتظار الاستقرار؟

japaric poll يأخذ الآن السياق بشكل صريح. AFAIK و TLS لم يعد مطلوبًا.

https://doc.rust-lang.org/nightly/std/future/trait.Future.html#tymethod.poll

تحرير: غير مناسب لـ غير المتزامن / انتظار ، فقط للعقود الآجلة.

[...] هل نحن على يقين من أنه يمكن إضافة دعم no_std بدون تغيير أي من القطع التي سيتم تثبيتها لشحن std متزامن / في انتظار الاستقرار؟

أنا أعتقد هذا. القطع ذات الصلة هي الوظائف الموجودة في std::future ، وكلها مخفية خلف ميزة gen_future غير مستقرة لن تستقر أبدًا. يستخدم التحويل async set_task_waker لتخزين المستيقظ في TLS ثم يستخدم poll_with_tls_waker await! poll_with_tls_waker للوصول إليه. إذا حصلت المولدات على دعم وسيطة استئناف ، فبإمكان التحويل async بدلاً من ذلك تمرير المستيقظ كمتوسط ​​استئناف ويمكن أن يقرأه await! خارج الوسيطة.

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

japaric نفس القارب. حتى لو عمل شخص ما على العقود الآجلة ، فإن ذلك يمثل مخاطرة كبيرة منذ المستوى 3 بالكامل.

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

  1. راجع وسيطة "الاستطلاع" https://github.com/aep/osaka/blob/master/osaka-dns/src/lib.rs#L76 it's an Arc
  2. تسجيل شيء ما في شيء الاستطلاع في السطر 87
  3. تعطي لتوليد نقطة استمرار عند السطر 92
  4. اتصل بمولد من مولد لإنشاء كومة مستوى أعلى في السطر 207
  5. أخيرًا تنفيذ المكدس بالكامل عن طريق تمرير وقت التشغيل في السطر 215

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

كنت في منتصف الطريق في تنفيذ ذلك

https://twitter.com/arvidep/status/1067383652206690307

ولكن نوعًا ما من العبث أن أقطع كل الطريق إذا كنت الشخص الوحيد الذي يريد ذلك.

ولم أستطع التوقف عن التفكير فيما إذا كان عدم تزامن / انتظار TLS بدون وسيطات المولد ممكنًا ، لذلك قمت بتطبيق زوج ماكرو no_std proc-macro قائم على async_block! / await! زوج ماكرو باستخدام المتغيرات المحلية فقط.

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

لقد أدركت للتو أنه لا يوجد أي ذكر لنقل await! من std إلى core في OP ، ربما يمكن إضافة # 56767 إلى قائمة المشكلات لحلها قبل التثبيت لتعقبها هذه.

@ Nemo157 نظرًا await! فهو ليس مانعًا على أي حال.

Centril لا أعرف من قال لك await! غير المتوقع أن يستقر ...: wink:

cramertj كان يقصد نسخة الماكرو وليس إصدار الكلمة الرئيسية على ما أعتقد ...

@ crlf0710 فعلت كذلك :)

cramertj ألا نريد إزالة الماكرو لأنه يوجد حاليًا اختراق قبيح في المترجم يجعل وجود كل من await و await! ممكنًا؟ إذا قمنا بتثبيت الماكرو ، فلن نتمكن أبدًا من إزالته.

stjepang أنا لا أهتم كثيرًا في أي اتجاه حول بناء الجملة await! ، بصرف النظر عن التفضيل العام لرموز postfix وكراهية الغموض والرموز غير المنطوقة / غير القادرة على Google. على حد علمي ، الاقتراحات الحالية (مع ? لتوضيح الأسبقية) هي:

  • await!(x)? (ما لدينا اليوم)
  • await x? ( await روابط أضيق من ? ، لا تزال تدوين البادئة ، تحتاج إلى أقواس لأساليب السلسلة)
  • await {x}? (كما هو مذكور أعلاه ، لكن يتطلب مؤقتًا {} لإزالة الغموض)
  • await? x ( await روابط أقل إحكامًا ، لا تزال تدوين البادئة ، تحتاج إلى أقواس لأساليب السلسلة)
  • x.await? (يبدو وكأنه حقل وصول)
  • x# / x~ / إلخ. (بعض الرموز)
  • x.await!()? (postfix-macro-style، withoutboats وأعتقد أن الآخرين ربما ليسوا من عشاق وحدات الماكرو postfix لأنهم يتوقعون . للسماح بالإرسال المستند إلى النوع ، وهو ما لن يحدث لوحدات الماكرو postfix )

أعتقد أن أفضل طريق للشحن هو الحصول على await!(x) ، un-keyword-ify await ، وفي النهاية بيع الناس يومًا ما على جودة وحدات الماكرو postfix ، مما يسمح لنا بإضافة x.await!() . أشخاص آخرون لديهم آراء مختلفة ؛)

أنا أتابع هذا الموضوع بشكل فضفاض للغاية ، لكن هذا رأيي:

أنا شخصياً أحب الماكرو await! كما هو وكما هو موصوف هنا: https://blag.nemo157.com/2018/12/09/inside-rusts-async-transform.html

إنه ليس أي نوع من السحر أو بناء جملة جديد ، مجرد ماكرو عادي. الأقل هو الأكثر ، بعد كل شيء.

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

وفي الوقت نفسه ، فإن async / yield عبارة عن سكر نحوي "فقط" للمولدات. إنه يذكرني بالأيام التي كانت JavaScript فيها تتلقى دعمًا غير متزامن / في انتظار الدعم وكان لديك مشاريع مثل Babel و Regenerator التي نقلت رمزًا غير متزامن لاستخدام المولدات والوعود / العقود الآجلة للعمليات غير المتزامنة ، تمامًا كما نفعل نحن.

ضع في اعتبارك أننا في النهاية سنرغب في أن تكون ميزة غير المتزامن والمولدات سمات مميزة ، ومن المحتمل أن تكون قابلة للتكوين مع بعضها البعض (إنتاج Stream ). ترك await! كماكرو ينخفض ​​إلى yield ليس حلاً دائمًا.

ترك الانتظار! باعتباره الماكرو الذي يقلل فقط من العائد ليس حلاً دائمًا.

لا يمكن أن يكون مرئيًا للمستخدم بشكل دائم لأنه ينخفض ​​إلى yield ، ولكن يمكن بالتأكيد الاستمرار في تنفيذه بهذه الطريقة. حتى عندما يكون لديك مولدات غير متزامنة = Stream لا يزال بإمكانك استخدام مثل yield Poll::Pending; مقابل yield Poll::Ready(next_value) .

ضع في اعتبارك أننا في النهاية سنرغب في أن تكون ميزة عدم التزامن والمولدات سمات مميزة

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

في الواقع ، لقد كتبت ذات مرة

cramertj لا يمكن تنفيذه بهذه الطريقة إذا كان الاثنان "تأثيرات" مميزة. هناك بعض النقاش حول هذا هنا: https://internals.rust-lang.org/t/pre-rfc-await-generators-directly/7202. لا نريد yield Poll::Ready(next_value) ، نريد yield next_value ، ولدينا await s في مكان آخر في نفس الوظيفة.

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

لا نريد أن نحصل على Poll :: Ready (next_value) ، نريد أن نحقق next_value ، وننتظر في مكان آخر في نفس الوظيفة.

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

cramertj هذا أيضًا:

  • await? x

novacrazy نعم ، إنها سمات مميزة ، لكن يجب أن تكون قابلة للتكوين معًا.

وهي قابلة للتكوين بالفعل في JavaScript:

https://thenewstack.io/whats-coming-up-in-javascript-2018-async-generators-better-regex/

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

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

Centril طيب ، فتح # 56974 ، هل هذا صحيح بما يكفي لإضافته كسؤال لم يتم حله إلى OP؟


لا أريد حقًا الدخول في بناء الجملة await مرة أخرى ، لكن لا بد لي من الرد على نقطة واحدة على الأقل:

أنا شخصياً أحب الماكرو await! كما هو وكما هو موصوف هنا: https://blag.nemo157.com/2018/12/09/inside-rusts-async-transform.html

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

  1. إخفاء التطبيق الأساسي ، حيث تشير إحدى المشكلات التي لم يتم حلها إلى أنه يمكنك حاليًا إنشاء مولد باستخدام || await!() .
  2. دعم المولدات غير المتزامنة ، كما ذكر cramertj ، يتطلب ذلك التفريق بين yield s المضافة بواسطة await و yield s الأخرى التي كتبها المستخدم. يمكن أن يتم هذا كمرحلة توسع ما قبل الماكرو ، _إذا لم يرغب المستخدمون أبدًا في yield داخل وحدات الماكرو ، ولكن هناك تركيبات مفيدة جدًا yield - في الماكرو مثل yield_from! . مع القيد بأنه يجب دعم yield s في وحدات الماكرو ، يتطلب هذا أن يكون await! ماكروًا مضمنًا على الأقل (إن لم يكن بناء الجملة الفعلي).
  3. دعم async fn على no_std . أعرف طريقتين لتنفيذ ذلك ، تتطلب كلتا الطريقتين async fn -created- Future و await لمشاركة معرف يتم تخزين المستيقظ فيه. الطريقة الوحيدة يمكن رؤية وجود معرف آمن صحيًا مشتركًا بين هذين المكانين إذا تم تنفيذ كليهما في المترجم.

أعتقد أن هناك القليل من الالتباس هنا - لم يكن القصد مطلقًا أن يكون await! قابلاً للتوسيع بشكل علني إلى غلاف حول المكالمات إلى yield . سيعتمد أي مستقبل للصيغة التي تشبه الماكرو await! على تطبيق لا يختلف عن تطبيق المترجم الحالي المدعوم من compile_error! ، assert! ، format_args! إلخ. وستكون قادرة على desugar إلى كود مختلف اعتمادًا على السياق.

الشيء الوحيد المهم الذي يجب فهمه هنا هو أنه لا يوجد فرق دلالي كبير بين أي من التركيبات المقترحة - إنها مجرد بناء جملة سطحي.

سأكتب بديلاً لحل بناء الجملة await .

بادئ ذي بدء ، أحب فكرة وضع await كمعامل postfix. لكن expression.await يشبه الحقل كثيرًا ، كما أشرنا سابقًا.

لذا فإن اقتراحي هو expression awaited . العيب هنا هو أن awaited لم يتم الاحتفاظ به بعد ككلمة رئيسية ، ولكنه أكثر لغة في اللغة الإنجليزية ومع ذلك لا توجد مثل هذه التعبيرات (أعني ، أشكال القواعد مثل expression [token] ) صالحة في Rust الآن ، لذلك يمكن تبرير ذلك.

ثم يمكننا كتابة expression? awaited لانتظار Result<Future,_> ، و expression awaited? لانتظار Future<Item=Result<_,_>> .

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

بينما لم يتم بيعي على الكلمة الرئيسية awaited ، أعتقد أنك تريد شيئًا ما.

الفكرة الرئيسية هنا هي: yield و await مثل return و ? .

يُرجع return x القيمة x ، بينما ينتج x x? x ، يعود مبكرًا إذا كان Err .
ينتج yield x قيمة x ، بينما ينتظر x awaited المستقبل x ، يعود مبكرًا إذا كان Pending .

هناك تناسق جميل لها. ربما يجب أن يكون await مشغلًا لما بعد الإصلاح.

let x = x.do_something() await.do_another_thing() await;
let x = x.foo(|| ...).bar(|| ... ).baz() await;

أنا لست من محبي بناء الجملة المنتظر postfix للسبب الدقيق الذي أظهرهcramertj للتو. يقلل من قابلية القراءة الإجمالية ، خاصة للتعبيرات الطويلة أو التعبيرات المتسلسلة. لا يعطي أي إحساس بالتداخل مثل await! / await . ليس لديها بساطة ? ، ونفد من الرموز لاستخدامها مع عامل postfix ...

أنا شخصياً ما زلت أؤيد await! للأسباب التي وصفتها سابقًا. إنه يشعر بالصدأ ولا معنى له.

يقلل من قابلية القراءة الإجمالية ، خاصة للتعبيرات الطويلة أو التعبيرات المتسلسلة.

في معايير Rustfmt ، يجب كتابة المثال

let x = x.do_something() await
         .do_another_thing() await;
let x = x.foo(|| ...)
         .bar(|| ...)
         .baz() await;

بالكاد أستطيع أن أرى كيف يؤثر ذلك على قابلية القراءة.

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

(لا يتطلب ذلك آلية ماكرو عامة كاملة لما بعد الإصلاح ؛ يمكن للمترجم أن يستخدم حالة خاصة .await!() .)

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

وأنا أيضا أعتقد ذلك

stream.for_each(async |item| {
    ...
}) await;

يقرأ بلطف أكثر من

await!(stream.for_each(async |item| {
    ...
});

بشكل عام ، سأدعم هذا.

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

لماذا ليس كلاهما؟

macro_rules! await {
    ($e:expr) => {{$e await}}
}

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

لذلك ، +1 لـ postfix.

أعتقد أنه يجب أن يكون لدينا أيضًا وظيفة بادئة ، بالإضافة إلى إصدار postfix.

بالنسبة إلى تفاصيل بناء جملة postfix ، فأنا لا أحاول أن أقول إن بناء جملة postfix الوحيد القابل للتطبيق هو .await!() ؛ أنا لست من محبي postfix await بمسافة رائدة.

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

بالنسبة لأولئك الذين لا يحبون مشغلي الكلمات الأساسية postfix ، يمكننا تحديد عامل تشغيل رمزي مناسب لـ await .

في الوقت الحالي ، نفد المشغلون من أحرف ASCII البسيطة لمشغل postfix. لكن ماذا عن

let x = do_something()⌛.do_somthing_else()⌛;

إذا كنا حقًا بحاجة إلى ASCII عادي ، فقد توصلت إلى (مستوحى من الشكل أعلاه)

let x = do_something()><.do_somthing_else()><;

أو (شكل مشابه في وضع أفقي)

let x = do_something()>=<.do_somthing_else()>=<;

فكرة أخرى ، هي جعل await قوسًا.

let x = >do_something()<.>do_something_else()<;

كل تلك الحلول ASCII، تشترك في قضية وفاة نفسها التي <..> يستخدم بشكل مفرط بالفعل وليس لدينا تحليل القضايا مع < و > . ومع ذلك ، قد يكون >< أو >=< أفضل لهذا لأنه لا يحتاج إلى مساحة داخل المشغل ، ولا يفتح < s في المركز الحالي.


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

let x = do_something()-await.do_something_else()-await;

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

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

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

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

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

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

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

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

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

يكون التعبير yield 42 من النوع ! ، بينما foo.await على النوع T حيث foo: impl Future<Output = T> . يقوم stjepang بإجراء القياس الصحيح مع ? و return هنا. await ليس مثل yield .

لماذا ليس كلاهما؟

macro_rules! await {
    ($e:expr) => {{$e await}}
}

ستحتاج إلى تسمية الماكرو بشيء آخر لأن await يجب أن يظل كلمة رئيسية حقيقية.


لعدة أسباب ، أنا أعارض البادئة await وحتى أكثر من ذلك فإن الكتلة تشكل await { ... } .

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

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

والأسوأ من ذلك أن يكون await { ... } . ستتحول هذه الصيغة ، إذا تم تنسيقها باستمرار وفقًا لـ rustfmt ، إلى:

    let x = await { // by analogy with `loop`
        foo.bar.baz.other_thing()
    };

لن يكون هذا مريحًا وسيؤدي إلى زيادة الطول الرأسي للوظائف بشكل كبير.


بدلاً من ذلك ، أعتقد أن الانتظار ، مثل ? ، يجب أن يكون postfix لأن ذلك يتناسب مع نظام Rust البيئي الذي يتمحور حول تسلسل الطريقة. تم ذكر عدد من صيغ ما بعد الإصلاح ؛ سأمر ببعضها:

  1. foo.await!() - هذا هو حل الماكرو postfix . على الرغم من أنني أؤيد بشدة وحدات ماكرو postfix ، إلا أنني أتفق مع cramertj في https://github.com/rust-lang/rust/issues/50547#issuecomment -454225040 أنه لا ينبغي لنا القيام بذلك ما لم نلتزم أيضًا بـ postfix وحدات الماكرو بشكل عام. أعتقد أيضًا أن استخدام ماكرو postfix بهذه الطريقة يعطي شعورًا ليس من الدرجة الأولى ؛ يجب علينا تجنب استخدام صيغة الماكرو لبناء لغة.

  2. foo await - هذا ليس سيئًا للغاية ، إنه يعمل بالفعل مثل عامل postfix ( expr op ) لكني أشعر أن هناك شيئًا مفقودًا في هذا التنسيق (أي أنه يشعر بأنه "فارغ") ؛ في المقابل ، يرفق expr? ? مباشرة على expr ؛ لا يوجد مكان هنا. هذا يجعل ? يبدو جذابًا بصريًا.

  3. foo.await - تم انتقاد هذا لأنه يبدو وكأنه حق الوصول إلى الحقل ؛ وهذا صحيح. ومع ذلك ، يجب أن نتذكر أن await هي كلمة رئيسية ، وبالتالي سيتم تمييز بناء الجملة على هذا النحو. إذا قرأت كود Rust في IDE الخاص بك أو ما يعادله على GitHub ، فسيكون await بلون أو جرأة مختلفة عن foo . باستخدام كلمة رئيسية مختلفة يمكننا توضيح ذلك:

    let x = foo.match?;
    

    عادةً ما تكون الحقول أيضًا أسماء بينما await فعل.

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

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

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

  4. foo# - هذا يستخدم sigil # لانتظار foo . أعتقد أن التفكير في sigil فكرة جيدة بالنظر إلى أن ? هو أيضًا علامة sigil ولأنه يجعل انتظار الوزن الخفيف. عند دمجه مع ? سيبدو الأمر وكأنه foo#? - يبدو ذلك جيدًا. ومع ذلك ، فإن # ليس له مبرر محدد. بدلا من ذلك ، هو مجرد سيجيل لا يزال متاحًا.

  5. foo@ - سيجيل آخر هو @ . عند الدمج مع ? ، نحصل على foo@? . أحد مبررات هذا sigil المحدد هو أنه يبدو a -ish ( @wait ).

  6. foo! - أخيرًا ، هناك ! . عند الدمج مع ? ، نحصل على foo!? . لسوء الحظ ، هذا له شعور معين تجاه WTF. ومع ذلك ، يبدو أن ! فرض القيمة التي تناسب "انتظار". هناك عيب واحد في أن foo!() هو بالفعل استدعاء ماكرو قانوني ، لذلك يجب كتابة (foo)!() انتظار واستدعاء دالة. سيؤدي استخدام foo! كصيغة إلى حرماننا من فرصة الحصول على وحدات ماكرو للكلمات الرئيسية (على سبيل المثال ، foo! expr ).

سيجيل واحد آخر هو foo~ . يمكن فهم الموجة على أنها "صدى" أو "تستغرق وقتًا". ومع ذلك ، لا يتم استخدامه في أي مكان بلغة Rust.

تم استخدام Tilde ~ في الأيام الخوالي للنوع المخصص للكومة: https://github.com/rust-lang/rfcs/blob/master/text/0059-remove-tilde.md

هل يمكن إعادة استخدام ? ؟ أم أن هذا سحر أكثر من اللازم؟ كيف سيبدو impl Try for T: Future ؟

parasyte نعم أتذكر. لكنها ما زالت قد ولت منذ فترة طويلة.

jethrogb لا توجد طريقة يمكنني من خلالها رؤية impl Try يعمل مباشرة ، ? صراحة return s نتيجة Try من الوظيفة الحالية بينما await يحتاج yield .

ربما يكون ? بغلاف خاص للقيام بشيء آخر في سياق المولد بحيث يمكن إما yield أو return اعتمادًا على نوع التعبير الذي يتم تطبيقه عليه ، لكنني لست متأكدًا من مدى قابلية فهم ذلك. أيضًا كيف سيتفاعل ذلك مع Future<Output=Result<...>> ، هل سيتعين عليك let foo = bar()??; لكليهما "انتظار" ثم الحصول على متغير Ok لـ Result ( أو هل ستعتمد المولدات ? على سمة ثلاثية يمكن أن yield ، return أو تتحلل إلى قيمة مع تطبيق واحد)؟

هذه الملاحظة الأخيرة بين قوسين تجعلني أعتقد أنه يمكن أن يكون عمليًا ، انقر لترى رسمًا سريعًا
enum GenOp<T, U, E> { Break(T), Yield(U), Error(E) }

trait TryGen {
    type Ok;
    type Yield;
    type Error;

    fn into_result(self) -> GenOp<Self::Ok, Self::Yield, Self::Error>;
}
باستخدام "foo؟" داخل المولد الذي يتوسع إلى شيء مثل (على الرغم من أن هذا به مشكلة ملكية ، ويحتاج أيضًا إلى تثبيت نتيجة "foo" المكدس)
loop {
    match TryGen::into_result(foo) {
        GenOp::Break(val) => break val,
        GenOp::Yield(val) => yield val,
        GenOp::Return(val) => return Try::from_error(val.into()),
    }
}

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

كان لدي نفس التفكير فيما يتعلق بإعادة استخدام ? مثلjethrogb.

@ Nemo157

لا توجد طريقة يمكنني من خلالها رؤية impl Try يعمل مباشرة ، ? صراحة return s نتيجة Try من الوظيفة الحالية أثناء الانتظار يحتاج إلى yield .

ربما أنا في عداد المفقودين بعض التفاصيل عن ? و Try سمة، ولكن أين / لماذا هو أن صراحة؟ أليس return في الإغلاق غير المتزامن هو نفسه yield أي حال ، مجرد انتقال مختلف للحالة؟

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

لا أفهم لماذا يجب أن يكون هذا مربكًا. إذا كنت تعتقد أن ? أنه "استمر أو تباعد" ، فهذا يبدو طبيعيًا ، IMHO. منحت ، تغيير سمة Try لاستخدام أسماء مختلفة لأنواع الإرجاع المرتبطة سيساعد.

أيضًا كيف سيتفاعل ذلك مع Future<Output=Result<...>> ، هل سيكون عليك let foo = bar()?? ؛

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

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

أنا لا أفهم هذا الجزء. هل يمكن أن تتطور؟

jethrogbrolandsteiner يمكن للبنية أن تنفذ كلاً من Try و Future . في هذه الحالة ، أيهما يجب أن يزيل غلافه ? ؟

jethrogbrolandsteiner يمكن للبنية أن تنفذ كلاً من المحاولة والمستقبل. في هذه الحالة ، أي واحد يجب؟ بسط؟

لا ، لا يمكن ذلك بسبب البطانية الضمنية حاول من أجل T: المستقبل.

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

لكن الأمر كله مجرد تظليل للدراجات ، أعتقد أنه يجب علينا تسوية بناء جملة الماكرو البسيط await!(my_future) على الأقل في الوقت الحالي

لكن الأمر كله مجرد تظليل للدراجات ، أعتقد أنه يجب علينا تسوية بناء جملة الماكرو البسيط await!(my_future) على الأقل في الوقت الحالي

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

الاستقرار عند الانتظار! (المستقبل) يستلزم أيضًا الانتظار حيث يتم التخلي عن كلمة رئيسية مما يجعل الاستخدام المستقبلي للانتظار ككلمة رئيسية أمرًا مستحيلًا.

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

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

أليس return في الإغلاق غير المتزامن هو نفسه yield أي حال ، مجرد انتقال مختلف للحالة؟

yield غير موجود في عملية الإغلاق غير المتزامن ، إنها عملية تم تقديمها أثناء التخفيض من بناء الجملة من async / await إلى المولدات / yield . في صيغة المولد الحالية ، يختلف yield تمامًا عن return ، إذا تم التوسيع ? قبل تحويل المولد ، فأنا لا أعرف كيف سيعرف وقت الإدراج a return أو yield .

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

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

لسوء الحظ ، لا أرى كيفية التعامل مع متغير سياق اليكر ...

أنا لا أفهم هذا الجزء. هل يمكن أن تتطور؟

يأخذ التحويل غير المتزامن متغير waker في وظيفة Future::poll إنشاؤها ، وهذا يحتاج بعد ذلك إلى أن يتم تمريره إلى عملية الانتظار المحولة. يتم التعامل مع هذا حاليًا من خلال متغير TLS المقدم بواسطة std كلاهما يحول المرجع ، إذا تم التعامل مع ? بدلاً من ذلك كنقطة إعادة إنتاج _ على مستوى المولدات _ فإن التحويل غير المتزامن يخسر بطريقة ما لإدخال مرجع المتغير هذا.

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

  • رأيي العام هو أن Rust قد امتد بالفعل ميزانيته غير المألوفة. سيكون من المثالي أن يكون مستوى السطح غير المتزامن / انتظار بناء الجملة مألوفًا لشخص قادم من JavaScript أو Python أو C # قدر الإمكان. سيكون من المثالي من هذا المنظور الاختلاف فقط بطرق ثانوية عن القاعدة. تختلف تراكيب Postfix على مدى تباعدها (على سبيل المثال ، foo await أقل تباعدًا من بعض sigil مثل foo@ ) ، لكنها كلها أكثر تباعدًا مما تنتظره البادئة.
  • أفضل أيضًا تثبيت البنية التي لا تستخدم ! . سيترك كل مستخدم يتعامل مع عدم التزامن / انتظار ليتساءل عن سبب كون الانتظار عبارة عن ماكرو بدلاً من تكوين تدفق تحكم عادي ، وأعتقد أن القصة هنا ستكون أساسًا "حسنًا ، لم نتمكن من اكتشاف بناء جملة جيد ، لذا استقرنا للتو مما يجعلها تبدو وكأنها ماكرو ". هذه ليست إجابة مقنعة. لا أعتقد أن الارتباط بين ! وتدفق التحكم يكفي حقًا لتبرير بناء الجملة هذا: أعتقد أن ! يعني على وجه التحديد المصروفات الكلية ، وهذا ليس كذلك.
  • أشك نوعًا ما في فائدة انتظار postfix بشكل عام (ليس تمامًا ، فقط نوعًا ما ). أشعر أن الرصيد يختلف قليلاً عن ? ، لأن الانتظار عملية أكثر تكلفة (أنت تسفر في حلقة حتى تصبح جاهزة ، بدلاً من مجرد التفريع والعودة مرة واحدة). أنا نوع من الشك في التعليمات البرمجية التي قد تنتظر مرتين أو ثلاث مرات في تعبير واحد ؛ يبدو لي أنه من الجيد أن أقول إنه يجب سحبها إلى ارتباطات السماح الخاصة بهم. لذا فإن المقايضة try! مقابل ? لا تجذبني بنفس القوة هنا. ولكن أيضًا ، سأكون منفتحًا على عينات التعليمات البرمجية التي يعتقد الناس حقًا أنه لا ينبغي سحبها إلى قائمة السماح وهي أكثر وضوحًا مثل سلاسل الطريقة.

ومع ذلك ، فإن بناء جملة postfix الأكثر قابلية للتطبيق الذي رأيته حتى الآن هو foo await :

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

ما زلت أميل إلى بناء جملة البادئة ، لكنني أعتقد أن بناء جملة await هو أول بناء جملة postfix يبدو وكأنه يحتوي على لقطة حقيقية بالنسبة لي.

Sidenote: من الممكن دائمًا استخدام الأقواس لتوضيح الأسبقية:

let x = (x.do_something() await).do_another_thing() await;
let x = x.foo(|| ...).bar(|| ... ).baz() await;

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

وكما ذكر earthengine من قبل ، فإن الإصدار متعدد الأسطر معقول جدًا (بدون أقواس إضافية):

let x = x.do_something() await
         .do_another_thing() await;

let x = x.foo(|| ...)
         .bar(|| ... )
         .baz() await;
  • سيكون من المثالي أن يكون مستوى السطح غير المتزامن / انتظار بناء الجملة مألوفًا لشخص قادم من JavaScript أو Python أو C # قدر الإمكان.

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

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

أتفق مع هذا الشعور ، لن يبدو .await!() الدرجة الأولى بدرجة كافية.

  • أشك نوعًا ما في فائدة انتظار postfix بشكل عام (ليس تمامًا ، فقط _sort of_). أشعر أن الرصيد يختلف قليلاً عن ? ، لأن الانتظار عملية أكثر تكلفة (أنت تسفر في حلقة حتى تصبح جاهزة ، بدلاً من مجرد التفريع والعودة مرة واحدة).

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

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

إذا كنت تشعر أنه يجب سحبها إلى ارتباطاتها let فلا يزال بإمكانك تحديد هذا الخيار باستخدام postfix await :

let temporary = some_computation() await?;

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

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

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

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

شعرت معظم رموز الفوشيه التي قرأتها بأنها غير طبيعية عند استخراجها في روابط let ومع let binding = await!(...)?; .

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

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

  • من الواضح أنه يحل مشكلة الأسبقية التي كان كل هذا حولها.

لا ، الأمر لا يتعلق فقط بالأولوية. سلاسل الطريقة لا تقل أهمية.

  • تبدو حقيقة أنها لا تعمل بشكل جيد مع تسلسل الأسلوب ميزة بالنسبة لي تقريبًا ، بدلاً من كونها عيبًا ، للأسباب التي أشرت إليها سابقًا.

لست متأكدًا من سبب عدم نجاحها مع تسلسل الطريقة.

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

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

منح أن كل خيار له جانب سلبي ، وأن أحدهم يجب أن ينتهي مع ذلك باختياره ... الشيء الذي يزعجني حول foo.await هو ذلك ، حتى لو افترضنا أنه لن يتم الخلط بينه وبين حقل هيكلي ، لا يزال يبدو مثل الوصول إلى حقل هيكلي. دلالة الوصول إلى المجال هو أنه لم يحدث شيء مؤثر بشكل خاص - إنها واحدة من أقل العمليات فاعلية في Rust. تنتظر في الوقت نفسه غير مؤثر للغاية، واحدة من أكثر عمليات إحداث الجانب (على حد سواء ينفذ عمليات I / O تراكمت في المستقبل، ولها آثار التحكم في التدفق). لذلك عندما قرأت foo.await.method() ، يخبرني عقلي أن أتخطى .await لأنه غير مثير للاهتمام نسبيًا ، ولا بد لي من استخدام الانتباه والجهد لتجاوز هذه الغريزة يدويًا.

لا يزال _ يبدو مثل _ الدخول إلى حقل هيكلي.

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

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

وأوافق بشدة على ذلك. await هي عملية تدفق تحكم مثل break أو return ، ويجب أن تكون واضحة. يبدو ترميز ما بعد الإصلاح المقترح غير طبيعي ، مثل if لبايثون: قارن بين if c { e1 } else { e2 } و e1 if c else e2 . إن رؤية عامل التشغيل في النهاية يجعلك تقوم بأخذ مزدوج ، بغض النظر عن أي تمييز في بناء الجملة.

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

هناك أيضًا مشكلة الألفة withoutboats المذكورة. يمكننا اختيار بناء جملة غريب ورائع إذا كان له بعض الفوائد الرائعة. هل تحتوي عليها postfix await ، بالرغم من ذلك؟

هل إبراز بناء الجملة ليس له تأثير / غير كافٍ على شكله والطريقة التي يعالج بها دماغك الأشياء؟

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

وأوافق بشدة على ذلك. await هي عملية تدفق تحكم مثل break أو return ، ويجب أن تكون واضحة.

ونحن نتفق. الرموز foo.await ، foo await ، foo# ، ... صريحة . ليس هناك انتظار ضمني يتم القيام به.

لا أرى أيضًا كيف يكون e.await أكثر اتساقًا مع بنية Rust من await!(e) أو await e .

بناء الجملة e.await في حد ذاته لا يتوافق مع بنية Rust ولكن postfix يناسب بشكل أفضل مع ? وكيف يتم هيكلة Rust APIs (تُفضل الطرق على الوظائف المجانية).

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

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

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

يمكننا اختيار بناء جملة غريب ورائع إذا كان له بعض الفوائد الرائعة. هل تحتوي عليها postfix await ؟

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

لا أعتقد أنك بحاجة إلى تغيير libsyntax على الإطلاق لـ .await لأنه يجب التعامل معه بالفعل كعملية ميدانية.

هذا ممتع.
لذا فإن الفكرة هي إعادة استخدام نهج self / super ... لكن للحقول بدلاً من مقاطع المسار.

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

#[derive(Default)]
struct S {
    r#await: u8
}

fn main() {
    let s = ;
    let z = S::default().await; //  Hmmm...
}

ليس هناك انتظار ضمني يتم القيام به.

ظهرت الفكرة عدة مرات في هذا الموضوع (اقتراح "الانتظار الضمني").

ليس لدينا وحدات ماكرو للتحكم في التدفق

هناك try! (والذي خدم غرضه جيدًا) ويمكن القول إنه تم إيقاف select! . لاحظ أن await "أقوى" من return ، لذا فليس من غير المعقول أن نتوقع ظهوره في الكود أكثر من ? 's return .

أجادل أنه يحدث بسبب تسلسل الأسلوب وتفضيل Rust لاستدعاءات الطريقة.

كما أن لديها تفضيلًا (أكثر وضوحًا) لمشغلي تدفق التحكم في البادئة.

في انتظار البريد؟ النحو ، إذا اقترن كـ (انتظار هـ)؟ يتعارض تمامًا مع كيفية ارتباط الاستراحة والعودة.

أنا أفضل await!(e)? ، await { e }? أو ربما { await e }? - لا أعتقد أنني رأيت الأخير تمت مناقشته ، ولست متأكدًا مما إذا كان يعمل.


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

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

ربما ينبغي التفكير في هذا بطريقة أخرى.

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

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

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

ربما يكون الحل الأفضل هو الأسهل ، وهو الماكرو الأصلي await! .

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

لا ارى كيف ...؟ await(foo)? / await { foo }? جيدًا تمامًا من حيث أسبقية المشغل وكيفية هيكلة واجهات برمجة التطبيقات في Rust - الجانب السلبي هو تكاثر الأقواس و (اعتمادًا على وجهة نظرك) التسلسل ، وليس كسر السوابق أو الوجود مربك.

هناك try! (والذي خدم غرضه جيدًا) ويمكن القول إنه تم إيقاف select! .

أعتقد أن كلمة المنطوق هنا مهملة . يعد استخدام try!(...) خطأ فادحًا في Rust 2018. إنه خطأ فادح الآن لأننا قدمنا ​​بنية أفضل من الدرجة الأولى وبعد الإصلاح.

لاحظ أن await "أقوى" من return ، لذا فليس من غير المعقول أن نتوقع ظهوره في الكود أكثر من ? 's return .

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

كما أن لديها تفضيلًا (أكثر وضوحًا) لمشغلي تدفق التحكم في البادئة.

تتم كتابة عوامل تدفق التحكم في البادئة في النوع ! . في هذه الأثناء ، عامل تدفق التحكم الآخر ? الذي يأخذ السياق impl Try<Ok = T, ...> ويمنحك T هو postfix.

لا ارى كيف ...؟ await(foo)? / await { foo }? جيدًا تمامًا من حيث أسبقية المشغل وكيفية هيكلة واجهات برمجة التطبيقات في Rust-

بناء الجملة await(foo) ليس هو نفسه await foo إذا كان الأقواس مطلوبًا للأول وليس للأخير. الأول لم يسبق له مثيل ، والأخير له الأسبقية في القضايا الواردة. ? كما ناقشنا هنا ، في منشور مدونة القارب ، وعلى Discord. يمثل بناء الجملة await { foo } مشكلة لأسباب أخرى (راجع https://github.com/rust-lang/rust/issues/50547#issuecomment-454313611).

الجانب السلبي هو تعبير الأقارب (حسب وجهة نظرك) والتسلسل ، وليس كسر السوابق أو التسبب في الارتباك.

هذا ما أعنيه بعبارة "واجهات برمجة التطبيقات منظمة". أعتقد أن طرق وأساليب التسلسل شائعة واصطلاحية في Rust. تتكون البادئة وصيغ الكتلة بشكل سيئ مع هؤلاء ومع ? .

قد أكون من الأقلية مع هذا الرأي ، وإذا كان الأمر كذلك ، فتجاهلني:

هل سيكون من العدل نقل مناقشة البادئة مقابل postfix إلى سلسلة محادثات Internals ، ثم العودة إلى هنا بالنتيجة؟ بهذه الطريقة يمكننا إبقاء مشكلة التتبع لتتبع حالة الميزة؟

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

هام للجميع: مزيد من مناقشة بناء الجملة await يجب أن تذهب هنا .

قفل مؤقتًا ليوم واحد لضمان حدوث مناقشة مستقبلية حول بناء الجملة await حول المشكلة المناسبة.

في الثلاثاء ، 15 يناير 2019 الساعة 07:10:32 صباحًا -0800 ، كتب باوان:

Sidenote: من الممكن دائمًا استخدام الأقواس لتوضيح الأسبقية:

let x = (x.do_something() await).do_another_thing() await;
let x = x.foo(|| ...).bar(|| ... ).baz() await;

هذا يقضي على الفائدة الأساسية لانتظار postfix: "فقط احتفظ
الكتابة / القراءة ". Postfix في انتظار ، مثل postfix ? ، يسمح بتدفق التحكم
لمواصلة التحرك من اليسار إلى اليمين:

foo().await!()?.bar().await!()

إذا كان await! بادئة ، أو رجوع عندما كان try! بادئة ، أو إذا كان لديك
لوضع أقواس ، ثم عليك العودة إلى الجانب الأيسر من
التعبير عند كتابته أو قراءته.

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

تقرير حالة انتظار Async:

http://smallcultfollowing.com/babysteps/blog/2019/03/01/async-await-status-report/


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

الإعلان عن مجموعة عمل التنفيذ

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

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

...

متى سيكون std::future::Future مستقرًا؟ هل يجب أن تنتظر انتظار المتزامن؟ أعتقد أنه تصميم جميل جدًا وأود أن أبدأ في نقل الكود إليه. (هل هناك شيم لاستخدامه في الاسطبل؟)

@ جرب الاطلاع على مشكلة التتبع الجديدة الخاصة بها: https://github.com/rust-lang/rust/issues/59113

مشكلة أخرى في المترجم غير متزامن / انتظار: https://github.com/rust-lang/rust/issues/59245

لاحظ أيضًا أنه يمكن تحديد https://github.com/rust-lang-nursery/futures-rs/issues/1199 في

يبدو أن هناك مشكلة في عمليات الإغلاق HRLB والإغلاق غير المتزامن: https://github.com/rust-lang/rust/issues/59337. (على الرغم من أن إعادة تخطي RFC لا تحدد في الواقع أن الإغلاق غير المتزامن يخضع لنفس التقاط عمر الوسيطة الذي تتمتع به الوظيفة غير المتزامنة).

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

لقد لاحظت للتو أن await!(fut) يتطلب حاليًا أن يكون fut Unpin : https://play.rust-lang.org/؟version=nightly&mode=debug&edition=2018&gist= 9c189fae3cfeecbb041f68f02f31893d

هل هذا متوقع؟ لا يبدو أنه موجود في RFC.

Ekleog هذا ليس await! يعطي الخطأ ، await! الناحية المفاهيمية يقوم دبابيس التكديس بالمستقبل الذي تم تمريره للسماح باستخدام العقود الآجلة !Unpin ( مثال سريع للملعب ). يأتي الخطأ من القيد المفروض على impl Future for Box<impl Future + Unpin> ، والذي يتطلب أن يكون المستقبل Unpin لمنعك من القيام بشيء مثل:

// where Foo: Future + !Unpin
let mut foo: Box<Foo> = ...;
Pin::new(&mut foo).poll(cx);
let mut foo = Box::new(*foo);
Pin::new(&mut foo).poll(cx);

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

انتظار ربما يكون بغلاف خاص للسماح Box<dyn Future> لأنه يستهلك المستقبل

ربما يجب إحياء السمة IntoFuture لـ await! ؟ Box<dyn Future> تنفيذ ذلك عن طريق التحويل إلى Pin<Box<dyn Future>> .

هنا يأتي الخطأ التالي مع عدم التزامن / انتظار: يبدو أن استخدام نوع مرتبط بمعامل نوع في نوع الإرجاع لاستنباط الاستدلال async fn : https://github.com/rust-lang/rust/ القضايا / 60414

بالإضافة إلى احتمال إضافة # 60414 إلى قائمة المنشورات العليا (لا أعرف ما إذا كان لا يزال قيد الاستخدام - ربما يكون من الأفضل الإشارة إلى ملصق github؟) ، أعتقد أن "Resolution of rust-lang / rfcs # 2418 ”يمكن وضع علامة عليه ، حيث أن سمة IIRC Future قد تم تثبيتها مؤخرًا.

لقد جئت للتو من منشور Reddit ويجب أن أقول إنني لا أحب بناء جملة postfix على الإطلاق. ويبدو أن غالبية Reddit لا يحبون ذلك أيضًا.

أنا أفضل الكتابة

let x = (await future)?

من قبول هذا النحو الغريب.

بالنسبة للتسلسل ، يمكنني إعادة بناء الكود الخاص بي لتجنب وجود أكثر من 1 await .

أيضًا ، يمكن لـ JavaScript في المستقبل القيام بذلك ( اقتراح خط الأنابيب الذكي ):

const x = promise
  |> await #
  |> x => x.foo
  |> await #
  |> x => x.bar

إذا تم تنفيذ البادئة await ، فهذا لا يعني أنه لا يمكن ربط await .

KSXGitHub ، هذا ليس المكان المناسب لهذه المناقشة حقًا ولكن المنطق موضح هنا وهناك أسباب جيدة جدًا لذلك تم التفكير فيها على مدار عدة أشهر من قبل العديد من الأشخاص https://boats.gitlab.io/blog/post / انتظار القرار /

KSXGitHub بينما أنا أيضًا لا أحب البنية النهائية ، فقد تمت مناقشتها على نطاق واسع في # 57640 ، https://internals.rust-lang.org/t/await-syntax-discussion-summary/ ، https: //internals.rust- lang.org/t/a-final-proposal-for-await-syntax/ وفي أماكن أخرى مختلفة. أعرب الكثير من الناس عن تفضيلهم هناك ، وأنت لا تقدم أي حجج جديدة للموضوع.

من فضلك لا تناقش قرارات التصميم هنا ، هناك موضوع لهذا الغرض الصريح

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

withoutboats حسب فهمي ، تم الاتفاق على الصيغة النهائية بالفعل ، ربما حان الوقت

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

آها ، تجاهل ذلك ، لقد اكتشفت للتو ملصق AsyncAwait-Blocking .

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

يحتوي MehrdadKhnzd https://github.com/rust-lang/rust/issues/62149 على معلومات حول تاريخ الإصدار المستهدف والمزيد

هل هناك خطة لتنفيذ Unpin تلقائيًا للعقود الآجلة التي تم إنشاؤها بواسطة async fn ؟

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

DoumanAsh أفترض أنه إذا لم يكن لدى غير المتزامن fn أي مراجع ذاتية نشطة عند نقاط العائد ، فمن الممكن أن يقوم المستقبل الذي تم إنشاؤه بتطبيق Unpin ، ربما؟

أعتقد أن ذلك يجب أن يكون مصحوبًا ببعض رسائل الخطأ المفيدة جدًا التي تقول "ليس Unpin بسبب _ هذا_ الاقتراض" + تلميح من "بدلاً من ذلك يمكنك وضع هذا المستقبل"

يشير التثبيت PR في # 63209 إلى أن "جميع أدوات الحظر مغلقة الآن." وتم هبوطه ليلًا في 20 أغسطس ، وبالتالي يتجه إلى خفض الإصدار التجريبي في وقت لاحق من هذا الأسبوع. من الجدير بالذكر أنه منذ 20 أغسطس تم تقديم بعض مشكلات الحظر الجديدة (كما تم تعقبها بواسطة علامة حظر AsyncAwait). اثنان من هؤلاء (# 63710 ، # 64130) يبدو أنهما لطيفان ولا يعوقان في الواقع الاستقرار ، ولكن هناك ثلاث قضايا أخرى (# 64391 ، # 64433 ، # 64477) تبدو جديرة بالمناقشة. ترتبط هذه المشكلات الثلاث الأخيرة ، وكلها تنشأ بسبب PR # 64292 ، والتي تم تعيينها نفسها لمعالجة مشكلة AsyncAwait-Blocking # 63832. لقد هبطت PR ، # 64584 ، في محاولة لمعالجة الجزء الأكبر من المشاكل ، لكن القضايا الثلاث لا تزال مفتوحة في الوقت الحالي.

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

bstrie نحن فقط نعيد استخدام "AsyncAwait-Blocking" لعدم وجود تسمية أفضل للإشارة إليها على أنها "ذات أولوية عالية" ، فهي لا تمنعها بالفعل. يجب علينا تجديد نظام وضع العلامات قريبًا لجعله أقل إرباكًا ، ccnikomatsakis.

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

earthengine لا أعتقد أن هذا تقييم عادل للوضع. كل القضايا التي ظهرت تستحق أخذها على محمل الجد. لن يكون من الجيد أن تهبط غير متزامن في انتظار أن يواجه الناس هذه المشكلات في محاولة لاستخدامها عمليًا :)

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات