Rust: مشكلة في تتبع "السمة الضمنية" (RFC 1522 ، RFC 1951 ، RFC 2071)

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

مشكلة تتبع جديدة = https://github.com/rust-lang/rust/issues/63066

حالة التنفيذ

تم تنفيذ الميزة الأساسية كما هو محدد في RFC 1522 ، ولكن كانت هناك مراجعات لا تزال بحاجة إلى عمل:

طلبات التعليقات

كان هناك عدد من RFCs فيما يتعلق بالسمات الضمنية ، والتي يتم تتبعها جميعًا بواسطة مشكلة التتبع المركزية هذه.

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

أثار التنفيذ أيضًا عددًا من الأسئلة المثيرة للاهتمام:

  • [x] ما هي أسبقية الكلمة الرئيسية impl عند تحليل الأنواع؟ مناقشة: 1
  • [] هل يجب أن نسمح بـ impl Trait بعد -> في fn أنواع السكر أو أقواس؟ # 45994
  • [] هل يتعين علينا فرض DAG عبر جميع الوظائف للسماح بالتسرب الآمن التلقائي ، أم يمكننا استخدام نوع من التأجيل. مناقشة: 1

    • الدلالات الحالية: DAG.

  • [x] كيف يمكننا دمج سمة ضمنية في regionck؟ مناقشة: 1 ، 2
  • [] هل يجب أن نسمح بتحديد الأنواع إذا كانت بعض المعلمات ضمنية وبعضها صريح؟ على سبيل المثال ، fn foo<T>(x: impl Iterator<Item = T>>) ؟
  • [] [بعض المخاوف بشأن استخدام سمة ضمنية متداخلة] (https://github.com/rust-lang/rust/issues/34511#issuecomment-350715858)
  • [x] هل يجب أن تكون الصيغة في الضمانة existential type Foo: Bar أم type Foo = impl Bar ؟ ( انظر هنا للمناقشة )
  • [] هل يجب أن تكون مجموعة "تحديد الاستخدامات" لـ existential type في أحد الضمانات مجرد عناصر للضمانات ، أو تتضمن عناصر متداخلة داخل وظائف الضم وما إلى ذلك؟ ( انظر هنا على سبيل المثال )
B-RFC-implemented B-unstable C-tracking-issue T-lang disposition-merge finished-final-comment-period

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

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

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

 fn does_some_operation() -> impl Future<Item=(), Error=()> {
-    let data_stored = Arc::new("hello");
+    let data_stored = Rc::new("hello");

     return some_long_operation.and_then(|other_stuff| {
         do_other_calculation_with(data_stored)
     });
}

(مثال أبسط: العمل ، التغييرات الداخلية تسبب الفشل )

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

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

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

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

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


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

ومع ذلك ، تُستخدم الوظائف والهياكل بشكل مختلف في قاعدة البيانات ، وهذه ليست نفس المشكلات.

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

عند تعديل الأجزاء الداخلية لوظيفة ما ، من الواضح بالتأكيد أنه يمكن للمرء أن يؤثر على كل من الأداء والصحة. ومع ذلك ، في Rust ، لا نحتاج إلى التحقق مما إذا كنا نعيد النوع الصحيح. الإعلانات عن الوظائف هي عقد ثابت يجب أن نتمسك به ، و rustc يراقب ظهورنا. إنه خط رفيع بين السمات التلقائية في البنيات وعودة الوظيفة ، لكن تغيير العناصر الداخلية لوظيفة ما هو أكثر روتينية. بمجرد أن يكون لدينا Future s بالكامل ، سيكون من الروتيني أيضًا تعديل الوظائف التي تعيد -> impl Future . ستكون هذه جميع التغييرات التي يحتاج المؤلفون إلى فحصها بحثًا عن تطبيقات الإرسال / المزامنة المعدلة إذا لم يلتقطها المترجم.

لحل هذه المشكلة ، يمكننا أن نقرر أن هذا عبء صيانة مقبول ، كما فعلت مناقشة RFC الأصلية . يوضح هذا القسم في السمة الضمنية المحافظة RFC أكبر الحجج لتسريب سمات السيارات ("OIBIT" هو الاسم القديم لسمات السيارات).

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


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

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

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


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

شكرًا على القراءة ، وآمل أن القرار النهائي يضع Rust في أفضل اتجاه يمكن أن يسير فيه.

ال 417 كومينتر

aturon هل يمكننا بالفعل وضع RFC في المستودع؟ ( علق mbrubeck هناك على أن هذه مشكلة.)

فعله.

المحاولة الأولى للتنفيذ هي # 35091 (ثانيًا ، إذا قمت بحساب فرعي من العام الماضي).

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

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

  • لا شيء يكشف النوع الملموس لـ impl Trait يجب أن يهتم بأعمار - البحث السريع عن Reveal::All يوحي بأن هذا هو الحال بالفعل في المترجم
  • يجب وضع حد على جميع الأنواع الملموسة من impl Trait في نوع الإرجاع للدالة ، بحيث يتجاوز عمر استدعاء هذه الوظيفة - وهذا يعني أن أي عمر يكون ، بالضرورة ، إما 'static أو إحدى معلمات عمر الوظيفة - _even_ إذا لم نتمكن من معرفة أي منها (مثل "الأقصر 'a و 'b ")
  • يجب أن نختار تباينًا لبارامترية العمر الضمني البالغة impl Trait (أي على جميع معلمات العمر في النطاق ، تمامًا كما هو الحال مع معلمات النوع): الثبات أسهل ويمنح قدرًا أكبر من التحكم في المستدعي ، بينما يتيح التناقض للمتصل القيام بذلك أكثر وسيتطلب التحقق من أن كل عمر في نوع الإرجاع في وضع مخالف (نفس الشيء مع معلمات النوع المتغير بدلاً من الثابت)
  • تتطلب آلية تسريب السمة التلقائية أن يتم وضع رابط السمة على النوع الخرساني ، في وظيفة أخرى - نظرًا لأننا محينا الأعمار وليس لدينا أي فكرة عن العمر الذي تذهب إليه ، يجب استبدال كل عمر تم محوه في النوع الخرساني مع متغير استدلال حديث يضمن ألا يكون أقصر من أقصر عمر من بين جميع معلمات العمر الفعلي ؛ تكمن المشكلة في حقيقة أن السمات الضمنية يمكن أن تتطلب في نهاية المطاف علاقات أقوى مدى الحياة (على سبيل المثال X<'a, 'a> أو X<'static> ) ، والتي يجب اكتشافها والخطأ فيها ، حيث لا يمكن إثباتها بالنسبة لهؤلاء. مدى الحياة

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

cc @ rust-lang / lang

eddyb

لكن الأعمار _ هي _ مهمة مع impl Trait - على سبيل المثال

fn get_debug_str(s: &str) -> impl fmt::Debug {
    s
}

fn get_debug_string(s: &str) -> impl fmt::Debug {
    s.to_string()
}

fn good(s: &str) -> Box<fmt::Debug+'static> {
    // if this does not compile, that would be quite annoying
    Box::new(get_debug_string())
}

fn bad(s: &str) -> Box<fmt::Debug+'static> {
    // if this *does* compile, we have a problem
    Box::new(get_debug_str())
}

لقد ذكرت ذلك عدة مرات في سلاسل RFC

نسخة خالية من السمات:

fn as_debug(s: &str) -> impl fmt::Debug;

fn example() {
    let mut s = String::new("hello");
    let debug = as_debug(&s);
    s.truncate(0);
    println!("{:?}", debug);
}

هذا إما UB أو لا يعتمد على تعريف as_debug .

@ arielb1 آه ، حسنًا ، لقد نسيت أن أحد الأسباب التي

@ arielb1 هل لدينا علاقة صارمة بالعمر الذي يمكن أن نضعه بين الأعمار الموجودة في نوع الخرسانة المحو مسبقًا والعمر المتأخر في التوقيع؟ بخلاف ذلك ، قد لا تكون فكرة سيئة مجرد إلقاء نظرة على العلاقات مدى الحياة وفشل insta أي مباشر أو _ غير مباشر_ 'a outlives 'b حيث يكون 'a _ أي شيء_ بخلاف 'static أو معلمة مدى الحياة و 'b يظهر في النوع الملموس لـ impl Trait .

آسف لأخذ بعض الوقت للكتابة مرة أخرى هنا. لذلك كنت أفكر
حول هذه المشكلة. شعوري هو أنه يتعين علينا ، في النهاية ، (و
تريد) تمديد regionck بنوع جديد من القيود - سأسميها
قيد \in ، لأنه يسمح لك بقول شيء مثل '0 \in {'a, 'b, 'c} ، مما يعني أن المنطقة المستخدمة لـ '0 يجب أن تكون كذلك
إما 'a أو 'b أو 'c . لست متأكدًا من أفضل طريقة للاندماج
هذا في حل نفسه - بالتأكيد إذا كانت مجموعة \in مفردة
مجموعة ، إنها مجرد علاقة متساوية (والتي لا نملكها حاليًا كـ a
شيء من الدرجة الأولى ، ولكن يمكن أن يتكون من حدين) ، ولكن
وإلا فإنه يجعل الأمور معقدة.

كل هذا يتعلق برغبتي في وضع مجموعة من قيود المنطقة
أكثر تعبيرا مما لدينا اليوم. بالتأكيد يمكن للمرء أن يؤلف ملف
قيد \in قيود OR و == . لكن بالطبع أكثر
يصعب حل القيود التعبيرية ولا يختلف الأمر عن \in .

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

pub fn foo<'a,'b>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> {...}

أعتقد أن أدق طريقة لإزالة الصغائر مقابل impl Trait هي على الأرجح ملف
نوع جديد:

pub struct FooReturn<'a, 'b> {
    field: XXX // for some suitable type XXX
}

impl<'a,'b> Iterator for FooReturn<'a,'b> {
    type Item = <XXX as Iterator>::Item;
}

الآن يجب أن يتصرف impl Iterator<Item=u32> في foo بالطريقة نفسها
FooReturn<'a,'b> سيتصرف. إنها ليست مباراة مثالية رغم ذلك. واحد
الاختلاف ، على سبيل المثال ، هو التباين ، كما ذكر إيديب - أنا كذلك
بافتراض أننا سنجعل الأنواع المشابهة لـ impl Foo ثابتة على النوع
معلمات foo . ومع ذلك ، فإن سلوك السمات التلقائية يعمل.
(هناك منطقة أخرى قد لا تكون فيها المطابقة مثالية وهي إذا أضفنا
القدرة على "اختراق" التجريد impl Iterator ، لذلك هذا الرمز
"داخل" التجريد يعرف النوع الدقيق - ثم يفرز
من إجراء عملية "تفكيك" ضمنية.)

في بعض النواحي ، يكون التطابق الأفضل هو اعتبار نوع من السمات الاصطناعية:

trait FooReturn<'a,'b> {
    type Type: Iterator<Item=u32>;
}

impl<'a,'b> FooReturn<'a,'b> for () {
    type Type = XXX;
}

الآن يمكننا اعتبار النوع impl Iterator مثل <() as FooReturn<'a,'b>>::Type . هذه أيضًا ليست مباراة كاملة ، لأننا
عادة سوف تطبيعه بعيدا. قد تتخيل استخدام التخصص
لمنع ذلك على الرغم من:

trait FooReturn<'a,'b> {
    type Type: Iterator<Item=u32>;
}

impl<'a,'b> FooReturn<'a,'b> for () {
    default type Type = XXX; // can't really be specialized, but wev
}

في هذه الحالة ، لن يتم تطبيع <() as FooReturn<'a,'b>>::Type ،
ولدينا تطابق أكثر قربًا. التباين ، على وجه الخصوص ، يتصرف
الصحيح؛ إذا أردنا الحصول على نوع ما "داخل"
التجريد ، سيكونون متماثلين لكن مسموح لهم بذلك
تطبيع. ومع ذلك ، هناك مشكلة: الأشياء ذات السمات التلقائية لا تفعل ذلك
عمل جيد. (قد نرغب في النظر في تنسيق الأمور هنا ،
فعلا.)

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

المكان الوحيد الذي يعتبر فيه هذا الإسقاط هو دليل مفيد حقًا
علاقة "العمر". إذا أردنا التحقق مما إذا كان <() as FooReturn<'a,'b>>::Type: 'x ، يخبرنا RFC 1214 أنه يمكننا إثبات ذلك
طالما أن 'a: 'x _ و_ 'b: 'x معلق. هذا ما أعتقده كيف نريد
للتعامل مع الأشياء من أجل السمة الضمنية أيضًا.

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

إذا نظرت إلى كلٍّ من بنية desugaring أو الضمنية ، فهناك امتداد
(ضمنيًا في الهيكل المعجمي لـ Rust) القيد الذي يمكن أن XXX
اسم فقط إما 'static أو مدى الحياة مثل 'a و 'b ، والتي
تظهر في توقيع الوظيفة. هذا هو الشيء الذي لسنا كذلك
النمذجة هنا. لست متأكدًا من أفضل طريقة للقيام بذلك - نوع ما
مخططات الاستدلال لها تمثيل مباشر أكثر لتحديد النطاق ، و
كنت أرغب دائمًا في إضافة ذلك إلى Rust ، لمساعدتنا في عمليات الإغلاق. ولكن
دعونا نفكر في مناطق دلتا الأصغر أولاً على ما أعتقد.

هذا هو المكان الذي يأتي منه القيد \in . يمكن للمرء أن يتخيل الإضافة
قاعدة فحص النوع التي (بشكل أساسي) FR(XXX) \subset {'a, 'b} -
مما يعني أن "المناطق الحرة" التي تظهر في XXX لا يمكن إلا أن تكون 'a و
'b . سينتهي الأمر بالترجمة إلى متطلبات \in لـ
مختلف المناطق التي تظهر في XXX .

لنلقِ نظرة على مثال حقيقي:

fn foo<'a,'b>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> {
    if condition { x.iter().cloned() } else { y.iter().cloned() }
}

هنا ، النوع إذا كان condition صحيحًا سيكون شيئًا مثل
Cloned<SliceIter<'a, i32>> . ولكن إذا كان condition خطأ ، فسنقوم بذلك
تريد Cloned<SliceIter<'b, i32>> . بالطبع في كلتا الحالتين سنفعل
ينتهي الأمر بشيء مثل (استخدام الأرقام لمتغيرات النوع / المنطقة):

Cloned<SliceIter<'0, i32>> <: 0
'a: '0 // because the source is x.iter()
Cloned<SliceIter<'1, i32>> <: 0
'b: '1 // because the source is y.iter()

إذا قمنا بإنشاء مثيل المتغير 0 إلى Cloned<SliceIter<'2, i32>> ،
لدينا '0: '2 و '1: '2 ، أو مجموعة كاملة من العلاقات الإقليمية
مثل:

'a: '0
'0: '2
'b: '1
'1: '2
'2: 'body // the lifetime of the fn body

إذن ما القيمة التي يجب أن نستخدمها مقابل '2 ؟ لدينا أيضا الإضافية
القيد أن '2 in {'a, 'b} . مع fn كما هو مكتوب ، أعتقد أننا
سيضطر إلى الإبلاغ عن خطأ ، لأنه لا 'a ولا 'b أ
الاختيار الصحيح. ومن المثير للاهتمام أنه إذا أضفنا القيد 'a: 'b ، فستكون هناك قيمة صحيحة ( 'b ).

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

حسنًا ، هذا بقدر ما حصلت عليه. =)

على PR # 35091 ، كتب @ arielb1 :

لا أحب نهج "التقاط جميع الأعمار في السمة الضمنية" وأفضل شيئًا مثل استبعاد مدى الحياة.

اعتقدت أنه سيكون من المنطقي أكثر أن نناقش هنا. @ arielb1 ، هل يمكنك توضيح المزيد حول ما <() as FooReturn<'a>>::Type بدلاً من <() as FooReturn<'a,'b>>::Type أو شيء من هذا القبيل؟

لا أعتقد أن قواعد الاستبعاد مدى الحياة كما هي ستكون دليلًا جيدًا في هذا الصدد: إذا اخترنا للتو عمر &self لتضمينه فقط ، فلن نكون بالضرورة قادرين على تضمين اكتب المعلمات من Self Struct ، ولا نوع المعلمات من الطريقة ، حيث قد تحتوي على شروط WF التي تتطلب منا تسمية بعض الأعمار الأخرى.

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

لم أشاهد تفاعلات impl Trait مع مناقشة الخصوصية في أي مكان.
الآن يمكن لـ fn f() -> impl Trait إرجاع نوع خاص S: Trait بشكل مشابه لكائنات السمات fn f() -> Box<Trait> . يمكن للكائنات من الأنواع الخاصة المشي بحرية خارج الوحدة النمطية الخاصة بهم في شكل مجهول.
يبدو هذا معقولًا ومرغوبًا فيه - النوع نفسه عبارة عن تفاصيل تنفيذ ، فقط واجهته المتاحة من خلال السمة العامة Trait متاحة للجمهور.
ومع ذلك ، هناك اختلاف واحد بين عناصر السمات و impl Trait . باستخدام كائنات السمات وحدها ، يمكن لجميع طرق السمات من الأنواع الخاصة الحصول على ارتباط داخلي ، وستظل قابلة للاستدعاء من خلال مؤشرات الوظيفة. باستخدام impl Trait s ، يمكن استدعاء طرق السمات للأنواع الخاصة مباشرة من وحدات الترجمة الأخرى. سيتعين على الخوارزمية التي تقوم بـ "استيعاب" الرموز أن تحاول بجهد أكبر لاستيعاب الأساليب فقط للأنواع غير مجهولة الهوية بـ impl Trait ، أو لتكون متشائمًا للغاية.

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

الطريقة "الصريحة" لكتابة foo ستكون

fn foo<'a: 'c,'b: 'c,'c>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> + 'c {
    if condition { x.iter().cloned() } else { y.iter().cloned() }
}

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

أنا أعارض إضافة استبعاد مدى الحياة حساسًا eddyb فقط في الحالة المحددة impl Trait وليس خلاف ذلك.

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

يبدو أنه ليس دائمًا ما يكفي "معلمة واحدة مدى الحياة":

fn foo<'a, 'b>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> {
    x.iter().chain(y).cloned()
}

في هذه الحالة ، يشير نوع المكرر المخفي إلى كل من 'a و 'b (على الرغم من أنه _ هو متغير في كلاهما ؛ لكن أعتقد أنه يمكننا التوصل إلى مثال ثابت).

لذا ناقشت أنا و aturon هذه المسألة إلى حد ما وأردت مشاركتها. يوجد حقًا زوجان من الأسئلة المتعامدة هنا وأريد الفصل بينهما. السؤال الأول هو "ما هي معلمات النوع / العمر التي يمكن استخدامها في النوع المخفي؟" من حيث (شبه-) desugaring إلى default type ، هذا ينزل إلى "نوع المعلمات التي تظهر في السمة التي نقدمها". لذلك ، على سبيل المثال ، إذا كانت هذه الوظيفة:

fn foo<'a, 'b, T>() -> impl Trait { ... }

سوف يتم التخلص منها إلى شيء مثل:

fn foo<'a, 'b, T>() -> <() as Foo<...>>::Type { ... }
trait Foo<...> {
  type Type: Trait;
}
impl<...> Foo<...> for () {
  default type Type = /* inferred */;
}

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

الإجابة الافتراضية التي استخدمناها هي "جميعهم" ، لذا هنا ... سيكون 'a, 'b, T (مع أي معلمات مجهولة قد تظهر). قد يكون هذا افتراضيًا معقولاً ، لكنه ليس _ ضروريًا_ أفضل خيار افتراضي. (كما أشار @ arielb1 .)

هذا له تأثير على علاقة الأعمار ، لأنه ، من أجل تحديد أن <() as Foo<...>>::Type (في إشارة إلى بعض إنشاء مثيل معين غير شفاف impl Trait ) يتجاوز العمر 'x ، يجب أن نظهر بشكل فعال أن ...: 'x (أي كل عمر ونوع معلمة).

هذا هو السبب في أنني أقول أنه لا يكفي النظر في معلمات العمر: تخيل أن لدينا بعض المكالمات إلى foo مثل foo::<'a0, 'b0, &'c0 i32> . هذا يعني أن جميع الأعمار الثلاثة ، '[abc]0 ، يجب أن تكون أكثر من 'x - بمعنى آخر ، طالما أن القيمة المرتجعة قيد الاستخدام ، فسيؤدي ذلك إلى استعارة جميع البيانات المقدمة في الوظيفة . ولكن ، كما توقع @ arielb1 ، يقترح Elision أن هذا سيكون عادة أطول من اللازم.

لذلك أتخيل أن ما نحتاجه هو:

  • لتسوية تقصير معقول ، ربما باستخدام الحدس من الاستبعاد ؛
  • للحصول على بناء جملة صريح عندما لا يكون الافتراضي مناسبًا.

aturon spitballed شيئًا مثل impl<...> Trait باعتباره الصيغة الصريحة ، والتي تبدو معقولة. لذلك ، يمكن للمرء أن يكتب:

fn foo<'a, 'b, T>(...) -> impl<T> Trait { }

للإشارة إلى أن النوع المخفي لا يشير في الواقع إلى 'a أو 'b ولكن فقط T . أو قد يكتب المرء impl<'a> Trait للإشارة إلى أنه لم يتم التقاط أي من 'b ولا T .

بالنسبة للإعدادات الافتراضية ، يبدو أن الحصول على المزيد من البيانات سيكون مفيدًا جدًا - لكن المنطق العام للتخلص يشير إلى أننا سنعمل جيدًا لالتقاط جميع المعلمات المسماة في النوع self ، عند الاقتضاء. على سبيل المثال ، إذا كان لديك fn foo<'a,'b>(&'a self, v: &'b [u8]) وكان النوع Bar<'c, X> ، فإن نوع self سيكون &'a Bar<'c, X> وبالتالي سنلتقط 'a و 'c و X افتراضيًا ، ولكن ليس 'b .


ملاحظة أخرى ذات صلة هي معنى ربط الحياة. أعتقد أن حدود عمر الصوت لها معنى موجود لا ينبغي تغييره: إذا كتبنا 'a impl (Trait+'a) فهذا يعني أن النوع المخفي T يفوق العمر 'a . وبالمثل يمكن كتابة impl (Trait+'static) للإشارة إلى عدم وجود مؤشرات مقترضة (حتى إذا تم تسجيل بعض الأعمار). عند استنتاج النوع المخفي T ، فإن هذا قد يعني ضمنيًا التزامًا مدى الحياة مثل $T: 'static ، حيث $T هو متغير الاستدلال الذي نقوم بإنشائه للنوع المخفي. سيتم التعامل مع هذا بالطريقة المعتادة. من منظور المتصل ، حيث يكون النوع المخفي مخفيًا جيدًا ، فإن الحد 'static سيسمح لنا باستنتاج أن impl (Trait+'static) يعيش بعد 'static حتى إذا كانت هناك معلمات عمر تم التقاطها.

هنا يتصرف تمامًا كما سيتصرف desugaring:

fn foo<'a, 'b, T>() -> <() as Foo<'a, 'b, 'T>>::Type { ... }
trait Foo<'a, 'b, T> {
  type Type: Trait + 'static; // <-- note the `'static` bound appears here
}
impl<'a, 'b, T> Foo<...> for () {
  default type Type = /* something that doesn't reference `'a`, `'b`, or `T` */;
}

كل هذا متعامد من الاستدلال. ما زلنا نريد (على ما أظن) إضافة فكرة "الاختيار من" القيد وتعديل الاستدلال ببعض الأساليب التجريبية ، وربما البحث الشامل (تشير التجربة من RFC 1214 إلى أن الاستدلال مع وجود احتياطي محافظ يمكن أن يقودنا بعيدًا جدًا ؛ لست على دراية بأشخاص يواجهون قيودًا في هذا الصدد ، على الرغم من وجود مشكلة على الأرجح في مكان ما). بالتأكيد ، إضافة حدود مدى الحياة مثل 'static أو 'a` قد تؤثر على الاستدلال ، وبالتالي تكون مفيدة ، لكن هذا ليس حلاً مثاليًا: لسبب واحد ، تكون مرئية للمتصل وتصبح جزءًا من API ، التي قد لا تكون مرغوبة.

الخيارات الممكنة:

عمر صريح مرتبط بإزالة معلمة الإخراج

مثل كائنات السمات اليوم ، تحتوي كائنات impl Trait على معلمة واحدة مرتبطة بمدى الحياة ، والتي يتم استنتاجها باستخدام قواعد elision.

العيب: غير مريح
ميزة: واضح

حدود عمر صريحة مع حذف "كل عام"

مثل كائنات السمات اليوم ، تحتوي الكائنات impl Trait على معلمة واحدة مرتبطة مدى الحياة.

ومع ذلك ، ينشئ elision معلمات مبكرة جديدة تتجاوز جميع المعلمات الصريحة:

fn foo<T>(&T) -> impl Foo
-->
fn foo<'total, T: 'total>(&T) -> impl Foo + 'total

الحرمان: يضيف معلمة مقيدة في وقت مبكر

أكثر.

واجهت هذه المشكلة مع ضمنية Trait + 'a والاقتراض: https://github.com/rust-lang/rust/issues/37790

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

https://play.rust-lang.org/؟gist=496ec05e6fa9d3a761df09c95297aa2a&version=nightly&backtrace=0

يقوم كل من ThingOne و ThingTwo بتنفيذ سمة Thing . build أنه سيعيد شيئًا ما باستخدام Thing ، وهو ما يفعله. ومع ذلك فهي لا تجمع. لذلك من الواضح أنني أسيء فهم شيء ما.

يجب أن يكون لهذا "الشيء" نوع ، ولكن في حالتك لديك نوعان متضاربان. اقترح nikomatsakis سابقًا إجراء هذا العمل بشكل عام عن طريق إنشاء مثل ThingOne | ThingTwo عند ظهور عدم تطابق في النوع.

eddyb هل يمكن أن تشرح بالتفصيل ThingOne | ThingTwo ؟ ألا تحتاج إلى الحصول على Box إذا كنا نعرف النوع فقط في وقت التشغيل؟ أم هو نوع من enum ؟

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

لقد أردت هذا النوع من الأشياء من قبل أيضًا. التعدادات المجهولة RFC: https://github.com/rust-lang/rfcs/pull/1154

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

هل يمكنك توضيح النصفين الآخرين الضمنيين لتلك الجمل؟ لا أفهم معظمها ، وأظن أنني أفتقد بعض السياق. هل كنت تستجيب بشكل ضمني لمشاكل أنواع النقابات؟ أن RFC هي ببساطة تعدادات مجهولة ، وليست أنواع اتحاد - (T|T) ستكون مشكلة تمامًا مثل Result<T, T> .

أوه ، لا داعي للقلق ، لقد أربكت المقترحات (تم تعليقها أيضًا على الهاتف المحمول حتى أقوم بفرز محرك الأقراص الثابتة الفاشل ، لذا أعتذر عن الظهور كما لو كان على Twitter).

أجد (الموضعية ، على سبيل المثال T|U != U|T ) التعدادات المجهولة مثيرة للاهتمام ، وأعتقد أنه يمكن تجربتها في مكتبة إذا كان لدينا أنواع مختلفة (يمكنك اتخاذ خطوة جانبية باستخدام hlist ) و أدوية كونستانت (كما سبق ، مع أرقام بينو).

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

لست متأكدًا مما إذا كان هذا هو المكان المناسب لطرح الأسئلة ، ولكن لماذا يلزم استخدام كلمة رئيسية مثل impl ؟ لم أجد مناقشة (قد يكون خطأي).

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

حيث

fn bar(a: &Foo) {
  ...
}

يعني "قبول إشارة إلى نوع ينفذ السمة Foo " أتوقعها

fn bar() -> Foo {
  ...
}

لتعني "إرجاع نوع يقوم بتنفيذ السمة Foo ". هل هذا مستحيل؟

@ kud1ing السبب هو عدم إزالة إمكانية وجود دالة تقوم بإرجاع النوع ذي الحجم الديناميكي Trait إذا تمت إضافة دعم قيم الإرجاع ذات الحجم الديناميكي في المستقبل. حاليًا Trait هو بالفعل DST صالح ، ليس من الممكن إرجاع التوقيت الصيفي لذا تحتاج إلى وضعه في الصندوق لجعله من النوع ذي الحجم.

تحرير: هناك بعض النقاش حول هذا الموضوع في مؤشر ترابط RFC المرتبط.

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

لقد جمعت من مناقشة RFC أن impl هو تطبيق نائب ، ولا يوجد impl مطلوب بشدة. هل هناك أي سبب لـ _not_ الرغبة في سمة impl إذا كانت القيمة المعادة ليست DST؟

أعتقد أن الأسلوب الضمني الحالي للتعامل مع "تسرب السمات التلقائي" يمثل مشكلة. يجب علينا بدلاً من ذلك فرض طلب DAG بحيث إذا قمت بتعريف fn fn foo() -> impl Iterator ، وكان لديك متصل fn bar() { ... foo() ... } ، فعلينا كتابة التحقق من foo() قبل bar() (حتى نعرف النوع المخفي). إذا ظهرت دورة ، فسنبلغ عن خطأ. هذا موقف متحفظ - ربما يمكننا أن نفعل ما هو أفضل - لكني أعتقد أن الأسلوب الحالي ، حيث نجمع التزامات السمات التلقائية ونتحقق منها في النهاية ، لا يعمل بشكل عام. على سبيل المثال ، لن يعمل بشكل جيد مع التخصص.

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

Nercury أنا لا أفهم. هل تسأل عما إذا كانت هناك أسباب لعدم الرغبة في أن يعني -> impl Trait fn foo() -> Trait -> impl Trait ؟

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

الفرق هو أن الدوال التي تقوم بإرجاع impl Trait تُرجع دائمًا نفس النوع - إنها في الأساس إرجاع نوع الاستدلال. IIUC ، الدوال التي تعيد فقط Trait ستكون قادرة على إرجاع أي تطبيق لتلك السمة ديناميكيًا ، لكن المتصل يجب أن يكون مستعدًا لتخصيص مساحة للقيمة المرتجعة عبر شيء مثل box foo() .

Nercury السبب البسيط هو أن بناء الجملة -> Trait له معنى بالفعل ، لذلك يتعين علينا استخدام شيء آخر لهذه الميزة.

لقد رأيت بالفعل أن الناس يتوقعون كلا النوعين من السلوك بشكل افتراضي ، ويظهر هذا النوع من الارتباك في كثير من الأحيان بما يكفي ، فأنا بصراحة أفضل أن لا يعني fn foo() -> Trait أي شيء (أو أن يكون تحذيرًا افتراضيًا) وكان هناك الكلمات الرئيسية لكل من "نوع معروف في وقت التجميع يمكنني اختياره ولكن المتصل لا يرى" الحالة و "كائن سمة يمكن إرساله ديناميكيًا إلى أي نوع تنفيذ سمة" ، على سبيل المثال fn foo() -> impl Trait مقابل fn foo() -> dyn Trait . لكن من الواضح أن تلك السفن قد أبحرت.

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

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

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

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

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

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

أظن أن أمر "تمرير التعداد التلقائي" لا يكون منطقيًا إلا لسمات أمان الكائن. هل ينطبق الشيء نفسه على impl Trait نفسه؟

rpjohnst ما لم يكن هذا متغير الطريقة الفعلي في بيانات وصفية

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

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

هذه نقطة مثيرة للاهتمام! لقد تم مناقشة ما هي الطريقة الصحيحة ل"desugar" impl سمة، وكان في الواقع على حافة مما يشير إلى أنه ربما أردنا أن التفكير في الأمر أكثر باسم "البنية مع حقل خاص" بدلا من "نوع الإسقاط مجردة " ترجمة. ومع ذلك ، يبدو أن هذا يعني شيئًا يشبه إلى حد كبير اشتقاق النوع الجديد المعمم ، والذي كان بالطبع مشهورًا بأنه غير سليم في هاسكل عند دمجه مع عائلات النوع . أعترف بعدم وجود فهم كامل لهذا الاختلال "في ذاكرة التخزين المؤقت" ولكن يبدو أنه يتعين علينا توخي الحذر الشديد هنا عندما نريد إنشاء تنفيذ تلقائي لسمة لنوع ما من الضمانات F<T> مقابل T .

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

المشكلة ، من حيث الصدأ

trait Foo {
    type Output;
    fn get() -> Self::Output;
}

fn foo() -> impl Foo {
    // ...
    // what is the type of return_type::get?
}

tl ؛ dr هو أن اشتقاق النوع الجديد المعمم (وهو) تم تنفيذه ببساطة عن طريق transmute ing the vtable - بعد كل شيء ، يتكون vtable من وظائف على النوع ، والنوع ونوعه الجديد لهما نفس التمثيل ، لذلك يجب أن تكون على ما يرام ، أليس كذلك؟ لكنها تتعطل إذا كانت هذه الوظائف تستخدم أيضًا أنواعًا يتم تحديدها بواسطة التفريع على مستوى النوع على الهوية (بدلاً من التمثيل) للنوع المحدد - على سبيل المثال ، استخدام وظائف النوع أو الأنواع المرتبطة (أو في Haskell ، GADTs). لأنه لا يوجد ضمان بأن تمثيلات هذه الأنواع متوافقة أيضًا.

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

(لست متأكدًا مما إذا كان هذا مرتبطًا بسؤالي الأصلي حول ما إذا كانت السمات المستخدمة مع impl Trait ، و / أو عبور التعداد التلقائي ، بحكم الضرورة ، يجب أن تكون آمنة من الكائنات ، على الرغم من ذلك؟)

rpjohnst يمكن للمرء أن يختار حالة التعداد لتحديد التكلفة:

fn foo() -> enum impl Trait { ... }

يكاد يكون هذا بالتأكيد طعامًا لـ RFC مختلف.

glaebhoerl نعم أمضيت بعض الوقت في البحث في القضية وشعرت أنه لن يكون مشكلة هنا ، على الأقل.

أعتذر إذا كان هذا شيئًا واضحًا ولكني أحاول فهم أسباب عدم ظهور impl Trait في أنواع الإرجاع من طرق السمات ، أو ما إذا كان ذلك منطقيًا على الإطلاق في المقام الأول؟ على سبيل المثال:

trait IterInto {
    type Output;
    fn iter_into(&self) -> impl Iterator<Item=impl Into<Self::Output>>;
}

aldanor هذا ناجحًا ، لكن لم يتم تنفيذه بعد.

من المنطقي

// What that trait would desugar into:
trait IterInto {
    type Output;
    type X: Into<Self::Output>;
    type Y: Iterator<Item=Self::X>;
    fn iter_into(&self) -> Self::Y;
}

// What an implementation would desugar into:
impl InterInto for FooList {
    type Output = Foo;
    // These could potentially be left unspecified for
    // a similar effect, if we want to allow that.
    type X = impl Into<Foo>;
    type Y = impl Iterator<Item=Self::X>;
    fn iter_into(&self) -> Self::Y {...}
}

على وجه التحديد ، فإن RHSs impl Trait في الأنواع المرتبطة بـ impl Trait for Type ستكون مشابهة للميزة المطبقة اليوم ، حيث لا يمكن فصلها عن الصدأ المستقر ، بينما في trait يمكن أن يكون.

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

fn foo() -> _ as Iterator<Item=u8> {}

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

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

اقترح استخدام بناء الجملة @Trait لاستبدال impl Trait ، كما هو مذكور هنا .

من الأسهل توسيعه إلى وظائف من النوع الآخر وفي تكوين مثل Box<@MyTrait> أو &@MyTrait .

@Trait مقابل any T where T: Trait و ~Trait مقابل some T where T: Trait :

fn compose<T, U, V>(f: @Fn(T) -> U, g: @Fn(U) -> V) -> ~Fn(T) -> V {
    move |x| g(f(x))
}

في fn func(t: T) -> V ، لا داعي للتمييز بين أي t أو بعض v ، وذلك كخاصية.

fn compose<T, U, V>(f: @Fn(T) -> U, g: @Fn(U) -> V) -> @Fn(T) -> V {
    move |x| g(f(x))
}

مازال يعمل.

@ JF-Liu أنا شخصياً أعارض وجود any و some في كلمة رئيسية واحدة / sigil لكنك محق تقنيًا في أنه يمكننا الحصول على علامة واحدة واستخدامها مثل impl Trait الأصلي

@ JF- Liueddyb كان هناك سبب لإزالة sigils من اللغة. لماذا هذا السبب لا ينطبق على هذه الحالة؟

يستخدم @ أيضًا في مطابقة الأنماط ، ولا تتم إزالته من اللغة.

الشيء الذي كان يدور في ذهني هو أن سيجيلات AFAIK تم استخدامها بشكل مفرط.

بناء الجملة bikesheding: أنا غير سعيد للغاية بشأن الترميز impl Trait ، لأن استخدام كلمة رئيسية (خط غامق في محرر) لتسمية نوع ما هو بصوت عالٍ جدًا. هل تتذكر ملاحظة بناء الجملة لـ C struct و Stroustroup بصوت عالٍ (الشريحة 14)؟

في https://internals.rust-lang.org/t/ideas-for-making-rust-easier-for-beginners/4761 ، اقترح konstin بناء جملة <Trait> . يبدو رائعًا حقًا ، خاصة في مواضع الإدخال:

fn take_iterator(iterator: <Iterator<Item=i32>>)

أرى أنه سيتعارض إلى حد ما مع UFCS ، لكن ربما يمكن حل ذلك؟

أشعر أيضًا باستخدام أقواس زاوية بدلاً من سمة ضمنية لتكون خيارًا أفضل ، على الأقل في موضع نوع الإرجاع ، على سبيل المثال:

fn returns_iter() -> <Iterator<Item=i32>> {...}
fn returns_closure() -> <FnOnce() -> bool> {...}

يتعارض بناء الجملة <Trait> مع الأدوية العامة ، ضع في اعتبارك:

Vec<<FnOnce() -> bool>> مقابل Vec<@FnOnce() -> bool>

إذا كان Vec<FnOnce() -> bool> مسموحًا به ، فإن <Trait> فكرة جيدة ، فهو يشير إلى التكافؤ مع معامل النوع العام. ولكن نظرًا لأن Box<Trait> يختلف عن Box<@Trait> ، يجب التخلي عن بناء الجملة <Trait> .

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

أنا فقط أدرك أنني اقترحت مجموعة شاملة لهذا التردد الراديوي في الخيط الداخلي (شكرًا على matklad لتوجيهي إلي هنا):

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

fn transform(iter: <Iterator>) -> <Iterator> {
    // ...
}

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

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

يجب أن يؤدي استخدام لون مختلف في تمييز بناء الجملة إلى حل المشكلة.

تناقش هذه الورقة التي أعدها Stroustrup الخيارات النحوية المماثلة لمفاهيم C ++ في القسم 7: http://www.stroustrup.com/good_concepts.pdf

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

fn transform(iter: <Iterator>) -> <Iterator>

يجب أن تكون إما معادلة لهذا

fn transform<T: Iterator, U: Iterator>(iter: T) -> U

أو يجب أن يكون معادلاً لهذا

fn transform(iter: impl Iterator) -> impl Iterator

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

ربما يجب أن يكون بناء الجملة متشابهًا ، لكن لا ينبغي أن يكون هو نفسه.

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

  • استخدم Type ، &Type ، Box<Type> لأنواع البيانات الملموسة ، الإرسال الثابت
  • استخدم @Trait ، &@Trait ، Box<@Trait> ومعلمة النوع العام لنوع البيانات المجردة ، الإرسال الثابت
  • استخدم &Trait ، Box<Trait> لنوع البيانات المجردة ، الإرسال الديناميكي

fn func(x: @Trait) يعادل fn func<T: Trait>(x: T) .
fn func<T1: Trait, T2: Trait>(x: T1, y: T2) يمكن كتابته ببساطة كـ fn func(x: <strong i="22">@Trait</strong>, y: @Trait) .
لا تزال هناك حاجة إلى المعلمة T في fn func<T: Trait>(x: T, y: T) .

struct Foo { field: <strong i="28">@Trait</strong> } يعادل struct Foo<T: Trait> { field: T } .

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

يمكنك إرجاع أي سمة ، الآن ، في Rust المستقر ، باستخدام الصيغة العامة الحالية. إنها ميزة مستخدمة بكثرة. يأخذ serde_json::de::from_slice &[u8] كمعامل ويعيد T where T: Deserialize .

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

للحصول على مثال مألوف أكثر ، يمكن أن يقوم Iterator::collect بإرجاع أي T where T: FromIterator<Self::Item> ، مما يعني ضمنيًا ترميزي المفضل: fn collect(self) -> any FromIterator<Self::Item> .

ماذا عن بناء الجملة
fn foo () -> _ : Trait { ... }
لقيم الإرجاع و
fn foo (m: _1, n: _2) -> _ : Trait where _1: Trait1, _2: Trait2 { ... }
للمعلمات؟

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

نعم ، يبدو التمسك بالكلمات الرئيسية الحالية أمرًا مثاليًا بالنسبة لي ؛ أود أن أرى impl للوجودية و for للعالمية.

أنا شخصياً أعارض دمج any و some في كلمة رئيسية واحدة / sigil

eddyb لن

((∃ T . F⟨T⟩) → R)  →  ∀ T . (F⟨T⟩ → R)

تحرير: إنه اتجاه واحد ، وليس تماثل.


غير ذي صلة: هل هناك أي اقتراح ذي صلة للسماح أيضًا بدولار impl Trait في وظائف متغيرة أخرى مثل

~ الصدأ
fn foo
(رد: F) -> R.
حيث F: FnOnce (ضمني SomeTrait) -> R {
رد الاتصال (create_something ())
}
~

في الوقت الحالي ، هذه ليست ميزة ضرورية ، حيث يمكنك دائمًا تخصيص وقت محدد لـ impl SomeTrait ، مما يضر بالقراءة ولكنه ليس بالأمر المهم.

ولكن إذا استقرت ميزة RFC 1522 ، فسيكون من المستحيل تعيين توقيع نوع لبرامج مثل المذكورة أعلاه إذا كان create_something ينتج impl SomeTrait (على الأقل بدون الملاكمة). أعتقد أن هذا يمثل مشكلة.

Rufflewind في العالم الحقيقي ، الأمور ليست واضحة تمامًا ، وهذه الميزة هي علامة تجارية محددة جدًا للوجودية (Rust لديها العديد الآن).

ولكن حتى ذلك الحين ، كل ما لديك هو استخدام التغاير لتحديد ما يعنيه impl Trait داخل وسيطات الوظيفة الخارجية.

هذا لا يكفي لـ:

  • باستخدام عكس الافتراضي
  • توضيح داخل نوع الحقل (حيث يكون كل من any و some مرغوب فيهما بنفس القدر)

Rufflewind هذا يبدو وكأنه impl Trait . أعلم أن هاسكل تستغل هذه العلاقة لاستخدام الكلمة الأساسية forall لتمثيل كل من الكليات والوجودية ، لكنها لا تعمل في السياق الذي نناقشه.

خذ هذا التعريف ، على سبيل المثال:

fn foo(x: impl ArgTrait) -> impl ReturnTrait { ... }

إذا استخدمنا القاعدة القائلة بأن " impl في الوسيطات هي عالمية ، وأن أنواع الإرجاع impl وجودية" ، فإن نوع عنصر الدالة foo منطقيًا هو هذا (في تدوين نوع الماكياج):

forall<T: ArgTrait>(exists<R: ReturnTrait>(fn(T) -> R))

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

forall<T: ArgTrait, R: ReturnTrait>(fn(T) -> R)

أو هذا:

exists<T: ArgTrait, R: ReturnTrait>(fn(T) -> R)

ولا يختزل أي من هذين إلى ما نريده بالقواعد المنطقية. حتى في نهاية المطاف any / some تفعل القبض تمييز هام لا يمكنك التقاط مع كلمة رئيسية واحدة. حتى أن هناك أمثلة معقولة في std حيث تريد عموميات في وضع الإرجاع. على سبيل المثال ، طريقة Iterator هذه:

fn collect<B>(self) -> B where B: FromIterator<Self::Item>;
// is equivalent to
fn collect(self) -> any FromIterator<Self::Item>;

ولا توجد طريقة لكتابتها بـ impl وقاعدة الوسيطة / الإرجاع.

tl ؛ يشير وجود impl السياق إلى كونه


للإشارة ، في تدويني ، تبدو علاقة Rufflewind المذكورة كما يلي:

fn(exists<T: Trait>(T)) -> R === forall<T: Trait>(fn(T) -> R)

وهو مرتبط بمفهوم الكائنات المميزة (الوجودية) التي تعادل العوامل العامة (العموم) ، ولكن ليس بهذا السؤال impl Trait .

ومع ذلك ، فأنا لا أؤيد بشدة استخدام any / some بعد الآن. أردت أن أكون دقيقًا بشأن ما نتحدث عنه ، و any / some تتمتع بهذا اللطف النظري والمرئي ، لكنني سأكون على ما يرام باستخدام impl مع السياق قاعدة. أعتقد أنه يغطي جميع الحالات الشائعة ، ويتجنب مشكلات قواعد الكلمات الأساسية السياقية ، ويمكننا الرجوع إلى معلمات النوع المسماة للباقي.

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

باختصار ، سأكون سعيدًا بما يلي:

  • impl Trait كاختصار لكل من العموميات والوجودية (السياقية).
  • معلمات النوع المسماة باعتبارها الخط العام الكامل لكل من المسلمات والوجودية. (أقل شيوعًا ضروريًا.)

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

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

~ الصدأ(ضمني MyTrait)~

هو مجرد سكر نحوي ل

~ الصدأ(موجودتي)~

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

~ الصدأfn (موجودT) -> (موجودص)~

بعد ذلك ، إذا قمت بسحب المحدد الكمي من وسيطة الوظيفة ، فإنه يصبح

~ الصدألfn (T) -> (موجودص)~

ذلك على الرغم من T هو دائما وجودية بالنسبة إلى نفسه، يبدو كما قريب عالمية لنوع وظيفة كاملة.


IMO ، أعتقد أن impl قد يصبح أيضًا الكلمة الأساسية الواقعية للأنواع الوجودية. في المستقبل ، ربما يمكن للمرء أن يبني أنواعًا وجودية أكثر تعقيدًا مثل:

~~ صدأ(ضمني(Vec، T))~ ~

قياسا على الأنواع العالمية (عبر HRTB)

~ الصدأ(لـ <'a> FnOnce (&' a T))~

Rufflewind هذا العرض لا يعمل لأن fn(T) -> (exists<R: ReturnTrait>(R)) لا يعادل منطقياً exists<R: ReturnTrait>(fn(T) -> R) ، وهو ما يعنيه حقًا نوع الإرجاع impl Trait .

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

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

solson نعم أنت على حق: لا يمكن طرح الوجود. هذا لا يحمل بشكل عام:

(T → ∃R. f(R))  ⥇  ∃R. T → f(R)

في حين أن هذه تنطبق بشكل عام:

(∃R. T → f(R))  →   T → ∃R. f(R)
(∀A. g(A) → T)  ↔  ((∃A. g(A)) → T)

آخرها مسؤول عن إعادة تفسير الوجود في الحجج على أنها أدوية جنيسة.

تحرير: عفوا، (∀A. g(A) → T) → (∃A. g(A)) → T يفعل الانتظار.

لقد قمت بنشر RFC مع اقتراح مفصل لتوسيع واستقرار impl Trait . إنه يعتمد على الكثير من المناقشة حول هذا الموضوع وخيوط سابقة.

تجدر الإشارة إلى أنه تم قبول https://github.com/rust-lang/rfcs/pull/1951 .

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

وجد في # 43869 أن الدالة -> impl Trait لا تدعم جسمًا متشعبًا تمامًا:

fn do_it_later_but_cannot() -> impl Iterator<Item=u8> { //~ ERROR E0227
    unimplemented!()
}

هل هذا متوقع (بما أن ! لا يتضمن Iterator ) ، أو يعتبر خطأ؟

ماذا عن تحديد الأنواع المستنبطة ، التي لا يمكن استخدامها فقط كقيم إرجاع ، ولكن كأي شيء (على ما أظن) يمكن استخدام نوع في الوقت الحالي؟
شيء مثل:
type Foo: FnOnce() -> f32 = #[infer];
أو بكلمة رئيسية:
infer Foo: FnOnce() -> f32;

يمكن بعد ذلك استخدام النوع Foo كنوع إرجاع أو نوع معلمة أو أي نوع آخر يمكن استخدامه من أجله ، ولكن سيكون من غير القانوني استخدامه في مكانين مختلفين يتطلبان نوعًا مختلفًا ، حتى لو كان ذلك النوع ينفذ FnOnce() -> f32 في كلتا الحالتين. على سبيل المثال ، لن يتم ترجمة ما يلي:

infer Foo: FnOnce() -> f32;

fn return_closure() -> Foo {
    || 0.1
}

fn return_closure2() -> Foo {
    || 0.2
}

fn main() {
    println!("{:?}, {:?}", return_closure()(), return_closure2()());
}

لا ينبغي تجميع هذا لأنه حتى أنواع الإرجاع من return_closure و return_closure2 كلاهما FnOnce() -> f32 ، أنواعها مختلفة بالفعل ، لأنه لا يوجد إغلاقان لهما نفس النوع في Rust . لتجميع ما سبق ، ستحتاج إلى تحديد نوعين مختلفين من الاستنتاجات:

infer Foo: FnOnce() -> f32;
infer Foo2: FnOnce() -> f32; //Added this line

fn return_closure() -> Foo {
    || 0.1
}

fn return_closure2() -> Foo2 { //Changed Foo to Foo2
    || 0.2
}

fn main() {
    println!("{:?}, {:?}", return_closure()(), return_closure2()());
}

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

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

cramertj آه لهذا السبب صمتت هذه القضية ..

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

بالنسبة للسياق ، فإن الفكرة هي تقريبًا

fn foo<'a, 'b, T, U>() -> impl Debug + 'a

سيكون (نوعًا ما) مفصولًا عن شيء من هذا القبيل

anonymous type Foo<'a, T, U>: Debug + 'a
fn foo<'a, 'b, T, U>() -> Foo<'a, T, U>

لاحظ أنه في هذا النموذج ، يمكنك معرفة المعلمات العامة التي تم التقاطها لأنها تظهر كوسيطات لـ Foo - على وجه الخصوص ، لم يتم التقاط 'b ، لأنها لا تظهر في مرجع السمة في بأي حال من الأحوال ، لكن معلمات النوع T و U هي دائمًا.

على أي حال ، في الوقت الحالي في المترجم ، عندما يكون لديك مرجع impl Debug ، فإننا نقوم بإنشاء معرف def يمثل - بشكل فعال - هذا النوع المجهول. ثم لدينا الاستعلام generics_of ، والذي يحسب المعلمات العامة. في الوقت الحالي ، يعيد هذا نفس سياق "التضمين" - أي الوظيفة foo . هذا ما نريد تغييره.

على "الجانب الآخر" ، أي في توقيع foo ، فإننا نمثل impl Foo كـ TyAnon . هذا صحيح بشكل أساسي - يمثل TyAnon المرجع إلى Foo الذي نراه في الوصف أعلاه. لكن الطريقة التي نحصل بها على "البدائل" لهذا النوع هي استخدام وظيفة "الهوية" ، وهي خاطئة بشكل واضح - أو على الأقل لا تعمم.

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

أعتقد أن ما يجب أن نفعله هو شيء من هذا القبيل:

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

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

ربما يكون الخيار الأسهل والأفضل هو استخدام Region<'tcx> ، ولكن سيتعين عليك تغيير أعماق مؤشر debruijn أثناء الانتقال إلى "إلغاء" أي مجلد تم تقديمه. ربما يكون هذا هو الخيار الأفضل بالرغم من ذلك.

لذلك ، نظرًا لأننا نحصل على عمليات رد نداء في visit_lifetime ، فإننا سنحولها إلى Region<'tcx> معبرًا عنها في العمق الأولي (سيتعين علينا التتبع أثناء مرورنا عبر المجلدات). سنقوم بتجميعها في ناقل ، وإزالة التكرارات.

عندما ننتهي ، لدينا شيئين:

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

حسنًا ، آسف إذا كان هذا غامضًا. لا يمكنني معرفة كيفية قولها بشكل أكثر وضوحًا. شيء لست متأكدًا منه - في الوقت الحالي ، أشعر أن تعاملنا مع المناطق معقد جدًا ، لذلك ربما توجد طريقة لإعادة تشكيل الأشياء لجعلها أكثر اتساقًا؟ أراهن بـ 10 دولارات على أن لدى eddyb بعض الأفكار هنا. ؛)

nikomatsakis أعتقد أن الكثير من ذلك مشابه لما أخبرته cramertj ، لكن أكثر

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

trait Foo<T> { }
impl Foo<()> for () { }
fn foo() -> impl Foo<impl Debug> {
  ()
}

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

على وجه التحديد ، من الواضح كيف نستنتج النوع الذي يتم إرجاعه هنا ( () ). ليس من الواضح كيف نستنتج نوع المعلمة impl Debug . بمعنى ، يمكنك التفكير في قيمة الإرجاع هذه على أنها شيء مثل -> ?T حيث ?T: Foo<?U> . علينا أن نستنتج قيم ?T و ?U بناءً فقط على حقيقة أن ?T = () .

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

يمكن أن يحدث هذا في العديد من السيناريوهات في Rust - وهو أمر مقلق بدرجة كافية ، ولكنه متعامد - ولكن هناك شيئًا مختلفًا حول حالة impl Trait . في حالة impl Trait ، ليس لدينا طريقة للمستخدم لإضافة التعليقات التوضيحية من النوع لتوجيه الاستدلال على طول! كما أننا لا نمتلك بالفعل خطة لمثل هذه الطريقة. الحل الوحيد هو تغيير واجهة fn إلى impl Foo<()> أو شيء آخر صريح.

في المستقبل ، باستخدام abstract type ، يمكن للمرء أن يتخيل السماح للمستخدمين بإعطاء القيمة المخفية صراحة (أو ربما مجرد تلميحات غير كاملة ، باستخدام _ ) ، والتي يمكن أن تساعد في الاستدلال ، مع الاحتفاظ تقريبًا بـ نفس الواجهة العامة

abstract type X: Debug = ();
fn foo() -> impl Foo<X> {
  ()
}

ومع ذلك ، أعتقد أنه سيكون من الحكمة تجنب تثبيت الاستخدامات "المتداخلة" للسمات الضمنية الوجودية ، باستثناء ارتباطات النوع المرتبطة (على سبيل المثال ، impl Iterator<Item = impl Debug> لا يعاني من هذه الغموض).

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

ربما يمكن أن تبدو مثل UFCS؟ على سبيل المثال <() as Foo<()>> - عدم تغيير النوع مثل as ، فقط قم بإلغاء اللبس. هذا بناء جملة غير صالح حاليًا حيث يتوقع أن يتبعه :: وأكثر.

لقد وجدت للتو حالة مثيرة للاهتمام بخصوص نوع الاستدلال مع سمة ضمنية لـ Fn :
الكود التالي يجمع ما يرام :

fn op(s: &str) -> impl Fn(i32, i32) -> i32 {
    match s {
        "+" => ::std::ops::Add::add,
        "-" => ::std::ops::Sub::sub,
        "<" => |a,b| (a < b) as i32,
        _ => unimplemented!(),
    }
}

إذا قمنا بالتعليق على السطر الفرعي ، فسيتم طرح خطأ تجميع :

error[E0308]: match arms have incompatible types
 --> src/main.rs:4:5
  |
4 | /     match s {
5 | |         "+" => ::std::ops::Add::add,
6 | | //         "-" => ::std::ops::Sub::sub,
7 | |         "<" => |a,b| (a < b) as i32,
8 | |         _ => unimplemented!(),
9 | |     }
  | |_____^ expected fn item, found closure
  |
  = note: expected type `fn(_, _) -> <_ as std::ops::Add<_>>::Output {<_ as std::ops::Add<_>>::add}`
             found type `[closure@src/main.rs:7:16: 7:36]`
note: match arm with an incompatible type
 --> src/main.rs:7:16
  |
7 |         "<" => |a,b| (a < b) as i32,
  |                ^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

oberien لا يبدو هذا مرتبطًا بـ impl Trait - إنه صحيح في الاستدلال بشكل عام. جرب هذا التعديل الطفيف لمثالك:

fn main() {
    let _: i32 = (match "" {
        "+" => ::std::ops::Add::add,
        //"-" => ::std::ops::Sub::sub,
        "<" => |a,b| (a < b) as i32,
        _ => unimplemented!(),
    })(5, 5);
}

يبدو أن هذا مغلق الآن:

ICEs عند التفاعل مع elision

الشيء الوحيد الذي لا أراه مدرجًا في هذه المشكلة أو في المناقشة هو القدرة على تخزين الإغلاق والمولدات - التي لا يوفرها المتصل - في حقول البنية. في الوقت الحالي ، هذا ممكن ولكنه يبدو قبيحًا: عليك إضافة معلمة نوع إلى البنية لكل حقل إغلاق / منشئ ، ثم في توقيع دالة المنشئ ، استبدل معلمة النوع بـ impl FnMut/impl Generator . هذا مثال ، وهو يعمل ، وهو أمر رائع جدًا! لكنه يترك الكثير مما هو مرغوب فيه. سيكون من الأفضل التخلص من معلمة النوع:

struct Counter(impl Generator<Yield=i32, Return=!>);

impl Counter {
    fn new() -> Counter {
        Counter(|| {
            let mut x: i32 = 0;
            loop {
                yield x;
                x += 1;
            }
        })
    }
}

قد لا يكون impl Trait الطريقة الصحيحة للقيام بذلك - ربما تكون أنواع مجردة ، إذا قرأت وفهمت RFC 2071 بشكل صحيح. ما نحتاجه هو شيء يمكننا كتابته في تعريف البنية بحيث يمكن استنتاج النوع الفعلي ( [generator@src/main.rs:15:17: 21:10 _] ).

أعتقد أن الأنواع المجردة

abstract type MyGenerator: Generator<Yield = i32, Return = !>;

pub struct Counter(MyGenerator);

impl Counter {
    pub fn new() -> Counter {
        Counter(|| {
            let mut x: i32 = 0;
            loop {
                yield x;
                x += 1;
            }
        })
    }
}

هل هناك مسار احتياطي إذا كان شخص آخر هو impl Generator الذي أريد وضعه في هيكلي ، لكنهم لم يصنعوا abstract type لي لاستخدامه؟

scottmcm لا يزال بإمكانك التصريح عن abstract type :

// library crate:
fn foo() -> impl Generator<Yield = i32, Return = !> { ... }

// your crate:
abstract type MyGenerator: Generator<Yield = i32, Return = !>;

pub struct Counter(MyGenerator);

impl Counter {
    pub fn new() -> Counter {
        let inner: MyGenerator = foo();
        Counter(inner)
    }
}

cramertj انتظر ، أنواع الملخصات موجودة بالفعل ليلاً ؟! أين العلاقات العامة؟

alexreg لا ، ليسوا كذلك.

تحرير: تحياتي زوار المستقبل! تم حل المشكلة أدناه


أود أن ألفت الانتباه إلى حالة الاستخدام غير التقليدية هذه التي تظهر في # 47348

use ::std::ops::Sub;

fn test(foo: impl Sub) -> <impl Sub as Sub>::Output { foo - foo }

هل يجب السماح بإعادة إسقاط على impl Trait مثل هذا؟ (لأنه حاليًا ، __ هو .__)

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

تضمين التغريدة يبدو أنه يمثل مشكلة ، لنفس السبب الذي يمثل مشكلة impl Foo<impl Bar> . في الأساس ، ليس لدينا أي قيود حقيقية على النوع المعني - فقط على الأشياء المتوقعة منه.

أعتقد أننا نريد إعادة استخدام المنطق حول "معلمات النوع المقيدة" من الضمانات. باختصار ، يجب أن يؤدي تحديد نوع الإرجاع إلى "تقييد" impl Sub . الوظيفة التي أشير إليها هي هذه:

https://github.com/rust-lang/rust/blob/a0dcecff90c45ad5d4eb60859e22bb3f1b03842a/src/librustc_typeck/constrained_type_params.rs#L89 -L93

قدر ضئيل من الفرز للأشخاص الذين يحبون مربعات الاختيار:

  • # 46464 انتهى -> خانة الاختيار
  • # 48072 تم -> خانة الاختيار

تضمينrfcbot fcp

أقترح تثبيت ميزتي conservative_impl_trait و universal_impl_trait ، مع تغيير واحد معلق (إصلاح لـ https://github.com/rust-lang/rust/issues/46541).

الاختبارات التي توثق الدلالات الحالية

يمكن العثور على اختبارات هذه الميزات في الدلائل التالية:

تمرير تشغيل / سمة ضمنية
واجهة المستخدم / سمة ضمنية
ترجمة فشل / ضمني سمة

الأسئلة التي تم حلها أثناء التنفيذ

تم حل تفاصيل تحليل impl Trait في RFC 2250 وتم تنفيذها في https://github.com/rust-lang/rust/pull/45294.

تم حظر impl Trait من موضع النوع المتداخل غير المرتبط وبعض مواضع المسار المؤهلة لمنع الغموض. تم تنفيذ ذلك في https://github.com/rust-lang/rust/pull/48084.

الميزات غير المستقرة المتبقية

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

اقترح عضو الفريق cramertj دمج هذا. الخطوة التالية هي المراجعة من قبل بقية الفرق الموسومة:

  • [x]aturon
  • [x]cramertj
  • [x]eddyb
  • [x]nikomatsakis
  • [x]nrc
  • [x]pnkfelix
  • [x] @ بدون قوارب

لا مخاوف مدرجة حاليا.

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

راجع هذا المستند للحصول على معلومات حول الأوامر التي يمكن أن يقدمها لي أعضاء الفريق.

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

ما هي حالة استخدام impl Trait في مواضع الوسيطة / الإرجاع في دوال السمات ، أو في بناء جملة Fn ، لهذه المسألة؟

alexreg Return-position impl Trait في السمات محظورة على RFC ، على الرغم من أن RFC 2071 سيسمح بوظيفة مماثلة بمجرد تنفيذها. لا يتم حظر Argument-position impl Trait في السمات على أي ميزات فنية أعلم بها ، ولكن لم يُسمح بها صراحةً في RFC لذلك تم حذفها في الوقت الحالي.

تم حظر بناء الجملة impl Trait في موضع الوسيطة Fn على مستوى النوع HRTB ، نظرًا لأن بعض الأشخاص يعتقدون أن T: Fn(impl Trait) يجب أن يكون T: for<X: Trait> Fn(X) . لم يتم حظر بناء الجملة impl Trait في موضع الإرجاع Fn لأي سبب تقني أعلم به ، ولكن لم يُسمح به في RFC بانتظار المزيد من أعمال التصميم - أتوقع أن انظر RFC آخر أو على الأقل FCP منفصل قبل تثبيت هذا.

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

قلق: https://github.com/rust-lang/rust/issues/34511#issuecomment -322340401 لا يزال كما هو. هل من الممكن السماح بما يلي؟

fn do_it_later_but_cannot() -> impl Iterator<Item=u8> { //~ ERROR E0227
    unimplemented!()
}

@ kennytm لا ، هذا غير ممكن في الوقت الحالي. تقوم هذه الوظيفة بإرجاع ! ، والتي لا تنفذ السمة التي قدمتها ، ولا توجد لدينا آلية لتحويلها إلى النوع المناسب. هذا أمر مؤسف ، ولكن لا توجد طريقة سهلة لإصلاحه في الوقت الحالي (بصرف النظر عن تطبيق المزيد من السمات مقابل ! ). كما أنه متوافق مع الإصدارات السابقة لإصلاحه في المستقبل ، لأن جعله يعمل سيسمح بصرامة بتجميع المزيد من التعليمات البرمجية.

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

الدافع هو منع libs من كسر توربوفيش المستخدمين عن طريق تغيير وسيطة من عامة صريحة إلى impl Trait . ليس لدينا حتى الآن دليل مرجعي جيد للليبس لمعرفة ما هو تغيير عاجل وما هو ليس تغييرًا جذريًا ومن غير المحتمل جدًا أن تلتقط الاختبارات هذا. لم تتم مناقشة هذه المشكلة بشكل كافٍ ، إذا كنا نرغب في الاستقرار قبل اتخاذ قرار كامل ، فعلينا على الأقل توجيه البندقية بعيدًا عن سفح مؤلفي lib.

الدافع هو منع libs من كسر توربوفيش المستخدمين عن طريق تغيير وسيطة من عامة صريحة إلى impl Trait .

آمل عندما يبدأ هذا في الحدوث ويبدأ الناس في الشكوى من فريق lang-team الذين يشككون حاليًا في اقتناعهم بأن impl Trait يجب أن يدعم بشكل صريح تقديم حجج النوع باستخدام turbofish.

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

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

أنا لا أوافق - لقد تم حل هذا. نحن لا نسمح باستخدام التربوفيش لهذه الوظائف تمامًا في الوقت الحالي. تغيير توقيع الوظيفة العامة لاستخدام impl Trait بدلاً من المعلمات العامة الصريحة هو تغيير فاصل.

إذا سمحنا باستخدام turbofish لهذه الوظائف في المستقبل ، فمن المحتمل أن يسمح فقط بتحديد معلمات من النوع غير impl Trait .

: bell: هذا يدخل الآن فترة التعليق النهائية ، وفقًا للمراجعة أعلاه . :جرس:

يجب أن أضيف أنني لا أريد الاستقرار حتى هبوط https://github.com/rust-lang/rust/pull/49041 . (ولكن آمل أن يكون ذلك قريبًا.)

لذا ، يحتوي # 49041 على إصلاح لـ # 46541 ، لكن هذا الإصلاح له تأثير أكبر مما توقعت - على سبيل المثال ، لا يقوم المترجم بالإقلاع الآن - وهو يعطيني قدرًا من التوقف المؤقت حول المسار الصحيح هنا. المشكلة في # 49041 هي أننا يمكن أن نسمح بطريق الخطأ لأعمار أن تتسرب لم يكن من المفترض أن نفعلها. هنا كيف يظهر هذا في المترجم. قد يكون لدينا طريقة مثل هذه:

impl TyCtxt<'cx, 'gcx, 'tcx>
where 'gcx: 'tcx, 'tcx: 'cx
{
    fn foos(self) -> impl Iterator<Item = &'tcx Foo> + 'cx {
        /* returns some type `Baz<'cx, 'gcx, 'tcx>` that captures self */
    }
}

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

abstract type Foos<'cx, 'tcx>: Iterator<Item = &'tcx Foo> + 'cx;

ومع ذلك فإن قيمة هذا النوع من الملخصات ستكون Baz<'cx, 'gcx, 'tcx> - لكن 'gcx ليس في النطاق!

الحل البديل هنا هو أنه يتعين علينا تسمية 'gcx في الحدود. هذا نوع من المزعج للقيام به ؛ لا يمكننا استخدام 'cx + 'gcx . يمكننا أن أفترض صنع سمة وهمية:

trait Captures<'a> { }
impl<T: ?Sized> Captures<'a> for T { }

ثم إرجاع شيء مثل هذا impl Iterator<Item = &'tcx Foo> + Captures<'gcx> + Captures<'cx> .

شيء نسيت ملاحظته: إذا كان نوع الإرجاع المعلن هو dyn Iterator<Item = &'tcx Foo> + 'cx ، فسيكون ذلك جيدًا ، لأنه من المتوقع أن تمحو الأنواع dyn فترات الحياة. لذلك لا أعتقد أن أي خطأ ممكن هنا ، بافتراض أنك لا تستطيع فعل أي مشكلة مع impl Trait الذي لن يكون ممكنًا مع dyn Trait .

يمكن للمرء أن يتخيل بشكل غامض فكرة أن قيمة النوع المجرد هي قيمة وجودية مماثلة: exists<'gcx> Baz<'cx, 'gcx, 'tcx> .

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

تحديث: لتوضيح المعنى الخاص بسمات dyn : أقول إنها تستطيع "إخفاء" عمر مثل 'gcx طالما أن الحد ( 'cx ، هنا) يضمن ذلك سيظل 'gcx متاحًا أينما تم استخدام dyn Trait .

nikomatsakis @ هذا مثال مثير للاهتمام ، لكنني لا أشعر أنه يغير التفاضل والتكامل الأساسي هنا ، أي أننا نريد أن تكون جميع الأعمار ذات الصلة واضحة من نوع الإرجاع وحده.

تبدو السمة Captures طريقة جيدة وخفيفة الوزن لهذا الموقف. يبدو أنه يمكن أن يذهب إلى std::marker باعتباره غير مستقر في الوقت الحالي؟

nikomatsakis لقد 'gcx في هذه الحالة ، على سبيل المثال ، 'gcx ليس "العمر المناسب" من وجهة نظر العميل. على أي حال ، فإن البدء في التحفظ يبدو جيدًا.

رأيي الشخصي هو أن https://github.com/rust-lang/rust/issues/46541 ليس خطأ حقًا - إنه السلوك الذي أتوقعه ، ولا أرى كيف يمكن جعله غير سليم ، ومن الألم العمل. IMO يجب أن يكون من الممكن إرجاع النوع الذي يطبق Trait ويفوق مدى الحياة 'a مثل impl Trait + 'a ، بغض النظر عن الأعمار الأخرى التي يحتوي عليها. ومع ذلك ، فأنا على ما يرام مع تثبيت نهج أكثر تحفظًا للبدء إذا كان هذا هو ما يفضله @ rust-lang / lang.

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

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

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

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

rfcbot تتعلق بالعودة المتعددة للمواقع

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

fn foo(empty: bool) -> impl Iterator<Item = u32> {
    if empty { None.into_iter() } else { &[1, 2, 3].cloned() }
}

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

fn foo(empty: bool) -> (impl Debug, impl Debug) {
    if empty { return (22, Default::default()); }
    return (Default::default(), false);
}

هنا ، النوع المستنتج هو (i32, bool) ، حيث يقيد أول return الجزء i32 ، والثاني return يقيد الجزء bool .

هذا يعني أنه لا يمكننا أبدًا دعم الحالات التي لا يتم فيها توحيد الكشوفين return (كما في المثال الأول) - وإلا فسيكون ذلك مزعجًا للغاية.

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

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

rfcbot حل مواقع الإرجاع المتعددة

لذلك تحدثت إلى cramertj على # rust-lang قليلاً. كنا نناقش فكرة جعل "العائد المبكر" غير مستقر لـ impl Trait ، حتى نتمكن في النهاية من تغييره.

لقد أوضحوا أنه سيكون من الأفضل أن يكون هناك اشتراك صريح لهذا النوع من بناء الجملة ، على وجه التحديد لأن هناك حالات أخرى (على سبيل المثال ، let x: impl Trait = if { ... } else { ... } ) حيث يريد المرء ذلك ، ولا يمكننا توقع للتعامل معها جميعًا ضمنيًا (بالتأكيد لا).

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

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

هذا المثال الأول foo ، بقدر ما فهمت المشكلة ، يمكن إما حله إلى (1) محاصر + محو النوع Iterator<Item = u32> ، أو (2) نوع المجموع std::option::Iter أو std::slice::Iter ، والتي بدورها ستشتق تنفيذ Iterator . في محاولة لإبقائها قصيرة ، نظرًا لوجود بعض التحديثات على المناقشة (على وجه التحديد ، قرأت سجلات IRC الآن) وأصبح من الصعب التقاطها: سأوافق بالتأكيد على صيغة ديناميكية للرقاقة الديناميكية ، على الرغم من أنني أيضًا افهم أن تسميته dyn قد لا يكون مثاليًا.

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

Centril نعم ، تلك الأشياء من frunk رائعة جدًا. ومع ذلك ، لاحظ أنه لكي يعمل CoprodInjector::inject ، يجب أن يكون النوع الناتج محسومًا ، وهو أمر مستحيل عادةً بدون تسمية النوع الناتج (على سبيل المثال ، -> Coprod!(A, B, C) ). غالبًا ما تكون الحالة التي تعمل بها مع أنواع غير قابلة للتسمية ، لذلك ستحتاج إلى -> Coprod!(impl Trait, impl Trait, impl Trait) ، والذي سيفشل في الاستدلال لأنه لا يعرف أي متغير يجب أن يحتوي على النوع impl Trait .

cramertj صحيح جدًا ( Map<Namable, Unnameable> ).

تمت مناقشة فكرة enum impl Trait من قبل في https://internals.rust-lang.org/t/pre-rfc-anonymous-enums/5695

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

fn foo(x: Foo) -> impl Future<Item = (), Error = Never> {
    match x {
        Foo::Bar => do_request().and_then(|res| ...).left().left(),
        Foo::Baz => do_other_thing().and_then(|res| ...).left().right(),
        Foo::Boo => do_third_thing().and_then(|res| ...).right(),
    }
}

cramertj لن أقول إن التعداد المجهول مشابه لـ enum impl Trait ، لأننا لا نستطيع استنتاج X: Tr && Y: Tr(X|Y): Tr (مثال العداد: Default سمة). لذلك سيحتاج مؤلفو المكتبة إلى impl Future for (X|Y|Z|...) يدويًا.

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

cramertj نظرًا ضمانة Default لـ (i32|String) ، فسنكون قادرين على كتابة <(i32|String)>::default() . لن يتم تجميع OTOH <enum impl Default>::default() ببساطة ، لذا بغض النظر عما نقوم بإنشائه تلقائيًا ، فسيظل آمنًا لأنه لا يمكن استدعاؤه على الإطلاق.

ومع ذلك ، هناك بعض الحالات التي لا يزال فيها التوليد التلقائي يسبب مشكلة مع enum impl Trait . يعتبر

pub trait Rng {
    fn next_u32(&mut self) -> u32;
    fn gen<T: Rand>(&mut self) -> T where Self: Sized;
    fn gen_iter<'a, T: Rand>(&'a mut self) -> Generator<'a, T, Self> where Self: Sized;
}

من الطبيعي تمامًا أنه إذا كان لدينا mut rng: (XorShiftRng|IsaacRng) يمكننا حساب rng.next_u32() أو rng.gen::<u64>() . ومع ذلك ، لا يمكن إنشاء rng.gen_iter::<u16>() لأن التوليد التلقائي يمكنه فقط إنتاج (Generator<'a, u16, XorShiftRng>|Generator<'a, u16, IsaacRng>) ، بينما ما نريده بالفعل هو Generator<'a, u16, (XorShiftRng|IsaacRng)> .

(ربما يمكن للمجمع أن يرفض تلقائيًا استدعاء التفويض غير الآمن تمامًا مثل الشيك Sized .)

يبدو لي FWIW أن هذه الميزة أقرب إلى عمليات الإغلاق منها إلى المجموعات (والتي هي بالطبع النظير المجهول struct للمجهول الافتراضي enum s). الطرق التي تكون بها هذه الأشياء "مجهولة" مختلفة.

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

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

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

(في الواقع ، تبدو هذه الميزة مرتبطة نوعًا ما بـ "الكائن الحرفي" الافتراضي - والذي سيكون للسمات الأخرى مثل صيغة الإغلاق الحالية لـ Fn . أي ، بدلاً من تعبير lambda واحد ، أنت ننفذ كل طريقة من السمة المعطاة (مع وجود self ضمنيًا) باستخدام المتغيرات الموجودة في النطاق ، وسيُنشئ المترجم نوعًا مجهول الهوية للاحتفاظ بالعلامات ، وتنفيذ السمة المعطاة لها ، لديك وضع move اختياري بنفس الطريقة ، وهكذا. على أي حال ، أعتقد أن طريقة مختلفة للتعبير عن if foo() { (some future) } else { (other future) } ستكون object Future { fn poll() { if foo() { (some future).poll() } else { (other future).poll() } } } (حسنًا ، ستحتاج أيضًا لرفع نتيجة foo() للخارج إلى let لذلك سيتم تشغيله مرة واحدة فقط). ميزة ، لكنها تشير إلى وجود علاقة. ربما يمكن أن تختفي الأولى في الثانية ، أو شيء من هذا القبيل.)

glaebhoerl هذه فكرة ممتعة للغاية! هناك أيضًا بعض الأعمال الفنية السابقة من Java هنا.

بعض الأفكار من أعلى رأسي (لذلك ليست مخبوزة جدا):

  1. [bikeshed] تشير البادئة object إلى أن هذا كائن سمة وليس مجرد كائن وجودي - ولكنه ليس كذلك.

بناء جملة بديل ممكن:

impl Future { fn poll() { if foo() { a.poll() } else { b.poll() } } }
// ^ --
// this conflicts with inherent impls for types, so you have to delay
// things until you know whether `Future` is a type or a trait.
// This might be __very__ problematic.

// and perhaps (but probably not...):
dyn Future { fn poll() { if foo() { a.poll() } else { b.poll() } } }
  1. [وحدات الماكرو / السكر] يمكنك توفير بعض السكر النحوي التافه بحيث تحصل على:
future!(if foo() { a.poll() } else { b.poll() })

نعم ، سؤال التركيب عبارة عن فوضى لأنه ليس من الواضح ما إذا كنت تريد استلهام الإلهام من struct حرفية أو عمليات الإغلاق أو impl block :) لقد اخترت للتو واحدة من أعلى رأسي على سبيل المثال مصلحة. (على أي حال ، لم تكن نقطتي الرئيسية هي أننا يجب أن نذهب ونضيف قيمًا حرفية للكائن [على الرغم من أنه ينبغي لنا] ولكن أعتقد أن المجهول enum s عبارة عن رنجة حمراء هنا [على الرغم من أنه يجب علينا إضافتها أيضًا].)

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

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

impl<A: IntoIterator, B: IntoIterator> IntoIterator for (A|B)  { /* dispatch appropriately */ }

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

-> impl IntoIterator<Item = Y>

ولكن في مكان آخر نقوم به

-> impl IntoIterator<IntoIter = X, Item = Y>

سيكون هذان هما ضمنيان متداخلان أعتقد أنه لا يمكن "تداخلهما" ؛ حسنًا ، ربما مع التخصص.

على أي حال ، فإن فكرة "التعداد السري" تبدو أنظف من كل مكان على ما أظن.

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

nikomatsakis : هل من العدل أن نقول إن ما يتم إرجاعه في هذه الحالة أقرب إلى dyn Trait منه إلى impl Trait ، لأن قيمة الإرجاع الاصطناعية / المجهولة تنفذ شيئًا يشبه الإرسال الديناميكي؟

cc https://github.com/rust-lang/rust/issues/49288 ، وهي مشكلة كنت أواجهها كثيرًا مؤخرًا عند العمل باستخدام أساليب السمات Future s و Future -returning.

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

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

 fn does_some_operation() -> impl Future<Item=(), Error=()> {
-    let data_stored = Arc::new("hello");
+    let data_stored = Rc::new("hello");

     return some_long_operation.and_then(|other_stuff| {
         do_other_calculation_with(data_stored)
     });
}

(مثال أبسط: العمل ، التغييرات الداخلية تسبب الفشل )

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

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

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

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

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


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

ومع ذلك ، تُستخدم الوظائف والهياكل بشكل مختلف في قاعدة البيانات ، وهذه ليست نفس المشكلات.

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

عند تعديل الأجزاء الداخلية لوظيفة ما ، من الواضح بالتأكيد أنه يمكن للمرء أن يؤثر على كل من الأداء والصحة. ومع ذلك ، في Rust ، لا نحتاج إلى التحقق مما إذا كنا نعيد النوع الصحيح. الإعلانات عن الوظائف هي عقد ثابت يجب أن نتمسك به ، و rustc يراقب ظهورنا. إنه خط رفيع بين السمات التلقائية في البنيات وعودة الوظيفة ، لكن تغيير العناصر الداخلية لوظيفة ما هو أكثر روتينية. بمجرد أن يكون لدينا Future s بالكامل ، سيكون من الروتيني أيضًا تعديل الوظائف التي تعيد -> impl Future . ستكون هذه جميع التغييرات التي يحتاج المؤلفون إلى فحصها بحثًا عن تطبيقات الإرسال / المزامنة المعدلة إذا لم يلتقطها المترجم.

لحل هذه المشكلة ، يمكننا أن نقرر أن هذا عبء صيانة مقبول ، كما فعلت مناقشة RFC الأصلية . يوضح هذا القسم في السمة الضمنية المحافظة RFC أكبر الحجج لتسريب سمات السيارات ("OIBIT" هو الاسم القديم لسمات السيارات).

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


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

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

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


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

شكرًا على القراءة ، وآمل أن القرار النهائي يضع Rust في أفضل اتجاه يمكن أن يسير فيه.

التوسيع في مراجعةdaboross wrt. الأسماء المستعارة للسمات ، يمكن للمرء تحسين بيئة العمل لسمات السيارات غير المتسربة مثل:

trait FutureNSS<T, E> = Future<Item = T, Error= E> + !Send + !Sync;

fn does_some_operation() -> impl FutureNSS<(), ()> {
     let data_stored = Rc::new("hello");
     some_long_operation.and_then(|other_stuff| {
         do_other_calculation_with(data_stored)
     });
}

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

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

ماذا عن طلب Send ما لم يتم وضع علامة عليه كـ !Send ، ولكن عدم تقديم Sync ما لم يتم وضع علامة عليه كمزامنة؟ ألا يفترض أن يكون الإرسال أكثر شيوعًا مقارنة بالمزامنة؟

مثله:

fn provides_send_only1() -> impl Trait {  compatible_with_Send_and_Sync }
fn provides_send_only2() -> impl Trait {  compatible_with_Send_only }
fn fails_to_complile1() -> impl Trait {  not_compatible_with_Send }
fn provides_nothing1() -> !Send + impl Trait { compatible_with_Send}
fn provides_nothing2() -> !Send + impl Trait { not_compatible_with_Send }
fn provides_send_and_sync() -> Sync + impl Trait {  compatible_with_Send_and_Sync }
fn fails_to_compile2() -> Sync + impl Trait { compatible_with_Send_only }

هل هناك تناقض بين impl Trait في موضع الوسيطة وموضع الإرجاع wrt. سمات السيارات؟

fn foo(x: impl ImportantTrait) {
    // Can't use Send cause we have not required it...
}

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

بالنسبة للقلق بشأن السمات التلقائية المخصصة ، أود أن أزعم أن أي سمات تلقائية جديدة يجب ألا يتم تنفيذها فقط للأنواع الجديدة التي يتم تقديمها بعد السمة التلقائية.

حسنا، هذا صحيح بالنسبة لل سيارات القادمة سمة Unpin (فقط لا implmented للمولدات ذاتية المرجع)، ولكن ... يبدو أن هذا الحظ البكم؟ هل هذا قيد يمكننا التعايش معه حقًا؟ لا أصدق أنه لن يكون هناك شيء في المستقبل يحتاج إلى تعطيل على سبيل المثال &mut أو Rc ...

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

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

وبالتالي ، فإن -> impl Trait يفي فقط بوعد قدمه 1.0 ، أو يزيل حالة الحافة ، أو يعمم الميزات الحالية: فهو يضيف أنواع المخرجات إلى الوظائف ، مع الأخذ بالآلية نفسها التي تم استخدامها دائمًا للتعامل مع الأنواع المجهولة وتطبيقها في المزيد من الحالات. ربما كان من الأفضل البدء بـ abstract type ، أي أنواع المخرجات للوحدات النمطية ، ولكن نظرًا لأن Rust لا يحتوي على نظام وحدة ML ، فإن الطلب ليس مشكلة كبيرة.

بدلاً من ذلك ، يبدو أن fn f(t: impl Trait) قد تمت إضافته "لمجرد أننا نستطيع" ، مما يجعل اللغة أكبر وأكثر غرابة دون رد ما يكفي في المقابل. لقد كافحت وفشلت في العثور على بعض الأطر الموجودة لتلائمها. أتفهم الجدل الدائر حول إيجاز fn f(f: impl Fn(...) -> ...) ، والتبرير القائل بأن الحدود يمكن أن تكون بالفعل في كل من العبارات <T: Trait> و where ، لكن هذه تبدو فارغة. إنهم لا ينفون الجوانب السلبية:

  • عليك الآن أن تتعلم تركيبتين للحدود- على الأقل <> / where صيغة واحدة.

    • يؤدي هذا أيضًا إلى إنشاء منحدر تعليمي وإخفاء فكرة استخدام نفس النوع العام في أماكن متعددة.

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

  • الآن ما يجب أن يكون تفاصيل تنفيذ الوظيفة (كيف تعلن معلمات النوع) يصبح جزءًا من واجهتها ، لأنه لا يمكنك كتابة نوعه!

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

  • إن القياس مع dyn Trait هو ، بصراحة ، تشبيه خاطئ:

    • يعني dyn Trait دائمًا نفس الشيء ، ولا "يصيب" الإعلانات المحيطة به إلا من خلال آلية السمة التلقائية الحالية.

    • dyn Trait في هياكل البيانات ، وهذه حقًا إحدى حالات الاستخدام الأساسية. لا معنى لهياكل البيانات impl Trait بدون النظر إلى جميع استخدامات بنية البيانات.

    • جزء مما يعنيه dyn Trait هو محو الكتابة ، لكن impl Trait لا يعني أي شيء عن تنفيذه.

    • ستكون النقطة السابقة أكثر إرباكًا إذا أدخلنا الأدوية غير أحادية الشكل. في الواقع، في وضع من هذا القبيل، fn f(t: impl Trait) من المرجح أ) لا تعمل مع ميزة جديدة، و / أو ب) تتطلب المزيد من حالة حافة المحاماة مثل هذه المسألة مع الصفات السيارات. تخيل fn f<dyn T: Trait>(t: T, u: dyn impl Urait) ! : تصرخ:

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

ماذا عن طلب إرسال ما لم يتم وضع علامة عليه! إرسال ، ولكن لا يقدم مزامنة ما لم يتم وضع علامة عليه كمزامنة؟ ألا يفترض أن يكون الإرسال أكثر شيوعًا مقارنة بالمزامنة؟

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

فكرة سقيفة الدراجة هنا حتى لا تصرف الانتباه عن النقاط أعلاه: بدلاً من impl ، استخدم type ؟ هذه هي الكلمة الأساسية المستخدمة للأنواع المرتبطة ، ومن المحتمل (واحدة) من الكلمة (الكلمات) الرئيسية المستخدمة في abstract type ، ولا تزال طبيعية إلى حد ما ، وتلمح أكثر إلى فكرة "أنواع الإخراج للوظائف":

// keeping the same basic structure, just replacing the keyword:
fn f() -> type Trait

// trying to lean further into the concept:
fn f() -> type R: Trait
fn f() -> type R where R: Trait
fn f() -> (i32, type R) where R: Trait
// or perhaps:
fn f() -> type R: Trait in R
// or maybe just:
fn f() -> type: Trait

شكرًا على القراءة ، وآمل أن القرار النهائي يضع Rust في أفضل اتجاه يمكن أن يسير فيه.

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

ويبدو لي أن السؤال المهم هو إلى أي مدى وظائف هي في الواقع مختلفة من البنيات:

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

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

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

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

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

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

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

أعتقد أن هذا قد تم مناقشته

في الواقع ، لقد :) منذ أول impl Trait RFC منذ عدة سنوات مضت. (Woah ، 2014. أشعر بالتقدم في السن.)

لقد كافحت وفشلت في العثور على بعض الأطر الموجودة لتلائمها.

أشعر بالعكس تماما. بالنسبة لي ، بدون impl Trait في موضع الوسيطة ، فإن impl Trait في موضع الإرجاع يبرز أكثر. الخيط الموحد الذي أراه هو:

  • impl Trait - حيث يظهر ، فإنه يشير إلى أنه سيكون هناك "نوع أحادي الشكل يستخدم Trait ". (يعتمد السؤال حول من يحدد هذا النوع - المتصل أو المستدعي - على مكان ظهور impl Trait .)
  • dyn Trait - حيث يظهر ، يشير إلى أنه سيكون هناك نوع ما يستخدم Trait ، لكن اختيار النوع يتم بشكل ديناميكي.

هناك أيضًا خطط لتوسيع مجموعة الأماكن التي يمكن أن يظهر فيها impl Trait ، بناءً على هذا الحدس. على سبيل المثال ، https://github.com/rust-lang/rfcs/pull/2071 تصاريح

let x: impl Trait = ...;

ينطبق نفس المبدأ: اختيار النوع معروف بشكل ثابت. وبالمثل ، فإن RFC نفسها تقدم abstract type (والتي يمكن أن تُفهم impl Trait كنوع من السكر التركيبي) ، والتي يمكن أن تظهر في السمات الضمنية وحتى كأعضاء في الوحدات النمطية.

فكرة سقيفة الدراجة هنا حتى لا تصرف الانتباه عن النقاط أعلاه: بدلاً من impl ، استخدم type ؟

أنا شخصياً لا أميل إلى إعادة ركوب الدراجة هنا. لقد أمضينا بعض الوقت في مناقشة بناء الجملة في https://github.com/rust-lang/rfcs/pull/2071 وفي أي مكان آخر. لا يبدو أن هناك "كلمة رئيسية مثالية" ، ولكن قراءة impl كـ "بعض الأنواع التي تنفذ" يعمل بشكل جيد imo.

اسمحوا لي أن أضيف المزيد حول تسرب السمات التلقائي:

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

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

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

لذلك - حتى هذه اللحظة على الأقل - ما زلت أشعر بالميل لمواصلة المسار الحالي.

أنا شخصياً لا أميل إلى إعادة ركوب الدراجة هنا.

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

بالنسبة لي ، بدون impl Trait في موضع الوسيطة ، فإن impl Trait في موضع الإرجاع يبرز أكثر.

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

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

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

كما ذكرت في تعليقي السابق ، أنا أيضًا معجب بـ abstract type . إنه ، مرة أخرى ، مجرد توسيع لمفهوم "نوع الإخراج" ليشمل الوحدات النمطية. كما أن تطبيق استخدام -> impl Trait و let x: impl Trait و abstract type للاستدلال لتسمية الأنواع المرتبطة هو أمر رائع أيضًا.

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

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

من الصعب حقًا معرفة مدى صحة ذلك.

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

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

مفهوم! ومع ذلك ، وأنا متأكد من أنك قد فكرت ، فإن القرارات هنا ستظل معنا لسنوات عديدة قادمة.

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

وهذا أمر جيد للاستماع! 🎉 وأعتقد أن هذا في الغالب يهدئ من مخاوفي.

صحيح ، على الرغم من أنني أشعر بالتوتر بالتأكيد بشأن فكرة "الاستفراد" بسمات معينة للسيارات مثل Send .

أنا أتفق كثيرا مع هذا الشعور 👍.

على أي حال ، ما تريده حقًا ، وما نحاول تأجيله =) ، هو نوع من الربط "الشرطي" (أرسل إذا كان إرسال T ). هذا هو بالضبط ما تعطيك السمات التلقائية.

أشعر أنه سيكون من الأفضل فهم T: Send => Foo<T>: Send إذا نص الرمز صراحة على ذلك.

fn foo<T: Extra, trait Extra = Send>(x: T) -> impl Bar + Extra {..}

مع ذلك ، كما ناقشنا في WG-Traits ، قد لا تحصل على أي استدلال هنا على الإطلاق ، لذلك عليك دائمًا تحديد Extra إذا كنت تريد شيئًا آخر غير Send ، والذي سيكون مشكلة إجمالية .

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

إن القياس مع dyn Trait هو ، بصراحة ، تشبيه خاطئ:

بالنسبة إلى impl Trait في موضع الوسيطة ، فهذا خطأ ، لكن ليس كذلك مع -> impl Trait لأن كلاهما نوعان وجوديان.

  • الآن ما يجب أن يكون تفاصيل تنفيذ الوظيفة (كيف تعلن معلمات النوع) يصبح جزءًا من واجهتها ، لأنه لا يمكنك كتابة نوعه!

أود أن أشير إلى أن ترتيب معلمات النوع لم يكن أبدًا أحد تفاصيل التنفيذ بسبب التوربيني ، وفي هذا الصدد ، أعتقد أن impl Trait يمكن أن يساعد لأنه يسمح لك بترك نوع معين من الحجج غير محددة في turbofish .

[..] الصيغة الموجودة هناك جيدة بما يكفي بحيث لا يشعر أحد بالحاجة إلى السكر النحوي مثل السمة.

لا تستصعب شئ أبدا؟ https://github.com/rust-lang/rfcs/issues/2274

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


daboross ، أردت

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

  • إذا تم التعامل مع السمات التلقائية على أنها إلغاء الاشتراك لـ impl Trait ، فيجب أن تكون مقابل dyn Trait أيضًا.
  • هذا بالطبع ينطبق حتى عندما يتم استخدام هذه التركيبات في موقف الحجة.
  • ولكن بعد ذلك ، سيكون من الغريب أن تتصرف الأدوية الجنيسة بشكل مختلف. بمعنى آخر ، بالنسبة إلى fn foo<T>(t: T) ، يمكنك أن تتوقع بشكل معقول T: Send افتراضيًا.
  • بالطبع لدينا آلية لذلك ، مطبقة حاليًا فقط على Sized ؛ إنها سمة يتم افتراضها افتراضيًا في كل مكان ، والتي يمكنك إلغاء الاشتراك فيها عن طريق كتابة ?Sized

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

ما هو أكثر من ذلك، على الرغم من: نحن في الحقيقة لا تريد خبز في افتراض سمة السيارات لالوراثة، لأن جزءا من جمال الوراثة اليوم هو أنك يمكن أن تكون فعالة عام حول ما إذا كان نوع من الأدوات سمة السيارات، ولها أن المعلومات فقط "تتدفق من خلال". على سبيل المثال ، ضع في اعتبارك fn f<T>(t: T) -> Option<T> . يمكننا تمرير T بغض النظر عما إذا كان Send ، وسيكون الناتج Send iff T كان. هذا جزء مهم للغاية من قصة الأدوية الجنيسة في Rust.

هناك أيضًا مشكلات في dyn Trait . على وجه الخصوص ، بسبب التجميع المنفصل ، سيتعين علينا تقييد طبيعة "إلغاء الاشتراك" هذه فقط بسمات تلقائية "معروفة" مثل Send و Sync ؛ قد يعني ذلك على الأرجح عدم الاستقرار على auto trait للاستخدام الخارجي.

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


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

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

الآن ما يجب أن يكون تفاصيل تنفيذ الوظيفة (كيف تعلن معلمات النوع) يصبح جزءًا من واجهتها ، لأنه لا يمكنك كتابة نوعه!

لا أفهم ما تعنيه بهذا. هل يمكنك التوسع؟

أريد أيضًا أن أشير إلى عبارات مثل:

fn f (t: impl Trait) بدلاً من ذلك يبدو أنه تمت إضافته "لمجرد أننا نستطيع"

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

المفاضلات موجودة ، وهناك بالفعل جوانب سلبية ، لكنها لا تساعدنا في الوصول إلى نتيجة منطقية لرسم كاريكاتوري "للجانب الآخر" من النقاش.

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

تسرب auto Trait

لقد أزعجتني فكرة auto Trait التسرب لفترة طويلة - من بعض النواحي ، يمكن أن تبدو مناقضة للعديد من أهداف تصميم Rust. بالمقارنة مع أسلافه ، مثل C ++ أو عائلة ML ، يعتبر Rust غير معتاد من حيث أنه يتطلب حدودًا عامة يتم ذكرها صراحة في إعلانات الوظائف. في رأيي ، هذا يجعل الوظائف العامة لـ Rust أسهل في القراءة والفهم ، ويوضح نسبيًا متى يتم إجراء تغيير غير متوافق مع الإصدارات السابقة. لقد واصلنا هذا النمط في منهجنا إلى const fn ، والذي يتطلب من الوظائف تحديد نفسها بشكل صريح كـ const بدلاً من استنتاج const ness من الهيئات الوظيفية. يشبه إلى حد كبير حدود السمات الصريحة ، وهذا يجعل من السهل معرفة الوظائف التي يمكن استخدامها بأي طرق ، ويمنح مؤلفي المكتبة الثقة في أن تغييرات التنفيذ الصغيرة لن تؤدي إلى كسر المستخدمين.

ومع ذلك ، فقد استخدمت وضع الإرجاع impl Trait نطاق واسع في مشاريعي الخاصة ، بما في ذلك عملي على نظام التشغيل Fuchsia ، وأعتقد أن تسرب السمات التلقائي هو الخيار الافتراضي الصحيح هنا. عمليًا ، ستكون نتيجة إزالة التسرب هو أنني يجب أن أعود وأضيف + Send إلى كل impl Trait وظيفة كتبتها على الإطلاق. تعتبر الحدود السلبية (تتطلب + !Send ) فكرة مثيرة للاهتمام بالنسبة لي ، ولكن بعد ذلك سأكتب + !Unpin على جميع الوظائف نفسها تقريبًا. يكون Explicitness مفيدًا عندما يُعلم قرارات المستخدمين أو يجعل الكود أكثر قابلية للفهم. في هذه الحالة ، أعتقد أنه لن يفعل أيًا منهما.

Send و Sync عبارة عن "سياقات" لبرنامج المستخدمين: من النادر جدًا أن أكتب تطبيقًا أو مكتبة تستخدم كلا النوعين Send و !Send (خاصة عند كتابة كود غير متزامن ليتم تشغيله على منفذ مركزي ، سواء كان متعدد الخيوط أم لا). يعد اختيار أن تكون آمنًا أم لا هو أحد الخيارات الأولى التي يجب إجراؤها عند كتابة طلب ، ومن هناك فصاعدًا ، اختيار أن تكون آمنًا للخيط يعني أن جميع الأنواع الخاصة بي يجب أن تكون Send . بالنسبة للمكتبات ، دائمًا ما أفضّل أنواع Send ، لأن عدم استخدامها يعني عادةً أن مكتبتي غير قابلة للاستخدام (أو تتطلب إنشاء سلسلة محادثات مخصصة) عند استخدامها ضمن سياق مترابط. سيكون أداء parking_lot::Mutex غير المتنازع عليه مطابقًا تقريبًا لأداء RefCell عند استخدامه على وحدات المعالجة المركزية الحديثة ، لذلك لا أرى أي دافع لدفع المستخدمين نحو !Send وظائف المكتبة مقابل Send و !Send على مستوى توقيع الوظيفة ، ولا أعتقد أنه سيكون شائعًا بالنسبة قام مؤلفو المكتبة بطريق الخطأ بتقديم أنواع !Send إلى أنواع impl Trait التي كانت في السابق Send . صحيح أن هذا الاختيار يأتي مع تكلفة مقروئية ووضوح ، لكنني أعتقد أن المقايضة تستحق ذلك جيدًا من أجل المزايا المريحة وقابلية الاستخدام.

موقف الوسيطة impl Trait

ليس لدي الكثير لأقوله هنا ، باستثناء أنه في كل مرة وصلت فيها إلى موقع الوسيطة impl Trait ، وجدت أنه زاد بشكل كبير من قابلية القراءة والبهجة العامة لتوقيعات وظيفتي. صحيح أنها لا تضيف قدرة جديدة غير ممكنة في Rust اليوم ، لكنها تحسن كبير في جودة الحياة لتوقيعات الوظائف المعقدة ، فهي تقترن جيدًا من الناحية المفاهيمية مع وضع الإرجاع impl Trait ، ويسهل انتقال مبرمجي OOP إلى رستانيات سعيدة. في الوقت الحالي ، هناك الكثير من التكرار حول الاضطرار إلى تقديم نوع عام مسمى فقط لتقديم حد (على سبيل المثال ، F في fn foo<F>(x: F) where F: FnOnce() مقابل fn foo(x: impl FnOnce()) ). يحل هذا التغيير هذه المشكلة وينتج عنه توقيعات وظائف يسهل قراءتها وكتابتها ، وتشعر IMO بأنها مناسبة بشكل طبيعي إلى جانب -> impl Trait .

TL ؛ دكتور: أعتقد أن قراراتنا الأصلية كانت صحيحة ، رغم أنها بلا شك تأتي مع مقايضات.
إنني أقدر حقًا كل شخص يتحدث بصوت عال ويخصص الكثير من الوقت والجهد للتأكد من أن Rust هي أفضل لغة يمكن أن تكون.

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

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

نعم ، هذا ما قصدته.

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

عبارات مثل ... تقوض مناقشة حسن النية

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

الآن ما يجب أن يكون تفاصيل تنفيذ الوظيفة (كيف تعلن معلمات النوع) يصبح جزءًا من واجهتها ، لأنه لا يمكنك كتابة نوعه!

لا أفهم ما تعنيه بهذا. هل يمكنك التوسع؟

بدعم impl Trait في موضع الوسيطة ، يمكنك كتابة هذه الوظيفة بطريقتين:

fn f(t: impl Trait)
fn f<T: Trait>(t: T)

يحدد اختيار النموذج ما إذا كان بإمكان مستهلك واجهة برمجة التطبيقات (API) حتى كتابة اسم أي إنشاء مثيل معين (على سبيل المثال لأخذ عنوانه). لا يسمح لك المتغير impl Trait بالقيام بذلك ، ولا يمكن حل هذا دون إعادة كتابة التوقيع لاستخدام صيغة <T> . علاوة على ذلك ، يعد الانتقال إلى بناء الجملة <T> تغييرًا فاصلاً!

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

لست متأكدًا من أن أيًا من التغييرات الأخرى "البسيطة ، ولكن المحدودة -> المعقدة ، ولكن العامة" ، بدافع من قابلية التعلم / بيئة العمل ، تتضمن تغييرات في كسر الواجهة بهذه الطريقة تمامًا. إما أن يتصرف المكافئ المعقد للطريقة البسيطة بشكل متماثل وتحتاج فقط إلى التبديل عندما تقوم بالفعل بتغيير الواجهة أو السلوك (على سبيل المثال elision مدى الحياة ، أو بيئة العمل المطابقة ، -> impl Trait ) ، أو أن التغيير عام ومقصود منه يمكن تطبيقها عالميًا (على سبيل المثال ، الوحدات / المسارات ، وأعمار النطاق ، dyn Trait ).

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

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

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

علاوة على ذلك ، يعد الانتقال إلى بناء الجملة <T> تغييرًا فاصلاً!

ليس بالضرورة ، باستخدام https://github.com/rust-lang/rfcs/pull/2176 ، يمكنك إضافة معلمة نوع إضافية T: Trait إلى النهاية وسيظل turbofish يعمل (إلا إذا كنت تشير إلى كسر بواسطة بعض الوسائل الأخرى غير الكسر التوربيني).

لا يتيح لك متغير السمات الضمنية القيام بذلك ، ولا يمكن حل ذلك دائمًا دون إعادة كتابة التوقيع لاستخدام بناء الجملة <T> . علاوة على ذلك ، يعد الانتقال إلى بناء الجملة <T> تغييرًا فاصلاً!

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

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

nikomatsakis يمكن أن يكون الانتقال إلى بناء الجملة الصريح تغييرًا مفاجئًا أيضًا ، إذا كان التوقيع القديم يحتوي على مزيج من معلمات النوع الصريحة والمعلمات الضمنية - أي شخص قدم معلمات النوع n سيحتاج الآن إلى تقديم n + 1 بدلا من ذلك. كانت تلك إحدى الحالات التي تهدف RFC التابع لـ

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

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

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

شكرا لك على معالجة هذا القلق بصدق.

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

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

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

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

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

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

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

التصميم الذي أود رؤيته هو (أ) نعم لقبول centril 's RFC أو شيء من هذا القبيل و (ب) للقول أنه يمكنك استخدام turbofish للمعلمات الصريحة (ولكن ليس impl Trait أنواع). ومع ذلك ، لم نفعل ذلك ، ويرجع ذلك جزئيًا إلى أننا كنا نتساءل عما إذا كانت هناك قصة قد مكّنت الانتقال من معلمة صريحة إلى سمة ضمنية.

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

كانت تلك إحدى الحالات التي تهدف RFC التابع لـ

_ [تريفيا] _ بالمناسبة ، كان nikomatsakis هو الذي <T: Trait> و impl Trait ؛) لم يكن هدف RFC في كل ذلك منذ البداية ، لكنها كانت مفاجأة جميلة. 😄

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

فترة التعليق الأخيرة قد اكتملت الآن.

إذا تم شحن هذا في 1.26 ، فإن https://github.com/rust-lang/rust/issues/49373 يبدو مهمًا جدًا بالنسبة لي ، Future و Iterator هما من الاستخدامات الرئيسية -حالات وكلاهما يعتمد بشكل كبير على معرفة الأنواع المرتبطة بها.

أجرى بحثًا سريعًا في أداة تعقب المشكلات ، و # 47715 عبارة عن ICE لا يزال بحاجة إلى الإصلاح. هل يمكننا الحصول على هذا قبل أن يصبح مستقرًا؟

شيء صادفته مع سمة ضمنية اليوم:
https://play.rust-lang.org/؟gist=69bd9ca4d41105f655db5f01ff444496&version=stable

يبدو أن impl Trait غير متوافق مع unimplemented!() - هل هذه مشكلة معروفة؟

نعم ، انظر # 36375 و # 44923

لقد أدركت للتو أن افتراض RFC 1951 impl Trait مع الكتل غير المتزامنة. على وجه التحديد ، إذا كنت تأخذ معلمة AsRef أو Into للحصول على واجهة برمجة تطبيقات أكثر راحة ، ثم قم بتحويلها إلى نوع مملوك قبل إرجاع كتلة async ، فلا يزال بإمكانك الحصول على المرتجع impl Trait مرتبط بأي عمر في هذا المعامل ، على سبيل المثال

impl HttpClient {
    fn get(&mut self, url: impl Into<Url>) -> impl Future<Output = Response> + '_ {
        let url = url.into();
        async {
            // perform the get
        }
    }
}

fn foo(client: &mut HttpClient) -> impl Future<Output = Response> + '_ {
    let url = Url::parse("http://foo.example.com").unwrap();
    client.get(&url)
}

بهذا ستحصل على error[E0597]: `url` does not live long enough لأن get يتضمن عمر المرجع المؤقت في impl Future إرجاعه. تم اختراع هذا المثال قليلاً حيث يمكنك تمرير عنوان url بالقيمة إلى get ، ولكن من شبه المؤكد أنه ستكون هناك حالات مماثلة تظهر في الكود الحقيقي.

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

impl HttpClient {
    abstract type Get<'a>: impl Future<Output = Response> + 'a;
    fn get(&mut self, url: impl Into<Url>) -> Self::Get<'_> {
        let url = url.into();
        async {
            // perform the get
        }
    }
}

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

أتساءل عما إذا كانت هناك طريقة أكثر إيجازًا لكتابة هذا ، أم أن هذا سينتهي فقط باستخدام أنواع مجردة لكل وظيفة تقريبًا وليس نوع الإرجاع العاري impl Trait ؟

@ Nemo157 نعم ، راجع https://github.com/rust-lang/rust/issues/42940

لذا ، إذا فهمت تعليق cramertj على هذه المسألة ، HttpClient::get شيء مثل `get` returns an `impl Future` type which is bounded to live for `'_`, but this type could potentially contain data with a shorter lifetime inside the type of `url` . (نظرًا لأن RFC تحدد صراحةً أن impl Trait يلتقط _جميع_ معلمات النوع العام ، ومن الأخطاء أنه يُسمح لك بالتقاط نوع قد يحتوي على عمر أقصر من العمر المعلن صراحةً).

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

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

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

@ Nemo157 نعم ، إصلاح # Url . هذا بالتأكيد تغيير نريد إجراؤه ، لكنني أعتقد أنه متوافق مع الإصدارات السابقة للقيام بذلك - فهو لا يسمح لنوع الإرجاع بأن يكون له عمر أقصر ، إنه يحد من الطرق التي يمكن من خلالها استخدام نوع الإرجاع.

على سبيل المثال ، الأخطاء التالية مع "المعلمة Iter قد لا تدوم طويلاً بما يكفي":

fn foo<'a, Iter>(_: &'a mut u32, iter: Iter) -> impl Iterator<Item = u32> + 'a
    where Iter: Iterator<Item = u32>
{
    iter
}

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

يبدو # 46541 قد انتهى. هل يمكن لشخص تحديث OP؟

هل هناك سبب لاختيار بناء الجملة abstract type Foo = ...; على type Foo = impl ...; ؟ لقد فضلت الخيار الأخير كثيرًا ، لاتساق بناء الجملة ، وأتذكر بعض النقاش حول هذا منذ فترة ، لكن لا يبدو أنني أجد ذلك.

أنا متحيز لـ type Foo = impl ...; أو type Foo: ...; ، abstract يبدو غريبًا غير ضروري.

إذا كنت أتذكر بشكل صحيح ، فإن أحد الاهتمامات الرئيسية هو أن الناس تعلموا تفسير type X = Y مثل استبدال نصي ("استبدل X بـ Y عند الاقتضاء"). هذا لا يعمل مقابل type X = impl Y .

أفضل type X = impl Y بنفسي لأن حدسي هو أن type يعمل مثل let ، لكن ...

alexreg هناك الكثير من النقاش حول هذا الموضوع في RFC 2071 . TL ؛ DR: type Foo = impl Trait; يكسر القدرة على desugar impl Trait إلى بعض النماذج "الأكثر وضوحًا" ، ويكسر حدس الناس حول الأسماء المستعارة التي تعمل كبديل نحوي أكثر ذكاءً.

أنا متحيز لكتابة Foo = impl ...؛ أو اكتب Foo: ... ؛ ، الملخص يبدو غريب الأطوار غير ضروري

يجب عليك الانضمام إلى المعسكر exists type Foo: Trait; : wink:

تضمين التغريدة لقد قمت للتو بتحديث نفسي على بعض من ذلك ، وإذا كنت صريحًا لا يمكنني القول أنني أفهم منطقwithoutboats . يبدو الأمر الأكثر بديهية بالنسبة لي (هل لديك مثال مضاد؟) والجزء المتعلق بالقصص الذي لا أفهمه. أعتقد أن حدسي يعمل مثل @ lnicola. أشعر أيضًا أن بناء الجملة هذا هو الأفضل للقيام بأشياء مثل https://github.com/rust-lang/rfcs/pull/2071#issuecomment -319012123 - هل يمكن القيام بذلك في الصيغة الحالية؟

exists type Foo: Trait; تحسنًا طفيفًا ، على الرغم من أنني سأستمر في إسقاط الكلمة الرئيسية exists . لن يزعجني type Foo: Trait; بما يكفي لتقديم شكوى. 😉 abstract غير ضروري / غريب الأطوار ، كما يقول eddyb .

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

هل يمكن أن يتم ذلك في الصيغة الحالية؟

نعم ، لكنها أكثر صعوبة. كان هذا هو السبب الرئيسي لتفضيل بناء الجملة = impl Trait (نموذج الكلمة الأساسية abstract ).

type Foo = (impl Bar, impl Baz);
type IterDisplay = impl Iterator<Item=impl Display>;

// can be written like this:

exists type Foo1: Bar;
exists type Foo2: Baz;
exists type Foo: (Foo1, Foo2);

exists type IterDisplayItem: Display;
exists type IterDisplay: Iterator<Item=IterDisplayItem>;

تحرير: exists type Foo: (Foo1, Foo2); أعلاه يجب أن يكون type Foo = (Foo1, Foo2); . اسف لخلط الامور.

cramertj تبدو البنية لطيفة. هل يجب أن تكون exists كلمة رئيسية مناسبة؟

cramertj حسنًا ، كنت أفكر أنه يجب عليك فعل شيء من هذا القبيل ... سبب وجيه لتفضيل = impl Trait ، أعتقد ذلك! بصراحة ، إذا اعتقد الناس أن الحدس حول الاستبدال ينهار بشكل كافٍ للأنواع الوجودية هنا (مقارنةً بالأسماء المستعارة البسيطة) ، فلماذا لا يكون الحل الوسط التالي؟

exists type Foo = (impl Bar, impl Baz);

(بصراحة ، أنا أفضل أن يكون لدي اتساق في استخدام الكلمة الرئيسية المفردة type لكل شيء.)

وجدت:

exists type Foo: (Foo1, Foo2);

غريب جدا. لا يتوافق استخدام Foo: (Foo1, Foo2) حيث RHS غير ملزم مع كيفية استخدام Ty: Bound في أي مكان آخر في اللغة.

تبدو الأشكال التالية جيدة بالنسبة لي:

exists type Foo: Bar + Baz;  // <=> "There exists a type Foo which satisfies Bar and Baz."
                             // Reads super well!

type Foo = impl Bar + Baz;

type Bar = (impl Foo, impl Bar);

أفضل أيضًا عدم استخدام abstract ككلمة هنا.

أجد exists type Foo: (Foo1, Foo2); غريبًا للغاية

يبدو هذا بالتأكيد خطأ بالنسبة لي ، وأعتقد أنه يجب أن يقول type Foo = (Foo1, Foo2); .

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

ما زلت أفضل أيضًا : Foo + Bar على : (Foo, Bar) ، = Foo + Bar ، = impl Foo + Bar أو = (impl Foo, impl Bar . استخدام + يعمل بشكل جيد مع جميع حدود الأماكن الأخرى ، ونقص = يشير حقًا إلى أننا لا نستطيع كتابة النوع الكامل. لا نصنع اسمًا مستعارًا للنوع هنا ، بل نصنع اسمًا لشيء نضمن أن يكون له حدود معينة ، لكن لا يمكننا تسميته صراحةً.


ما زلت أحب اقتراح بناء الجملة من https://github.com/rust-lang/rfcs/pull/2071#issuecomment -318852774 من:

type ExistentialFoo: Bar;
type Bar: Baz + Bax;

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

لا بد أني أفسر (impl Foo, impl Bar) بشكل مختلف تمامًا عن بعضكم ... بالنسبة لي ، هذا يعني أن النوع عبارة عن 2-tuple من بعض الأنواع الوجودية ، وهو مختلف تمامًا عن impl Foo + Bar .

alexreg إذا كان هذا هو قصدcramertj ، فما زلت أجد ذلك غريبًا جدًا مع بناء الجملة : :

exists type Foo: (Foo1, Foo2);

يبدو أنه لا يزال غير واضح تمامًا فيما يتعلق بما يفعله - لا تحدد الحدود عادةً مجموعة من الأنواع المحتملة في أي حال ، ويمكن بسهولة الخلط بينها وبين معنى Foo: Foo1 + Foo2 بناء الجملة.

= (impl Foo, impl Bar) فكرة مثيرة للاهتمام - السماح بإنشاء مجموعات وجودية بأنواع غير معروفة سيكون أمرًا مثيرًا للاهتمام. لا أعتقد أننا _ نحتاج_ لدعم هؤلاء ، حيث يمكننا فقط تقديم نوعين وجوديين للضمانات Foo و impl Bar ثم الاسم المستعار من النوع الثالث لـ tuple.

daboross حسنًا ، أنت تصنع "نوعًا وجوديًا" ، وليس "نوع موجود" ؛ وهو ما يطلق عليه في نظرية النوع. لكني أعتقد أن الصياغة "يوجد نوع Foo الذي ..." يعمل بشكل جيد مع كل من النموذج العقلي ومن منظور النوع النظري.

لا أعتقد أننا بحاجة إلى دعم هؤلاء ، حيث يمكننا فقط تقديم نوعين وجوديين مقابل impl Foo و impl Bar ثم الاسم المستعار من النوع الثالث لـ tuple.

هذا يبدو غير مريح ... المؤقتون ليسوا لطيفين للغاية.

alexreg ملاحظة: لم أقصد أن أقول إن impl Bar + Baz; هو نفسه (impl Foo, impl Bar) ، من الواضح أن الأخير هو 2-tuple.

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

إذا كانت هذه هي نية cramertj ،

exists type Foo: (Foo1, Foo2);

يبدو أنه لا يزال غير واضح تمامًا فيما يتعلق بما يفعله - لا تحدد الحدود عادةً مجموعة من الأنواع المحتملة على أي حال ، ويمكن بسهولة الخلط بينها وبين معنى Foo: Foo1 + Foo2.

ربما يكون الأمر غير واضح بعض الشيء (ليس واضحًا مثل (impl Foo, impl Bar) ، والذي سأفهمه بشكل حدسي على الفور) - لكنني لا أعتقد أنني سأخلط بينه وبين Foo1 + Foo2 ، شخصيًا.

= (impl Foo، impl Bar) فكرة مثيرة للاهتمام - السماح بإنشاء مجموعات وجودية بأنواع غير معروفة سيكون أمرًا مثيرًا للاهتمام. لا أعتقد أننا بحاجة إلى دعم هؤلاء ، حيث يمكننا فقط تقديم نوعين وجوديين لـ impl Foo وضمن Bar ثم اسم مستعار من النوع الثالث لـ tuple.

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

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

عفوًا ، راجع تعديلي على https://github.com/rust-lang/rust/issues/34511#issuecomment -386763340. exists type Foo: (Foo1, Foo2); المفترض أن يكون type Foo = (Foo1, Foo2); .

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

type A = impl Foo;
type B = (impl Foo, impl Bar, String);

alexreg نعم، أنا لا أعتقد أن هذا هو معظم في بناء الجملة مريح.

باستخدام RFC https://github.com/rust-lang/rfcs/pull/2289 ، هكذا سأعيد كتابة مقتطف

type Foo = (impl Bar, impl Baz);
type IterDisplay = impl Iterator<Item: Display>;

// alternatively:

exists type IterDisplay: Iterator<Item: Display>;

type IterDisplay: Iterator<Item: Display>;

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

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

type Foo = (impl Bar, impl Baz);
type IterDisplay: Iterator<Item: Display>;

تحرير: ما هي الصيغة التي يجب استخدامها؟ IMO ، يجب أن تفضل clippy بشكل لا لبس فيه بناء الجملة Type: Bound عندما يكون من الممكن استخدامه لأنه أكثر راحة ومباشرة.

أفضل المتغير type Foo: Trait على المتغير type Foo = impl Trait . إنه يطابق بناء جملة النوع المرتبط ، وهو أمر جيد لأنه أيضًا "نوع إخراج" للوحدة النمطية التي تحتوي عليه.

يتم استخدام بناء الجملة impl Trait لكل من أنواع المدخلات والمخرجات بالفعل ، مما يعني أنه يخاطر بإعطاء انطباع بالوحدات النمطية متعددة الأشكال. :(

إذا تم استخدام impl Trait فقط لأنواع المخرجات ، فقد أفضّل متغير type Foo = impl Trait على أساس أن بناء جملة النوع المرتبط يكون أكثر للسمات (التي تتوافق بشكل فضفاض مع توقيعات ML) بينما type Foo = .. بناء الجملة

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

أفضل المتغير type Foo: Trait على المتغير type Foo = impl Trait .

أوافق على أنه يجب استخدامه كلما أمكن ذلك ؛ ولكن ماذا عن حالة (impl T, impl U) حيث لا يمكن استخدام بناء الجملة المرتبط مباشرة؟ يبدو لي أن إدخال أسماء مستعارة مؤقتة يضر بالقراءة.

يبدو أن استخدام مجرد type Name: Bound سيكون مربكًا عند استخدامه داخل كتل impl :

impl Iterator for Foo {
    type Item: Display;

    fn next(&mut self) -> Option<Self::Item> { Some(5) }
}

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

pub abstract type First: Display;
pub abstract type Second: Debug;

impl Iterator for Foo {
    type Item = (First, Second);

    fn next(&mut self) -> Option<Self::Item> { Some((5, 6)) }
}

ضد

impl Iterator for Foo {
    type Item = (impl Display, impl Debug);

    fn next(&mut self) -> Option<Self::Item> { Some((5, 6)) }
}

@ Nemo157 لماذا لا نسمح بكليهما:

pub type First: Display;
pub type Second: Debug;

impl Iterator for Foo {
    type Item = (First, Second);
    fn next(&mut self) -> Option<Self::Item> { Some((5, 6)) }
}

و:

impl Iterator for Foo {
    type Item = (impl Display, impl Debug);
    fn next(&mut self) -> Option<Self::Item> { Some((5, 6)) }
}

؟

لا أفهم سبب الحاجة إلى وجود صيغتين لنفس الميزة ، باستخدام بناء الجملة type Name = impl Bound; يوفر أسماء الجزأين بشكل صريح ، فسيظل ممكنًا:

pub type First = impl Display;
pub type Second = impl Debug;

impl Iterator for Foo {
    type Item = (First, Second);
    fn next(&mut self) -> Option<Self::Item> { Some((5, 6)) }
}

@ Nemo157 أوافق على أنه لا داعي (ولا ينبغي) أن يكونا تركيبين مختلفين. لا أجد type (بدون كلمة رئيسية بادئة) محيرًا على الإطلاق ، يجب أن أقول.

rpjohnst ما هي هيك وحدة متعددة الأشكال؟ :-) على أي حال ، لا أفهم سبب وجوب نمذجة بناء الجملة بعد تعريفات النوع المرتبطة ، والتي تضع حدود السمات على النوع. هذا لا علاقة له بالحدود .

alexreg الوحدة النمطية متعددة الأشكال هي التي تحتوي على معلمات النوع ، بنفس الطريقة التي يعمل بها fn foo(x: impl Trait) . إنه ليس شيئًا موجودًا ، ولذا لا أريد أن يعتقد الناس أنه موجود.

abstract type ( تحرير: لتسمية الميزة ، وليس اقتراح استخدام الكلمة الرئيسية) له علاقة بالحدود! الحدود هي الشيء الوحيد الذي تعرفه عن النوع. والفرق الوحيد بينها وبين الأنواع المرتبطة بها هو أنه تم استنتاجها ، لأنها عادة ما تكون غير قابلة للتسمية.

@ Nemo157 أصبح بناء الجملة Foo: Bar أكثر دراية بالفعل في سياقات أخرى (حدود على الأنواع المرتبطة ، وعلى معلمات النوع) وهو أكثر راحة و (IMO) واضحًا عندما يمكن استخدامه دون إدخال مؤقت.

جاري الكتابة:

type IterDisplay: Iterator<Item: Display>;

يبدو كتابًا مباشرًا أكثر بكثير. ما أريد أن أقوله بالمقارنة مع

type IterDisplay = impl Iterator<Item = impl Display>;

أعتقد أن هذا مجرد تطبيق ثابت لدينا بالفعل ؛ لذلك فهي ليست جديدة حقًا.

EDIT2: الصيغة الأولى هي أيضًا الطريقة التي أريدها بها في rustdoc.

يصبح الانتقال من سمة تتطلب شيئًا ما على نوع مرتبط ، إلى ضمني أمرًا سهلاً أيضًا:

trait Foo {
    type Bar: Baz;
    // stuff...
}

struct Quux;

impl Foo for Quux {
    type Bar: Baz; // Oh look! Same as in the trait; I had to do nothing!
    // stuff...
}

يبدو بناء الجملة impl Bar أفضل عندما تضطر إلى تقديم مؤقتات ، لكنه يطبق أيضًا بناء الجملة باستمرار طوال الوقت.

إن القدرة على استخدام كلا الصيغتين لن تختلف كثيرًا عن القدرة على استخدام impl Trait في موضع الوسيطة بالإضافة إلى وجود معلمة نوع صريحة T: Trait يتم استخدامها بعد ذلك بواسطة وسيطة.

EDIT1: في الواقع ، سيكون وجود بنية واحدة فقط غلافًا خاصًا ، وليس العكس.

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

على أي حال ، أنا لست ضد بناء الجملة type Foo: Bar; ، ولكن من أجل الخير ، دعنا نتخلص من الكلمة الأساسية abstract . type بحد ذاته واضح تمامًا ، في أي ظرف من الظروف.

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

أيضًا ، بافتراض أنني رأيت type Iter: Iterator<Item = Foo> ، فسوف يتعين عليّ العثور على Foo ومعرفة ما إذا كان نوعًا أم سمة قبل أن أعرف ما يحدث.

وأخيرًا ، أعتقد أن الدليل المرئي لنقاط الاستدلال سيساعد أيضًا في تصحيح أخطاء الاستدلال وتفسير رسائل خطأ الاستدلال.

لذلك أعتقد أن المتغير = / impl يحل القليل من الورق.

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

أيضًا ، بافتراض أنني رأيت النوع Iter: Iterator<Item = Foo> ، فسوف يتعين عليّ العثور على Foo ومعرفة ما إذا كان نوعًا أم سمة قبل أن أعرف ما يحدث.

هذا لا أحصل عليه ؛ يجب أن يكون Item = Foo دائمًا نوعًا في الوقت الحاضر نظرًا لأن dyn Foo مستقر (ويتم التخلص التدريجي من السمة المجردة ...)؟

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

هذا لا أحصل عليه ؛ العنصر = Foo يجب أن يكون دائمًا نوعًا في الوقت الحاضر نظرًا لأن dyn Foo مستقر (ويتم التخلص التدريجي من السمة المجردة ...)؟

نعم ، ولكن في المتغير الأقل impl المقترح ، يمكن أن يكون نوعًا مستنتجًا بنوع محدد أو محدد. على سبيل المثال ، Iterator<Item = String> مقابل Iterator<Item = Display> . يجب أن أعرف السمات لأعرف ما إذا كان الاستدلال يحدث أم لا.

تحرير: آه ، لم ألاحظ أن أحدًا استخدم : . كندة ما أعنيه بكل سهولة :) لكنك محق في أنهم مختلفون.

تحرير 2: أعتقد أن هذه المشكلة ستظل خارج الأنواع المرتبطة. بالنظر إلى type Foo: (Bar, Baz) ستحتاج إلى معرفة Bar و Baz لمعرفة مكان حدوث الاستنتاج.

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

EDIT1: في الواقع ، سيكون وجود بنية واحدة فقط غلافًا خاصًا ، وليس العكس.

توجد حاليًا طريقة واحدة فقط للإعلان عن أنواع _existential_ ، -> impl Trait . هناك طريقتان للإعلان عن أنواع _universal_ ( T: Trait و : impl Trait في قائمة وسيطات).

إذا كانت لدينا وحدات متعددة الأشكال تأخذ أنواعًا عالمية ، فيمكنني رؤية بعض الحجج حول ذلك ، لكنني أعتقد أن الاستخدام الحالي لـ type Name = Type; في كل من الوحدات النمطية وتعريفات السمات هو بمثابة معلمة لنوع الإخراج ، والتي يجب أن تكون وجودية نوع.


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

نعم ، ولكن في المتغير الأقل من impl المقترح ، يمكن أن يكون نوعًا مستنتجًا بنوع محدد أو محدد. على سبيل المثال ، Iterator<Item = String> مقابل Iterator<Item = Display> . يجب أن أعرف السمات لأعرف ما إذا كان الاستدلال يحدث أم لا.

أعتقد أن المتغير الأقل impl يستخدم : Bound في جميع الحالات للأنواع الوجودية ، لذلك يمكن أن يكون لديك Iterator<Item = String> أو Iterator<Item: Display> كحدود للسمة ، لكن Iterator<Item = Display> قد يكون

@ Nemo157
أنت محق فيما يتعلق بحالة النوع المرتبط ، سيئتي هناك. لكن (كما هو مذكور في تعديلي) أعتقد أنه لا تزال هناك مشكلة مع type Foo: (A, B) . نظرًا لأن A أو B يمكن أن يكون نوعًا أو سمة.

أعتقد أن هذا أيضًا سبب وجيه للذهاب مع = . يخبرك : فقط أنه تم استنتاج بعض الأشياء ، لكنه لا يخبرك بأي منها. type Foo = (A, impl B) أوضح بالنسبة لي.

أفترض أيضًا أن قراءة مقتطفات الشفرة وتقديمها أسهل باستخدام impl ، كسياق إضافي حول ما هي سمة وما لا يلزم توفيره أبدًا.

تحرير: بعض الاعتمادات: حجتي هي في الأساس نفس حجةalexreg هنا ، أردت فقط التوسع في سبب اعتقادي أن impl هو الأفضل.

يوجد حاليًا طريقة واحدة فقط للإعلان عن الأنواع الوجودية ، -> impl Trait . هناك طريقتان للإعلان عن الأنواع العامة ( T: Trait و : impl Trait في قائمة وسيطات).

هذا ما أقوله: PI لماذا يجب أن يكون للتقدير الكمي طريقتان ولكن وجودي واحد فقط (تجاهل dyn Trait ) في أماكن أخرى ؟

يبدو من المحتمل بنفس القدر بالنسبة لي أن يذهب المستخدم ويكتب type Foo: Bound; و type Foo = impl Bound; بعد أن تعلم أجزاء مختلفة من اللغة ، ولا يمكنني القول أن بناء جملة واحد أفضل بشكل واضح في جميع الحالات ؛ من الواضح لي أن بناء جملة أفضل لبعض الأشياء والآخر لأشياء مختلفة.

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

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

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

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

type A: Foo;
type B: Bar;
type C: Baz;
type D: Iterator<Item = C>; 
type E = (A, Vec<B>, D);

باستخدام الصيغة التي أفضّلها (والبعض الآخر هنا) ، يمكننا كتابة كل هذا في سطر واحد ، وعلاوة على ذلك ، من الواضح على الفور مكان حدوث القياس الكمي!

type E = (impl Foo, Vec<impl Bar>, impl Iterator<Item = impl Baz>);

غير متعلق بما سبق: متى نلعب لتطبيق let x: impl Trait ليلة؟ لقد كنت أفتقد هذه الميزة لفترة من الوقت الآن.

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

شيء آخر هو: هل يمكن للمرء أن يسمح حتى : ضمن ربط نوع مرتبط ، ضمن بناء الجملة هذا؟

نعم لما لا؛ سيكون هذا تأثيرًا طبيعيًا لـ rust-lang / rfcs # 2289 + type Foo: Bound .

يمكنك أيضًا القيام بما يلي:

type E = (impl Foo, Vec<impl Bar>, impl Iterator<Item: Baz>);

Centril أعتقد أنها فكرة سيئة للسماح

Centril أنا نوعاً ما مع nikomatsakis على RFC الخاص بك راجع للشغل ، آسف. تفضل كتابة impl Iterator<Item = impl Baz> . لطيفة وصريحة.

alexreg هذا عادل ؛

لكن (لسوء الحظ) (اعتمادًا على وجهة نظرك) ، بدأنا بالفعل "السماح بصيغتين بديلتين" باستخدام impl Trait في موضع الوسيطة ، بحيث يكون لدينا Foo: Bar و impl Bar العمل ليعني نفس الشيء؛

إنه من أجل القياس الكمي الشامل ، لكن الترميز impl Trait لا يهتم حقًا بأي جانب من الازدواجية هو عليه ؛ بعد كل شيء ، لم نذهب مع any Trait و some Trait .

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


ملاحظة:

إعادة. impl Iterator<Item = impl Baz> لا يعمل كقيد في جملة where؛ لذلك يجب عليك مزجها مثل Iter: Iterator<Item = impl Baz> . يجب أن تسمح: Iter = impl Iterator<Item = impl Baz> حتى تعمل بشكل موحد (ربما ينبغي علينا ذلك؟).

استخدام : Bound أيضًا بدلاً من = impl Bound هو أيضًا صريح ، فقط أقصر ^ ، -
أعتقد أن الاختلاف في التباعد بين X = Ty و X: Ty يجعل بناء الجملة مقروءًا.

بعد تجاهل نصيحتي الخاصة ، دعنا نواصل هذه المحادثة في RFC ؛)

لكن (لسوء الحظ) (اعتمادًا على POV الخاص بك) ، بدأنا بالفعل "السماح بصيغتين بديلتين" باستخدام سمة ضمنية في موضع المناقشة ، بحيث يكون لدينا كل من Foo: Bar و impl Bar يعملان على نفس المعنى ؛

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

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

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

ملاحظة:

إعادة. impl Iterator<Item = impl Baz> لا يعمل كقيد في جملة where؛ لذلك يجب عليك مزجها مثل Iter: Iterator<Item = impl Baz> . يجب أن تسمح بـ: Iter = impl Iterator<Item = impl Baz> حتى تعمل بشكل موحد (ربما ينبغي علينا ذلك؟).

أود أن أقول إننا إما ندعم فقط where Iter: Iterator<Item = T>, T: Baz (كما لدينا الآن) أو نذهب بالكامل مع Iter = impl Iterator<Item = impl Baz> (كما اقترحت). فقط السماح لمنزل في منتصف الطريق يبدو نوعًا من التفرغ.

استخدام : Bound هو أيضًا بدلاً من = impl Bound هو أيضًا صريح ، أقصر فقط ^ ، -
أعتقد أن الاختلاف في التباعد بين X = Ty و X: Ty يجعل بناء الجملة مقروءًا.

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

بعد تجاهل نصيحتي الخاصة ، دعنا نواصل هذه المحادثة في RFC ؛)

انتظر ، تقصد RFC الخاص بك؟ أعتقد أنه وثيق الصلة بهذا وهذا ، مما يمكنني قوله. :-)

انتظر ، تقصد RFC الخاص بك؟ أعتقد أنه وثيق الصلة بهذا وهذا ، مما يمكنني قوله. :-)

موافق؛ دعنا نواصل هنا إذن ؛

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

وجهة نظري كلها حول الاتساق والتماثل. = ص
إذا سُمح لك بكتابة impl Trait لكلٍّ من القياس الكمي الوجودي والعالمي ، فمن المنطقي بالنسبة لي أنه يجب أيضًا السماح لك باستخدام Type: Trait لكل من القياس الكمي العام والوجودي.

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

fn foo(bar: impl Trait, baz: typeof bar) { // eww... but possible!
    ...
}

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

حجتي هي أن مفاجأة المستخدمين بعبارة "هذه البنية قابلة للاستخدام في مكان آخر ومعناها واضح هنا ، ولكن لا يمكنك كتابتها في هذا المكان" تكلف أكثر من امتلاك طريقتين للقيام بذلك (والتي يتعين عليكما التعرف عليها على أي حال ). لقد فعلنا أشياء مماثلة مع https://github.com/rust-lang/rfcs/pull/2300 (مدمجة) ، https://github.com/rust-lang/rfcs/pull/2302 (PFCP) ، https : //github.com/rust-lang/rfcs/pull/2175 (مدمج) حيث نقوم بملء فجوات التناسق حتى مع إمكانية الكتابة بطريقة أخرى من قبل.

إنه مقروء ، لكنني لا أعتقد أنه من الواضح / الصريح تقريبًا استخدام نوع وجودي.

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

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

هذا لا أحصل عليه ؛ يبدو لي أن Assoc = impl Trait يجب أن يتسبب في انقسام السطر أكثر من Assoc: Trait لمجرد أن الأول أطول.

أود أن أقول إننا إما ندعم فقط where Iter: Iterator<Item = T>, T: Baz (كما لدينا الآن) أو نذهب بالكامل مع Iter = impl Iterator<Item = impl Baz> (كما اقترحت).
فقط السماح لمنزل في منتصف الطريق يبدو نوعًا من التفرغ.

بالضبط !، دعونا لا نذهب في منتصف الطريق للمنزل / المشاركة ونطبق where Iter: Iterator<Item: Baz> ؛)

@ Centril حسنًا ، لقد

سأقوم بتحرير هذا مع ردي الكامل غدا.

تعديل

كما يشير Centril ، نحن بالفعل ندعم الأنواع العامة باستخدام بناء الجملة : Trait (ملزم). على سبيل المثال

fn foo<T: Trait>(x: T) { ... }

جنبًا إلى جنب مع الأنواع العالمية "المناسبة" أو "المُحددة" ، على سبيل المثال

fn foo(x: impl Trait) { ... }

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

الآن ، لدينا بالفعل impl Trait في موضع إرجاع الوظيفة أيضًا ، والذي يمثل نوعًا وجوديًا. أنواع السمات المرتبطة موجودة في النموذج ، وتستخدم بالفعل بناء الجملة : Trait .

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

type A: Iterator<Item: Foo + Bar>;
type B = (impl Baz, impl Debug, String);

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

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

struct Foo {
    pub foo: impl Display,
}

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

هاتان عمليتان مختلفتان اختلافًا جوهريًا ، نعم كلاهما يستخدم حدود السمات ، لكني لا أرى أي سبب يجعل وجود طريقتين للإعلان عن نوع وجودي من شأنه أن يقلل من الارتباك للقادمين الجدد. إذا كانت محاولة استخدام type Name: Trait أمرًا محتملًا في الواقع بالنسبة للوافدين الجدد ، فيمكن حل ذلك عن طريق lint:

    type Foo: Display;
    ^^^^^^^^^^^^^^^^^^
note: were you attempting to create an existential type?
note: suggested replacement `type Foo = impl Display`

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

أشعر أنني لا أمتلك خبرة كافية مع Rust حتى الآن لأعلق على RFCs. ومع ذلك ، أنا مهتم برؤية هذه الميزة مدمجة في Rust الليلي والمستقر ، من أجل استخدامها مع Rust libp2p لبناء بروتوكول تجزئة لـ Ethereum كجزء من تنفيذ Drops of Diamond التجزئة . لقد اشتركت في المشكلة ، ولكن ليس لدي الوقت لمواكبة جميع التعليقات! كيف يمكنني البقاء على اطلاع بأحدث المستجدات بشأن هذه المشكلة ، دون الحاجة إلى قراءة التعليقات السريعة؟

أشعر أنني لا أمتلك خبرة كافية مع Rust حتى الآن لأعلق على RFCs. ومع ذلك ، أنا مهتم برؤية هذه الميزة مدمجة في Rust الليلي والمستقر ، من أجل استخدامها مع Rust libp2p لبناء بروتوكول تجزئة لـ Ethereum كجزء من تنفيذ Drops of Diamond التجزئة . لقد اشتركت في المشكلة ، ولكن ليس لدي الوقت لمواكبة جميع التعليقات! كيف يمكنني البقاء على اطلاع بأحدث المستجدات بشأن هذه المشكلة ، دون الحاجة إلى قراءة التعليقات السريعة؟ في الوقت الحالي ، يبدو أنني قد أضطر إلى القيام بذلك عن طريق تسجيل الوصول من وقت لآخر ، وعدم الاشتراك في المشكلة. سيكون من الجيد أن أتمكن من الاشتراك عن طريق البريد الإلكتروني للحصول على أخبار عالية المستوى حول هذا الموضوع.

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

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

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

في المناقشة ، أعتقد أن مجموعة طلبات التعليقات (RFCs) بأكملها واللغة كما هي يجب أن تؤخذ على أنها ليست مسلمات ، ثم حجج قوية.


يمكنك إثبات الحالة (لكنني لن أفعل) أن:

struct Foo {
    pub foo: impl Display,
}

يعادل لغويًا:

struct Foo<T: Display> {
    pub foo: T,
}

تحت المنطق الوظيفي-الحجة.

في الأساس ، بالنظر إلى impl Trait ، عليك أن تفكر "هل هذا يشبه نوع الإرجاع ، أم مثل الحجة؟" ، والتي يمكن أن تكون صعبة.


إذا كانت محاولة استخدام type Name: Trait أمرًا محتملًا فعليًا بالنسبة للوافدين الجدد ، فيمكن حل ذلك عن طريق lint:

أود أيضًا أن ألتقط ، لكن في الاتجاه الآخر ؛ أعتقد أن الطرق التالية يجب أن تكون اصطلاحية:

// GOOD:
type Foo: Iterator<Item: Display>;

type Bar = (impl Display, impl Debug);

// BAD
type Foo = impl Iterator<Item = impl Display>;

type Bar0: Display;
type Bar1: Debug;
type Bar = (Bar0, Bar1);

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

لا يوجد سوى _طريقة واحدة_ للإعلان عن الأنواع الكمية وجوديًا: existential type Name: Bound; (باستخدام existential لأن ذلك محدد في RFC ، فأنا لا أعارض تمامًا إسقاط الكلمة الأساسية ضمن هذه الصيغة).

هناك أيضًا سكر للإعلان ضمنيًا عن نوع كمي وجودي غير مسمى في النطاق الحالي: impl Bound (تجاهل السكر الكمي العام في وسيطات الوظيفة في الوقت الحالي).

لذا ، فإن استخدام نوع الإرجاع الحالي هو حل بسيط:

fn foo() -> impl Iterator<Item = impl Display> { ... }
existential type _0: Display;
existential type _1: Iterator<Item = _0>;
fn foo() -> _1 { ... }

يمتد إلى const و static و let هو أمر تافه بالمثل.

الامتداد الوحيد غير المذكور في RFC هو: دعم هذا السكر في بناء جملة type Alias = Concrete; ، لذلك عندما تكتب

type Foo = impl Iterator<Item = impl Display>;

هذا هو السكر في الواقع

existential type _0: Display;
existential type _1: Iterator<Item = _0>;
type Foo = _1;

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

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

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

أنا أتفق في الغالب مع تعليق

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

إذن ... لقد أجرينا الكثير من المناقشات حول بناء الجملة للأنواع الوجودية المسماة الآن. هل نحاول الوصول إلى استنتاج وكتابته في منشور RFC / PR ، بحيث يمكن لشخص ما بدء العمل على التنفيذ الفعلي؟ :-)

شخصيًا ، بمجرد أن نطلق على الوجود ، أفضل استخدام نسج (إن وجد) بعيدًا عن أي استخدام لـ impl Trait أي مكان .

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

هل RFC الموجود على impl Trait في موضع الوسيطة محدث؟ إذا كان الأمر كذلك ، فهل من الآمن القول أن دلالاتها "عالمية"؟ إذا كان الأمر كذلك: أريد أن أبكي. بشدة.

phaazon : ملاحظات إصدار Rust 1.26 مقابل impl Trait :

ملاحظة جانبية لكتابة المنظرين هناك: هذا ليس وجوديًا ، ولا يزال عالميًا. بمعنى آخر ، السمة الضمنية هي سمة عامة في موضع الإدخال ، ولكنها وجودية في موضع الإخراج.

فقط للتعبير عن أفكاري حول ذلك:

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

أعتقد حقا impl Trait في القرار الحالي الموقف حجة لأمر مؤسف. :بكاء:

أعتقد حقًا أن السمة الضمنية في موقف الحجة القرار الحالي أمر مؤسف. 😢

على الرغم من أنني ممزقة قليلاً بشأن هذا الأمر ، أعتقد بالتأكيد أنه سيكون من الأفضل قضاء الوقت في تنفيذ let x: impl Trait الآن!

سوف تفتح لنا الوجود المتغاير الأبواب لوظائف الرتبة n

لدينا بالفعل بناء جملة له ( fn foo(f: impl for<T: Trait> Fn(T)) ) ، (المعروف أيضًا باسم "نوع HRTB") ، لكنه لم يتم تنفيذه بعد. ينتج عن fn foo(f: impl Fn(impl Trait)) خطأ "غير مسموح بتداخل impl Trait " ، وأتوقع أننا نريده أن يعني الإصدار الأعلى رتبة ، عندما نحصل على نوع HRTB.

هذا مشابه لما يعنيه Fn(&'_ T) for<'a> Fn(&'a T) ، لذلك لا أتوقع أن يكون الأمر مثيرًا للجدل.

بالنظر إلى المسودة الحالية ، فإن موضع الوسيطة impl Trait هو _ عالمي_ ، لكنك تقول أن impl for<_> Trait يحولها إلى _existential_ ؟! كم هو مجنون هذا؟

لماذا اعتقدنا أن لدينا حاجة لتقديم _ ثم بطريقة أخرى _ لبناء _ عالمي _؟ أعني:

fn foo(x: impl MyTrait)

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

fn foo(x: impl Trait) -> impl Trait

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

أرغ أعتقد أن كل هذا تم قبوله بالفعل وأنا أصرخ من أجل لا شيء. أنا فقط أعتقد أنه أمر مؤسف حقيقي. أنا متأكد من أنني لست الوحيد المحبط من قرار RFC.

(ربما لا توجد فائدة كبيرة من الاستمرار في مناقشة هذا الأمر بعد استقرار الميزة ، ولكن انظر هنا للحصول على حجة مقنعة (والتي أتفق معها) حول سبب وجود الدلالات المنطقية في impl Trait في موضع النقاش. متماسك. Tl ؛ dr هو لنفس السبب الذي يجعل fn foo(arg: Box<Trait>) يعمل بنفس الطريقة تقريبًا مثل fn foo<T: Trait>(arg: Box<T>) ، على الرغم من أن dyn Trait هو أمر وجودي ؛ الآن استبدل dyn بـ impl .)

بالنظر إلى المسودة الحالية ، فإن موضع الوسيطة impl Trait عام ، لكنك تقول أن impl for<_> Trait يحولها إلى وجودي ؟!

لا ، كلاهما عالمي. أنا أقول أن الاستخدامات ذات الترتيب الأعلى ستبدو كما يلي:

fn foo<F: for<G: Fn(X) -> Y> Fn(G) -> Z>(f: F) {...}

والتي يمكن كتابتها في نفس وقت إضافتها (على سبيل المثال بدون تغييرات على impl Trait ) على النحو التالي:

fn foo(f: impl for<G: Fn(X) -> Y> Fn(G) -> Z) {...}

هذا عالمي impl Trait ، فقط أن Trait هو HRTB (على غرار impl for<'a> Fn(&'a T) ).
إذا قررنا (وهو ما أتوقعه على الأرجح) أن الوسيطات impl Trait داخل Fn(...) هي أيضًا عامة ، فيمكنك كتابة هذا لتحقيق نفس التأثير:

fn foo(f: impl Fn(impl Fn(X) -> Y) -> Z) {...}

هذا ما اعتقدت أنك تقصده بـ "مرتبة أعلى" ، إذا لم تكن كذلك ، فيرجى إبلاغي بذلك.

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

fn foo() -> impl for<G: Fn(X) -> Y> Fn(G) -> Z {...}

أن تكتب مثل هذا:

fn foo() -> impl Fn(impl Fn(X) -> Y) -> Z {...}

سيكون هذا impl Trait وجوديًا يحتوي على impl Trait عالميًا (مرتبط بالوظيفة الوجودية ، بدلاً من وظيفة التضمين).

eddyb ألن يكون من المنطقي أن يكون لديك كلمتان رئيسيتان منفصلتان للتقدير الوجودي والكمي العام بشكل عام ، من أجل التناسق وعدم الخلط بين المبتدئين؟
ألن تكون الكلمة الرئيسية للتقدير الوجودي قابلة لإعادة الاستخدام أيضًا للأنواع الوجودية؟
لماذا نستخدم impl لوجوديا (والعالمي) الكمي ولكن existential لأنواع الوجودية؟

أود أن أوضح ثلاث نقاط:

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

إنه لأمر محزن حقًا أن تصل هذه التعليقات بعد الاستقرار ويجب فعل شيء حيال هذه الظاهرة

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

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

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

صحيح ، لم نتمكن من الحصول على تعليقات من الأشخاص الجدد على الصدأ والذين لم يكونوا من أصحاب النظريات

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

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

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

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

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

لا ، إن إضافة كلمتين رئيسيتين إضافيتين لتحديد الأنواع الوجودية والعالمية لن يؤدي إلى تحسين الالتباس ، بل سيجعل الأمور أكثر سوءًا.

أسوأ؟ كيف ذلك؟ أفضل أن أتذكر أكثر من الغموض / الارتباك.

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

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

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

كما قلت ، ربما يكون من الأفضل إجراء المناقشة الوصفية حول التعليقات في مكان آخر

على سبيل المثال هنا ، لقد بدأت بالفعل مثل هذه المناقشة وهناك بعض الخطوات الفعلية التي يمكن اتخاذها ، حتى لو كانت صغيرة: https://internals.rust-lang.org/t/idea-mandate-n-independent-uses -قبل-استقرار-ميزة / 7522/14. لم يكن لدي الوقت لكتابة RFC ، لذلك إذا قام شخص ما بضربني على ذلك أو أراد المساعدة ، فلن أمانع.

أسوأ؟ كيف ذلك؟

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

impl Trait كما هو الحال في انتقاء المستدعين سيكون كافياً. إذا حاولت استخدامه في موقف الحجة ، فأنت تقدم الالتباس. من شأن HRTBs إزالة هذا الالتباس.

vorner سابقًا جادلت بأنه يجب علينا إجراء اختبار A / B الفعلي مع مبتدئين Rust لمعرفة ما يجدون في الواقع أسهل وأصعب في التعلم ، لأنه من الصعب تخمينه كشخص يتقن التعامل مع Rust.
أتذكر ، FWIW ، عندما كنت أتعلم Rust (قادمًا من C ++ ، D ، Java ، إلخ) ، كان من السهل فهم النوع الكمي عالميًا (بما في ذلك بناء الجملة) (كانت الأعمار في الأدوية الجنسية أصعب قليلاً).
أعتقد أن impl Trait لأنواع الوسائط ستؤدي إلى الكثير من الارتباك من المبتدئين أسفل السطر والعديد من الأسئلة مثل هذا .
في حالة عدم وجود أي دليل على أن التغييرات ستجعل تعلم Rust أسهل ، يجب علينا الامتناع عن إجراء مثل هذه التغييرات ، وبدلاً من ذلك إجراء تغييرات تجعل / تحافظ على Rust أكثر اتساقًا لأن الاتساق يجعله على الأقل سهل التذكر. سيتعين على المبتدئين في Rust قراءة الكتاب عدة مرات على أي حال ، لذا فإن تقديم impl Trait لـ args للسماح بتأجيل الأدوية الجنيسة في الكتاب حتى وقت لاحق لا يزيل أي تعقيد.

eddyb بالمناسبة ، لماذا نحتاج إلى كلمة رئيسية أخرى existential للأنواع بالإضافة إلى impl ؟ (أتمنى أن نستخدم some لكليهما ..)

أتذكر ، FWIW ، عندما كنت أتعلم Rust (قادمًا من C ++ ، D ، Java ، إلخ) ، كان من السهل فهم النوع الكمي عالميًا (بما في ذلك بناء الجملة) (كانت الأعمار في الأدوية الجنسية أصعب قليلاً).

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

لذا ، فإن السؤال هو ، هل لدى المجتمع صوت كافٍ لإعادة فتح النقاش ببعض البيانات لدعم الحجج؟

eddyb Btw ، لماذا نحتاج إلى كلمة رئيسية وجودية أخرى للأنواع بالإضافة إلى الضمني؟ (أتمنى أن نستخدم البعض لكليهما ..)

لماذا لا forall … / أنا أتسلل ببطء بعيدًا

phaazon لدينا forall (أي "عالمي") وهو for ، على سبيل المثال في HRTB ( for<'a> Trait<'a> ).

eddyb Yep ، ثم استخدمها للوجود أيضًا ، كما تفعل Haskell مع forall ، على سبيل المثال.

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

أنا لا أفهم ذلك حقًا. ما الهدف من وجودهم في موقف حجة؟ أنا لا أكتب الكثير من Rust ، لكنني أحببت حقًا أن أكون قادرًا على القيام بـ -> impl Trait . متى سأستخدمها في موقف الحجة؟

كنت أفهم أنه كان في الغالب من أجل الاتساق. إذا كان بإمكاني كتابة النوع impl Trait في موضع وسيطة في توقيع fn ، فلماذا لا يمكنني كتابته في مكان آخر؟

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

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

أيضا ، ثم تنشأ المشكلة التي يجب أن تقاوم / ضد!

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

لذلك لا يمكنك أن تأخذ عنوانها

يمكنك ، من خلال الاستدلال عليه من التوقيع.

ما الهدف من وجودهم في موقف حجة؟

لا يوجد شيء لأنه نفس الشيء تمامًا مثل استخدام تقييد سمة.

fn foo(x: impl Debug)

هو بالضبط نفس الشيء مثل

fn foo<A>(x: A) where A: Debug
fn foo<A: Debug>(x: A)

أيضًا ، ضع في اعتبارك هذا:

fn foo<A>(x: A) -> A where A: Debug

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

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

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

Verner مع

ما مدى الاتساق عندما يختار المستدعي النوع -> impl Trait بينما يختار المتصل النوع في x: impl Trait ؟

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

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

كان لدينا RFCs ، اللذان تلقيا ما يقرب من 600 تعليق بينهما منذ أكثر من عامين ، لحل الأسئلة التي يتم نقلها إلى هذا الموضوع:

  • rust-lang / rfcs # 1522 ("الحد الأدنى impl Trait ")
  • rust-lang / rfcs # 1951 ("إنهاء بناء الجملة وتحديد نطاق المعلمة impl Trait ، مع توسيعها إلى الوسائط")

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

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

كيف تم تثبيته عندما لم يحصل impl Trait حتى على دعم في موقف الحجة لـ fns في السمات ؟؟

daboross ثم يجب تحديد خانة الاختيار في

(اكتشفت فقط أن https://play.rust-lang.org/؟gist=47b1c3a3bf61f33d4acb3634e5a68388&version=stable يعمل حاليًا)

أعتقد أنه من الغريب أن https://play.rust-lang.org/؟gist=c29e80715ac161c6dc95f96a7f91aa8c&version=stable&mode=debug لا يعمل (حتى الآن) ، ما هو أكثر من رسالة الخطأ هذه. هل أنا الوحيد الذي يفكر بهذه الطريقة؟ ربما ستحتاج إلى إضافة مربع اختيار لـ impl Trait في موضع الإرجاع في السمات ، أو هل كان قرارًا واعيًا للسماح فقط impl Trait في موضع الوسيطة لوظائف السمات ، مما يفرض استخدام existential type لأنواع الإرجاع؟ (الذي ... قد يبدو غير متسق بالنسبة لي ، لكن ربما أفتقد نقطة ما؟)

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

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

نعم - تم تأجيل موضع الإرجاع impl Trait في السمات حتى نمتلك المزيد من الخبرة العملية في استخدام الأنواع الوجودية في السمات.

cramertj هل وصلنا إلى المرحلة التي نمتلك فيها الخبرة العملية الكافية لتنفيذ ذلك؟

أود أن أرى سمة ضمنية في عدد قليل من الإصدارات المستقرة قبل أن نضيف المزيد من الميزات.

@ mark-im فشلت في معرفة ما هو مثير للجدل عن بعد حول موضع الإرجاع impl Trait لطرق السمات ، شخصيًا ... ربما أفتقد شيئًا ما.

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

أرى. أعتقد أنني أعتبرها جزءًا مفقودًا من ميزة موجودة أكثر من كونها ميزة جديدة.

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

phaazon ربما ، نعم ... لا أعرف حقًا مدى اختلاف تفاصيل التنفيذ مقارنة بما لدينا بالفعل اليوم ، ولكن ربما يمكن لشخص ما التعليق على ذلك. أود أن أرى أنواعًا وجودية لارتباطات let / const أيضًا ، لكن يمكنني بالتأكيد قبول ذلك كميزة تتجاوز هذه الميزة ، وبالتالي انتظار دورة أخرى أو نحو ذلك قبل البدء بها.

أتساءل عما إذا كان بإمكاننا التراجع عن السمات الضمنية العامة في السمات ...

لكن نعم ، أعتقد أنني أرى وجهة نظرك.

@ mark-im لا لا نستطيع ، فهي مستقرة بالفعل .

إنها في وظائف ، لكن ماذا عن إعلانات السمات؟

@ mark-im كما يظهر في المقتطف ، فهي مستقرة في كل من الضمانات وإعلانات السمات ..

ما عليك سوى القفز للحاق بالمستوى الذي نستقر فيه بـ abstract type . أنا شخصياً مشترك في بناء الجملة وأفضل الممارسات المقترحة من

// GOOD:
type Foo: Iterator<Item: Display>;

type Bar = (impl Display, impl Debug);

// BAD
type Foo = impl Iterator<Item = impl Display>;

type Bar0: Display;
type Bar1: Debug;
type Bar = (Bar0, Bar1);

التي تنطبق على بعض الرموز الخاصة بي أعتقد أنها ستبدو مثل:

// Concrete type with a generic body
struct Data<TBody> {
    ts: Timestamp,
    body: TBody,
}


// A name for an inferred iterator
type IterData = Data<impl Read>;
type Iter: Iterator<Item = IterData>;


// A function that gives us an iterator. Also takes some arbitrary range
fn iter(&self, range: impl RangeBounds<Timestamp>) -> Result<Iter, Error> { ... }


// A struct that holds on to that iterator
struct HoldsIter {
    iter: Iter,
}

ليس من المنطقي بالنسبة لي أن يكون type Bar = (impl Display,); جيدًا ، لكن type Bar = impl Display; سيكون أمرًا سيئًا.

إذا اتخذنا قرارًا بشأن تراكيب لنوع وجودي بديل مختلف (كلها مختلفة عن rfc 2071 ؟) ، فهل سيكون موضوع المنتدى على https://users.rust-lang.org/ مكانًا جيدًا للقيام بذلك؟

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

ما الخطأ في type Foo = impl Trait ؟

daboross ربما يكون المنتدى الداخلي بدلاً من ذلك. أنا أفكر في كتابة RFC حول هذا الموضوع لإنهاء بناء الجملة.

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

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

هل هناك أي حجة متعلقة بالماكرو لبناء جملة أو للآخر؟

tomaka في الحالة الأولى type Foo = (impl Display,) هو في الحقيقة الصيغة الوحيدة التي لديك. إن تفضيلي لـ type Foo: Trait على type Foo = impl Trait يأتي فقط من حقيقة أننا نلزم نوعًا يمكننا تسميته ، مثل <TFoo: Trait> أو where TFoo: Trait ، بينما مع impl Trait لا يمكننا تسمية النوع.

للتوضيح ، أنا لا أقول أن type Foo = impl Bar سيء ، فأنا أقول إن type Foo: Bar أفضل في الحالات البسيطة ، ويرجع ذلك جزئيًا إلى دافعKodrAus .

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

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

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

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

إليك كيف أقرأ تعريفات الأنواع هذه:

  • type Foo: Trait تعني " Foo هو نوع يطبق Trait "
  • يعني type Foo = impl Trait " Foo هو اسم مستعار من نوع ما يطبق Trait "

بالنسبة لي ، يعلن Foo: Trait ببساطة قيدًا على Foo تنفيذ Trait . بطريقة ما، type Foo: Trait يشعر غير مكتملة. يبدو أن لدينا قيدًا ، لكن التعريف الفعلي Foo مفقود.

من ناحية أخرى ، فإن impl Trait يشير إلى "هذا نوع واحد ، لكن المترجم يكتشف اسمه". لذلك ، يعني type Foo = impl Trait أن لدينا بالفعل نوعًا ملموسًا (والذي يستخدم Trait ) ، منه Foo هو مجرد اسم مستعار.

أعتقد أن type Foo = impl Trait ينقل المعنى الصحيح بشكل أوضح: Foo هو اسم مستعار من نوع ما يستخدم Trait .

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

يعني type Foo: Trait "Foo هو نوع يقوم بتنفيذ السمات"
[..]
بطريقة ما ، يبدو أن type Foo: Trait غير مكتمل.

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

من ناحية أخرى ، فإن impl Trait يشير إلى "هذا نوع واحد ، لكن المترجم يملأ الفراغ". لذلك ، يشير type Foo = impl Trait إلى أن لدينا بالفعل نوعًا ملموسًا (والذي يستخدم Trait ) ، منه Foo هو اسم مستعار ، لكن المترجم سوف يكتشف النوع الذي هو بالفعل.

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

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

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

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

يعتبر الانقسام الموسع مقابل الانقسام المكثف أمرًا مثيرًا للاهتمام - لم أفكر مطلقًا في impl Trait بهذه الطريقة من قبل.

مع ذلك ، أختلف في الاستنتاج. FWIW ، لم أتمكن أبدًا من البحث عن الأنواع الوجودية في Haskell و Scala ، لذا اعتبروني مبتدئًا. :) impl Trait in Rust بدا بديهيًا جدًا منذ اليوم الأول ، وربما يرجع ذلك إلى حقيقة أنني أعتقد أنه اسم مستعار مقيد بدلاً من ما يمكن فعله بالنوع. حتى بين معرفة ما Foo هو وما يمكن القيام به مع ذلك، وأنا اختيار الأول.

فقط 2c الخاص بي ، رغم ذلك. قد يكون لدى الآخرين نماذج عقلية مختلفة impl Trait .

أتفق تمامًا مع هذا التعليق : يبدو أن type Foo: Trait غير مكتمل. ويبدو أن type Foo = impl Trait أكثر تشابهًا مع استخدامات impl Trait أي مكان آخر ، مما يساعد اللغة على الشعور بمزيد من الاتساق والتذكر.

joshtriplett راجع https://github.com/rust-lang/rust/issues/34511#issuecomment -387238653 لبدء مناقشة الاتساق ؛ أعتقد أن السماح لأشكال النموذج هو في الواقع الشيء المتسق الذي يجب القيام به. والسماح فقط بأحد النماذج (أيهما ...) غير متسق. السماح بـ type Foo: Trait يتناسب أيضًا بشكل جيد مع https://github.com/rust-lang/rfcs/pull/2289 والذي يمكنك من خلاله تحديد: type Foo: Iterator<Item: Display>; مما يجعل الأشياء موحدة بدقة.

stjepang لا يتطلب منك المنظور type Foo: Bar; فهم الكميات الوجودية في نظرية النوع. كل ما تحتاج إلى فهمه حقًا هو أن Foo يتيح لك القيام بجميع العمليات التي يوفرها Bar ، هذا كل شيء. من منظور مستخدم Foo ، هذا أيضًا كل ما هو مثير للاهتمام.

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

أعتقد أنني أفهم الآن من أين أتيت وجاذبية دفع بناء الجملة Type: Trait إلى أكبر عدد ممكن من الأماكن.

هناك دلالة قوية حول استخدام : لحدود نوع-implements-trait و = المستخدمة لتعريفات النوع و type-equals-another-type bands.

أعتقد أن هذا واضح في RFC أيضًا. على سبيل المثال ، خذ هذين النوعين من الحدود:

  • Foo: Iterator<Item: Bar>
  • Foo: Iterator<Item = impl Bar>

هذان الحدين في النهاية لهما نفس التأثير ، لكنهما (على ما أعتقد) مختلفان تمامًا. يقول الأول " Item يجب أن ينفذ السمة Bar " ، بينما يقول الأخير " Item يجب أن يكون مساويًا لنوع ما ينفذ Bar ".

دعني أحاول توضيح هذه الفكرة باستخدام مثال آخر:

trait Person {
    type Name: Into<String>; // Just a type bound, not a definition!
    // ...
}

struct Alice;

impl Person for Alice {
    type Name = impl Into<String>; // A concrete type definition.
    // ...
}

كيف يجب أن نحدد نوعًا وجوديًا يستخدم Person إذن؟

  • type Someone: Person ، يبدو كنوع مرتبط.
  • type Someone = impl Person ، وهو يشبه تعريف النوع.

stjepang يبدو أن نوعًا مرتبطًا ليس بالأمر السيئ :) يمكننا تنفيذ Person for Alice مثل ذلك:

struct Alice;
trait Person          { type Name: Into<String>; ... }
impl Person for Alice { type Name: Into<String>; ... }

نظرة m'a! العناصر الموجودة داخل { .. } لكل من السمة والتضمين متطابقة ، وهذا يعني أنه يمكنك نسخ النص من السمة دون تغيير بقدر ما يتعلق الأمر بـ Name .

نظرًا لأن النوع المرتبط هو دالة على مستوى النوع (حيث تكون الوسيطة الأولى هي Self ) ، يمكننا أن نرى اسمًا مستعارًا للنوع كنوع مرتبط بـ 0-arity ، لذلك لا يحدث شيء غريب.

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

نعم؛ أجد الصياغة الأولى أكثر في الجوهر وطبيعية. :)

تضمين التغريدة هل هذا يعني أن type Thing; وحده كافٍ لتقديم نوع مجردة؟

trait Neg           { type Output; fn neg(self) -> Self::Output; }
impl Neg for MyType { type Output; fn neg(self) -> Self::Output { self } }

@ kennytm أعتقد أنه ممكن تقنيًا ؛ ولكن يمكنك أن تسأل عما إذا كان ذلك مرغوبًا أو لا يعتمد على الأفكار الضمنية / الصريحة. في هذه الحالة بالذات ، أعتقد أنه سيكون من الكافي من الناحية الفنية كتابة:

trait Neg           { type Output; fn neg(self) -> Self::Output; }
impl Neg for MyType { fn neg(self) -> Self::Output { self } }

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

أعتقد أنه بعد قراءة هذا كله ، أميل إلى الاتفاق أكثر مع @ Centril. عندما أرى type Foo = impl Bar أميل إلى الاعتقاد بأن Foo هو نوع معين ، كما هو الحال مع الأسماء المستعارة الأخرى. لكنها ليست كذلك. ضع في اعتبارك هذا المثال:

type Displayable = impl Display;

fn foo() -> Displayable { "hi" }
fn bar() -> Displayable { 42 }

IMHO من الغريب بعض الشيء رؤية = في إعلان قابل للعرض ولكن بعد ذلك لا تكون أنواع الإرجاع foo و bar متساوية (أي أن هذا = ليس متعدٍ ، على عكس أي مكان آخر). تكمن المشكلة في أن Foo ليس اسمًا مستعارًا لنوع معين يحدث لتضمين بعض السمات. بعبارة أخرى ، إنه نوع واحد في أي سياق يتم استخدامه ولكن هذا النوع قد يكون مختلفًا للاستخدامات المختلفة ، كما في المثال.

ذكر قلة من الناس أن type Foo: Bar يشعر بأنه "غير مكتمل". بالنسبة لي هذا شيء جيد. بمعنى ما ، Foo غير مكتمل ؛ لا نعرف ما هو ، لكننا نعلم أنه يرضي Bar .

@ مارك ايم

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

واو ، هل هذا صحيح حقا؟ من المؤكد أن هذا سيكون محيرًا للغاية بالنسبة لي.

هل هناك سبب يجعل Displayable اختصارًا لـ impl Display بدلاً من نوع واحد محدد؟ هل هذا السلوك مفيد حتى مع الأخذ في الاعتبار أنه يمكن استخدام الأسماء المستعارة للسمات (مشكلة التتبع: https://github.com/rust-lang/rust/issues/41517) بطريقة مماثلة؟ مثال:

trait Displayable = Display;

fn foo() -> impl Displayable { "hi" }
fn bar() -> impl Displayable { 42 }

@ مارك ايم

type Displayable = impl Display;

fn foo() -> Displayable { "hi" }
fn bar() -> Displayable { 42 }

هذا ليس مثالا صحيحا. من قسم المراجع حول الأنواع الوجودية في RFC 2071 :

existential type Foo = impl Debug;

Foo كـ i32 في أماكن متعددة في الوحدة. ومع ذلك ، فإن كل دالة تستخدم Foo كـ i32 يجب أن تضع قيودًا على Foo بحيث يجب أن تكون i32

يجب أن يكون كل إعلان عن نوع وجودي مقيدًا بجسم وظيفي واحد على الأقل أو مُهيئ ثابت / ثابت. يجب على الجسم أو المُهيئ إما تقييد أو عدم وضع قيود على نوع وجودي معين.

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

أعتقد أن المثال الخاص بك سيعطي شيئًا مثل expected type `&'static str` but found type `i32` على عائد bar ، نظرًا لأن foo قد قام بالفعل بتعيين النوع الملموس من Displayable إلى &'static str .

تحرير: ما لم تكن قادمًا إلى هذا من الحدس الذي

type Displayable = impl Display;

fn foo() -> Displayable { "hi" }
fn bar() -> Displayable { 42 }

يعادل

fn foo() -> impl Display { "hi" }
fn bar() -> impl Display { 42 }

بدلا من توقعي

existential type _0 = impl Display;
type Displayable = _0;

fn foo() -> Displayable { "hi" }
fn bar() -> Displayable { 42 }

أعتقد أن أيًا من هذين التفسرين صحيح قد يعتمد على RFC الذي قد يكتب

تكمن المشكلة في أن Foo ليس اسمًا مستعارًا لنوع معين يحدث لتضمين بعض السمات.

أعتقد أن أيًا من هذين التفسرين صحيح قد يعتمد على RFC الذي قد يكتب

سبب وجود type Displayable = impl Display; هو أنه اسم مستعار لنوع معين.
راجع https://github.com/rust-lang/rfcs/issues/1738 ، وهي المشكلة التي تحلها هذه الميزة.

@ Nemo157 توقعاتك صحيحة. :)

الأتى:

type Foo = (impl Bar, impl Bar);
type Baz = impl Bar;

سيتم إلغاءه من أجل:

/* existential */ type _0: Bar;
/* existential */ type _1: Bar;
type Foo = (_0, _1);

/* existential */ type _2: Bar;
type Baz = _2;

حيث _0 و _1 و _2 كلها أنواع مختلفة اسميًا ، لذا فإن Id<_0, _1> و Id<_0, _2> و Id<_1, _2> (و المثيلات المتماثلة) كلها غير مأهولة ، حيث يتم تعريف Id في refl .

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

بالنسبة إلى بناء الجملة type Foo: Trait ، أتوقع تمامًا أن يكون مثل هذا ممكنًا:

trait Trait {
    type Foo: Display;
    type Foo: Debug;
}

بنفس الطريقة مثل where Foo: Display, Foo: Debug ممكن حاليا.

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

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

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

بالنسبة إلى بناء الجملة type Foo: Trait ، أتوقع تمامًا أن يكون مثل هذا ممكنًا:

إنه ممكن. تعلن هذه "الأسماء المستعارة للنوع" عن الأنواع المرتبطة (يمكن تفسير الأسماء المستعارة على أنها وظائف مستوى نوع 0-ary بينما الأنواع المرتبطة هي وظائف مستوى نوع + 1). بالطبع لا يمكنك الحصول على أنواع متعددة مرتبطة بنفس الاسم في سمة واحدة ، قد يكون ذلك مثل محاولة تحديد اسمين مستعارين من النوع بنفس الاسم في وحدة نمطية. في impl ، يتوافق type Foo: Bar أيضًا مع القياس الكمي.

أوه ، وأعتقد أنه كلما زاد بناء الجملة لدى Rust ، أصبح تعلمه أكثر صعوبة.

كلا الصيغتين مستخدمة بالفعل. type Foo: Bar; قانوني بالفعل في السمات ، وأيضًا للتقدير العام مثل Foo: Bar حيث Foo هو متغير نوع. يستخدم impl Trait للتقدير الوجودي في موضع الإرجاع وللتقدير العام في موضع الحجة. السماح بفجوات تناسق السدادات في اللغة. إنها أيضًا مثالية للسيناريوهات المختلفة ، وبالتالي فإن وجود كلاهما يمنحك الأفضل العالمي.

علاوة على ذلك ، من غير المحتمل أن يحتاج المبتدئ إلى type Foo = (impl Bar, impl Baz); . من المحتمل أن تكون معظم الاستخدامات type Foo: Bar; .

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

أفضل ما إذا كان سيتم توضيح ذلك. لذلك بدلا من

type Foo = impl SomeTrait;
fn foo_func() -> Foo { ... }

نكتب

fn foo_func() -> impl SomeTrait { ... }
type Foo = return_type_of(foo_func);

(مع اسم return_type_of ليتم تضمينها بالدراجة) ، أو حتى

fn foo_func() -> impl SomeTrait as Foo { ... }

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

trait Bar
{
    type Assoc: SomeTrait;
    fn func() -> Assoc;
}

impl Bar for SomeType
{
    type Assoc = return_type_of(Self::func);
    fn func() -> Assoc { ... }
}

او حتى

impl Bar for SomeType
{
    fn func() -> impl SomeTrait as Self::Assoc { ... }
}

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

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

إنه ممكن. تعلن هذه "الأسماء المستعارة للنوع" عن الأنواع المرتبطة (يمكن تفسير الأسماء المستعارة على أنها وظائف مستوى نوع 0-ary بينما الأنواع المرتبطة هي وظائف مستوى نوع + 1). بالطبع لا يمكنك الحصول على أنواع متعددة مرتبطة بنفس الاسم في سمة واحدة ، قد يكون ذلك مثل محاولة تحديد اسمين مستعارين من النوع بنفس الاسم في وحدة نمطية. في ضمني ، اكتب Foo: Bar يتوافق أيضًا مع القياس الوجودي.

(آسف ، قصدت وضعه في impl Trait for Struct ، وليس في trait Trait )

أنا آسف ، لست متأكدًا من فهمي. ما أحاول قوله ، بالنسبة لي هو رمز مثل

impl Trait for Struct {
    type Type: Debug;
    type Type: Display;

    fn foo() -> Self::Type { 42 }
}

(رابط الملعب للنسخة الكاملة)
أشعر أنه يجب أن يعمل.

لأنه مجرد وضع اثنين حدود على Type ، في نفس الطريقة كما where Type: Debug, Type: Display العمل .

إذا كان هذا غير مسموح به (ما يبدو أنني أفهمه من خلال "بالطبع لا يمكنك الحصول على أنواع متعددة مرتبطة بنفس الاسم في سمة واحدة"؟ ولكن نظرًا لخطئي في كتابة trait Trait بدلاً من impl Trait for Struct لست متأكدًا) ، إذن أعتقد أن هذه مشكلة في بناء الجملة type Type: Trait .

ثم ، داخل تصريح trait ، تكون البنية بالفعل type Type: Trait ، ولا تسمح بتعريفات متعددة. لذلك أعتقد أن هذا القارب قد أبحر بالفعل منذ فترة طويلة ...

ومع ذلك ، كما هو موضح أعلاه من قبل stjepang و @ joshtriplett ، type Type: Trait غير مكتمل. وعلى الرغم من أنه قد يكون منطقيًا في إعلانات trait (تم تصميمه بالفعل ليكون غير مكتمل ، على الرغم من أنه غريب أنه لا يسمح بتعريفات متعددة) ، فإنه لا معنى له في كتلة impl Trait ، حيث من المفترض أن يكون النوع معروفًا على وجه اليقين (ولا يمكن كتابته حاليًا إلا كـ type Type = RealType )

يستخدم impl Trait للتقدير الوجودي في موضع الإرجاع وللتقدير العام في موضع الوسيطة.

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

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

الأمثل والبساطة

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

impl Trait و :

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

تخيل مبتدئًا رأى دائمًا type Type: Trait يأتي على أول type Type = impl Trait . من المحتمل أن يخمنوا ما يحدث ، لكنني متأكد من أنه ستكون هناك لحظة "WTF هل هذا؟ لقد كنت أستخدم Rust لسنوات ولا يزال هناك بناء جملة لم أره مطلقًا؟ ". وهو أكثر أو أقل الفخ الذي سقطت فيه ++ C.

ميزة سخام

ما أفكر فيه هو ، في الأساس ، أنه كلما زادت الميزات التي تتمتع بها ، زادت صعوبة تعلم اللغة. ولا أرى ميزة كبيرة لاستخدام type Type: Trait على type Type = impl Trait : هل ، مثل ، حفظ 6 أحرف؟

وجود rustc يخرج خطأ عند رؤية type Type: Trait الذي يقول أن الشخص الذي يكتبه لاستخدام type Type = impl Trait سيكون أكثر منطقية بالنسبة لي: على الأقل هناك طريقة واحدة لكتابة الأشياء ، من المنطقي للجميع ( impl Trait معروف بالفعل بوضوح كموضع وجودي في الإرجاع) ، وهو يغطي جميع حالات الاستخدام. وإذا حاول الناس استخدام ما يعتقدون أنه حدسي (على الرغم من أنني لا أوافق على ذلك ، فإن = impl Trait بالنسبة لي أكثر سهولة ، مقارنة بـ = i32 ) ، يتم إعادة توجيههم بشكل صحيح إلى الطريقة الصحيحة التقليدية لكتابتها.

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

تمت مناقشة typeof باختصار في الإصدار الذي فتحته منذ 1.5 عام: https://github.com/rust-lang/rfcs/issues/1738#issuecomment -258353755

بالحديث كمبتدئ ، أجد بناء الجملة type Foo: Bar محيرًا. إنه بناء جملة النوع المرتبط ، لكن من المفترض أن تكون هذه في السمات ، وليس البنيات. إذا رأيت impl Trait مرة واحدة ، يمكنك معرفة ما هو ، أو يمكنك البحث عنه. من الصعب القيام بذلك باستخدام البنية الأخرى ، ولست متأكدًا من الفائدة.

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

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

واو ، هذا ليس ما فهمته على الإطلاق. شكرا @ Nemo157 على

في هذه الحالة ، أفضل صياغة الجملة.

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

ثم أعتقد أن هذه مشكلة في بناء الجملة type Type: Trait .

يمكن السماح به وسيتم تعريفه جيدًا تمامًا ، لكنك عادةً تكتب where Type: Foo + Bar بدلاً من where Type: Foo, Type: Bar ، لذلك لا يبدو ذلك فكرة جيدة جدًا. يمكنك أيضًا بسهولة إطلاق رسالة خطأ جيدة لهذه الحالة تقترح عليك كتابة Foo + Bar بدلاً من ذلك في حالة النوع المرتبط.

type Foo = impl Bar; لديه أيضًا مشكلات في الفهم حيث ترى = impl Bar وتخلص إلى أنه يمكنك استبداله في كل مرة حيث يتم استخدامه كـ -> impl Bar ؛ لكن هذا لن ينجح. قام @ mark-im بعمل هذا التفسير ، والذي يبدو أنه خطأ أكثر احتمالاً. لذلك ، أستنتج أن type Foo: Bar; هو الخيار الأفضل لقابلية التعلم.

ومع ذلك ، كما هو موضح أعلاه بواسطة stjepang و @ joshtriplett ، اكتب النوع: يبدو أن السمة غير مكتملة.

إنه ليس غير مكتمل من POV الممتد. تحصل على قدر من المعلومات بالضبط من type Foo: Bar; كما تحصل عليه من type Foo = impl Bar; . لذلك من منظور ما يمكنك القيام به بـ type Foo: Bar; ، فقد اكتمل. في الواقع ، هذا الأخير مفصول على أنه type _0: Bar; type Foo = _0; .

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

بعد قولي هذا ، أعتقد أنه سيكون من الأفضل عدم إعادة إشعال هذا النقاش :)

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

حسنًا ، أعتقد أحيانًا أن فقدان الأفضل لصالح البساطة أمر جيد.

إذا كان يجب أن نذهب إلى البساطة ، فسأضع بدلاً من ذلك type Foo = impl Bar; بدلاً من ذلك.
وتجدر الإشارة إلى أن البساطة المفترضة لـ C (المفترض ، لأن Haskell Core وأشياء مماثلة ربما تكون أبسط بينما لا تزال سليمة ..) تأتي بسعر باهظ عندما يتعلق الأمر بالتعبير والصحة. C ليست نجمتي الشمالية في تصميم اللغة ؛ بعيد عنه.

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

لكنها لن تختلف على الإطلاق في معاني الكلمات. واحد desugars إلى الآخر.
أعتقد أن الخلط في محاولة كتابة type Foo: Bar; أو type Foo = impl Bar فقط لأن أحدهما لا يعمل حتى مع وجود دلالات محددة جيدًا هو فقط في طريق المستخدم. إذا حاول المستخدم كتابة type Foo = impl Bar; ، فحينئذٍ يُطلق الوبر ويقترح type Foo: Bar; . يقوم الوبر بتعليم المستخدم بناء الجملة الآخر.
بالنسبة لي ، من المهم أن تكون اللغة موحدة ومتسقة ؛ إذا قررنا استخدام كلا الصيغتين في مكان ما ، فيجب أن نطبق هذا القرار باستمرار.

تخيل مبتدئًا رأى دائمًا type Type: Trait يأتي على أول type Type = impl Trait .

في هذه الحالة بالذات ، سوف ينطلق الوبر ويوصي بالصيغة السابقة. عندما يتعلق الأمر بـ type Foo = (impl Bar, impl Baz); ، سيتعين على المبتدئين تعلم -> impl Trait أي حال ، لذلك يجب أن يكونوا قادرين على استنتاج المعنى من ذلك.

وهو أكثر أو أقل الفخ الذي سقطت فيه ++ C.

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

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

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

ولا أرى ميزة كبيرة لاستخدام type Type: Trait أكثر من type Type = impl Trait: إنه ، مثل ، حفظ 6 أحرف؟

نعم ، تم حفظ 6 أحرف فقط. ولكن إذا أخذنا في الاعتبار type Foo: Iterator<Item: Iterator<Item: Display>>; ، فسنحصل بدلاً من ذلك على: type Foo = impl Iterator<Item = impl Iterator<Item = impl Display>>; الذي يحتوي على ضوضاء أكثر بكثير. type Foo: Bar; هو أيضًا أكثر مباشرة مقارنة بالأخير ، وأقل عرضة للتفسير الخاطئ (إعادة الاستبدال ..) ، ويعمل بشكل أفضل مع الأنواع المرتبطة (انسخ النوع من السمة ..).
علاوة على ذلك ، يمكن تمديد type Foo: Bar بشكل طبيعي إلى type Foo: Bar = ConcreteType; مما سيكشف عن النوع الملموس ولكن أيضًا يضمن أنه يرضي Bar . لا يمكن فعل شيء من هذا القبيل مقابل type Foo = impl Trait; .

وجود خطأ في إخراج rustc عند رؤية type Type: Trait الذي يقول أن الشخص الذي يكتبه ليستخدم type Type = impl Trait سيكون أكثر منطقية بالنسبة لي: على الأقل هناك طريقة واحدة لكتابة الأشياء ،

تتم إعادة توجيههم بشكل صحيح إلى الطريقة التقليدية الصحيحة لكتابتها.

أقترح أن تكون هناك طريقة تقليدية واحدة لكتابة الأشياء ؛ type Foo: Bar; .

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

بالحديث كمبتدئ ، أجد بناء الجملة type Foo: Bar محيرًا. إنه بناء جملة النوع المرتبط ، لكن من المفترض أن تكون هذه في السمات ، وليس البنيات.

سأكرر أن الأسماء المستعارة للنوع يمكن حقًا اعتبارها أنواعًا مرتبطة. ستكون قادرًا على قول:

trait Foo        { type Baz: Quux; }
// User of `Bar::Baz` can conclude `Quux` but nothing more!
impl Foo for Bar { type Baz: Quux; }

// User of `Wibble` can conclude `Quux` but nothing more!
type Wibble: Quux;

نرى أنه يعمل بالضبط بنفس الطريقة في الأنواع المرتبطة والأسماء المستعارة.

نعم ، تم حفظ 6 أحرف فقط. ولكن إذا أخذنا في الاعتبار type Foo: Iterator<Item: Iterator<Item: Display>>; ، فسنحصل بدلاً من ذلك على: type Foo = impl Iterator<Item = impl Iterator<Item = impl Display>> ؛ التي لديها الكثير من الضوضاء.

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

type Foo: Iterator<Item: Iterator<Item: Display>>;
type Foo = impl Iterator<Item: Iterator<Item: Display>>;
existential type Foo: Iterator<Item: Iterator<Item: Display>>;
existential type Foo = impl Iterator<Item: Iterator<Item: Display>>;

القدرة على استخدام الاختصار المقترح الخاص بك Trait<AssociatedType: Bound> بدلاً من بناء جملة Trait<AssociatedType = impl Bound> للإعلان عن أنواع وجودية مجهولة للأنواع المرتبطة من النوع الوجودي (إما مسمى أو مجهول) هي ميزة مستقلة (ولكن من المحتمل أن تكون ذات صلة بـ شروط الحفاظ على مجموعة كاملة من سمات النوع الوجودي متسقة).

@ Nemo157 إنها ميزات مختلفة ، نعم ؛ لكني أعتقد أنه من الطبيعي النظر إليهم معًا من أجل الاتساق.

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

أنا آسف ، لكنهم مخطئون. إنه ليس غير مكتمل من POV الممتد.

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

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

أعتقد أنه من المفيد إخبار الناس أنه يمكنهم أخذ كل الأشياء التي يعرفونها عن -> impl Trait وتطبيقها على type Foo = impl Trait ؛ هناك مفهوم عام impl Trait يمكنهم رؤيته يستخدم كحجر بناء في كلا المكانين. تخفي البنية مثل type Foo: Trait تلك الكتلة الإنشائية المعممة.

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

كنت أقترح أنه يبدو غير مكتمل ؛ تبدو خاطئة بالنسبة لي وللآخرين.

على ما يرام؛ أقترح استخدام مصطلح مختلف عن incomplete هنا لأنه بالنسبة لي يشير إلى نقص في المعلومات.

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

ما لاحظته كان خطأ في التفسير ، تم ارتكابه في الموضوع ، حول ما يعنيه type Foo = impl Bar; . فسر شخص واحد استخدامات مختلفة لـ Foo أنها ليست من النوع نفسه اسمياً ، بل أنواع مختلفة. أي بالضبط: "اسم مستعار لسمة يمكن أن تأخذ نوعًا ملموسًا مختلفًا في كل مرة تستخدمها" .

ذكر البعض أن type Foo: Bar; أمر محير ، لكنني لست متأكدًا من التفسير البديل لـ type Foo: Bar; والذي يختلف عن المعنى المقصود. سأكون مهتمًا بسماع تفسيرات بديلة.

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

سأكرر أن الأسماء المستعارة للنوع يمكن حقًا اعتبارها أنواعًا مرتبطة.

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

قارن ذلك برؤية الأنواع المرتبطة في تعريف السمات. في هذه الحالة ، ترى أنه شيء يجب على البنى الأخرى تحديده أو تنفيذه. ولكن إذا عثرت على type Foo: Debug خارج إحدى السمات ، فلن تعرف ما هو هذا. لا يوجد أحد لتطبيقه ، فهل هو نوع من التصريح المسبق؟ هل لها علاقة بالميراث ، كما هو الحال في C ++؟ هل هي مثل وحدة ML حيث يختار شخص آخر النوع؟ وإذا كنت قد رأيت impl Trait قبل ، فلا يوجد ما يربط بينهما. نكتب fn foo() -> impl ToString ، وليس fn foo(): ToString .

اكتب Foo = impl Bar ؛ يحتوي أيضًا على مشكلات في الفهم من حيث أنك ترى = impl Bar واستنتج أنه يمكنك فقط استبداله في كل مرة حيث يتم استخدامه كـ -> شريط ضمني

لقد قلتها هنا من قبل ، ولكن هذا مثل التفكير في أن let x = foo(); يعني أنه يمكنك استخدام x بدلاً من foo() . على أي حال ، إنها تفاصيل يمكن لأي شخص البحث عنها بسرعة عند الحاجة ، ولكنها لا تغير المفهوم بشكل جذري.

وهذا يعني أنه من السهل معرفة ما يدور حوله (نوع مستخلص مثل -> impl Trait ) ، حتى إذا كنت لا تعرف بالضبط كيف يعمل (ماذا يحدث عندما يكون لديك تعريفات متضاربة له). مع الصيغة الأخرى ، من الصعب أن ندرك ما هو عليه.

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

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

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

type Foo: Trait; إقرارًا كاملاً. يبدو أنه ينقصه شيء ما. ويبدو مختلفًا تمامًا عن type Foo = SomeType<X, Y, Z>; .

ربما نكون قد وصلنا إلى النقطة التي لا يمكن لخطوطنا الفردية بمفردها سد فجوة الإجماع هذه بين type Inferred: Trait و type Inferred = impl Trait .

هل تعتقد أنه سيكون من المفيد تجميع تنفيذ تجريبي لهذه الميزة مع أي بناء جملة (حتى تلك المحددة في RFC) حتى نتمكن من البدء في اللعب بها في برامج أكبر لنرى كيف تتناسب مع السياق؟

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

[..] impl Trait يعمل في كل مكان ، أو تقريبًا

حسنًا ، Foo: Bound يعمل أيضًا في كل مكان تقريبًا ؛)

ولكن إذا عثرت على type Foo: Debug خارج إحدى السمات ، فلن تعرف ما هو هذا.

أعتقد أن تطور استخدامه في: سمة -> ضمني -> اسم مستعار يساعد في التعلم.
علاوة على ذلك ، أعتقد أن الاستنتاج بأن "النوع Foo ينفذ التصحيح" من المحتمل أن يكون من
رؤية type Foo: Debug في السمات ومن الحدود العامة وهذا صحيح أيضًا.

هل لها علاقة بالميراث ، كما هو الحال في C ++؟

أعتقد أن نقص الميراث في Rust يحتاج إلى التعلم في مرحلة مبكرة جدًا مقارنة بتعلم الميزة التي نناقشها لأن هذا أمر أساسي للغاية بالنسبة لـ Rust.

هل هي مثل وحدة ML حيث يختار شخص آخر النوع؟

يمكن أيضًا إجراء هذا الاستنتاج مقابل type Foo = impl Bar; بسبب arg: impl Bar حيث يختار المتصل (المستخدم) النوع. بالنسبة لي ، الاستنتاج الذي يختاره المستخدم للنوع يبدو أقل احتمالًا بالنسبة لـ type Foo: Bar; .

لقد قلتها هنا من قبل ، ولكن هذا يشبه التفكير في أن let x = foo(); يعني أنه يمكنك استخدام x بدلاً من foo() .

إذا كانت اللغة هي شفافة مرجعيا، يمكنك استبدال x لل foo() . حتى نضيف type Foo = impl Foo; إلى النظام ، تكون الأسماء المستعارة afaik شفافة مرجعية. على العكس من ذلك ، إذا كان هناك ارتباط متاح بالفعل x = foo() ، فيمكن استبدال foo() بـ x .

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

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

عادل بما يكفي؛ ولكن ما الذي من المفترض أن يكون عليه؟

type Foo: Trait; إقرارًا كاملاً.

تبدو كاملة بالنسبة لي. يبدو وكأنه حكم بأن Foo يرضي Trait وهو بالضبط المعنى المقصود.

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

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

@ مارك ايم

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

هذا هو بالضبط ما أشعر به بالنسبة لي أيضًا.

هل هناك أي فرصة لمعالجة البندين المؤجلين قريبًا ، بالإضافة إلى مسألة استبعاد مدى الحياة؟ كنت سأفعل ذلك بنفسي ، لكن ليس لدي فكرة كيف!

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

varkor ما الدلالات غير واضحة؟ لم يتغير شيء في AFAIK عن دلالات impl Trait منذ RFC 1951 ، وتوسعت في عام 2071.

alexreg لم يكن لدي أي خطط لذلك ، ولكن إليك مخطط تقريبي: بعد إضافة التحليل ، تحتاج إلى خفض أنواع static s و const s داخل أداة وجودية سياق السمات ، كما يحدث هنا لأنواع إرجاع الوظائف. . ومع ذلك ، سترغب في جعل DefId في ImplTraitContext::Existential اختياريًا ، نظرًا لأنك لا تريد أن تلتقط impl Trait الأدوية العامة من تعريف الوظيفة الأم. يجب أن يمنحك ذلك القليل من الطريق. قد يكون لديك وقت أسهل إذا اعتمدت على النوع الوجودي للعلاقات العامة @ oli-obk.

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

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

تقتصر دلالات السمة الضمنية في اللغة تمامًا على استخدامها في توقيعات الوظائف وليس صحيحًا أن توسيعها إلى مواضع أخرى له معنى واضح.

تم تحديد المعنى في RFC 2071 .

cramertj : المعنى في RFC 2071 غامض ويسمح بتفسيرات متعددة لما تعنيه عبارة "النوع الوجودي" هناك.

TL ؛ DR - لقد حاولت تحديد معنى دقيق لـ impl Trait ، والذي أعتقد أنه يوضح التفاصيل التي كانت ، على الأقل حدسيًا ، غير واضحة ؛ مع اقتراح بناء جملة اسم مستعار جديد.

الأنواع الوجودية في الصدأ (بوست)


كان هناك الكثير من النقاش الدائر في دردشة Discord rust-lang حول الدلالات الدقيقة (أي الرسمية والنظرية) لـ impl Trait في اليومين الماضيين. أعتقد أنه كان من المفيد توضيح الكثير من التفاصيل حول الميزة وما هي بالضبط وما هي ليست كذلك. كما يلقي بعض الضوء على ما هي الصيغ المعقولة للأسماء المستعارة من النوع.

لقد كتبت ملخصًا صغيرًا لبعض استنتاجاتنا. يقدم هذا تفسيرًا لـ impl Trait والذي أعتقد أنه نظيف إلى حد ما ، ويصف بدقة الاختلافات بين موضع الوسيطة impl Trait وموضع الإرجاع impl Trait (وهو ليس "عالميًا- "مقابل" الكمي الوجودي "). هناك أيضا بعض الاستنتاجات العملية.

في ذلك ، أقترح صيغة جديدة تفي بالمتطلبات الشائعة "للاسم المستعار من النوع الوجودي":
type Foo: Bar = _;

نظرًا لأنه موضوع معقد ، فهناك الكثير من الأشياء التي يجب توضيحها أولاً ، لذلك قمت بكتابتها كمنشور منفصل. هو محل تقدير كبير ردود الفعل!

الأنواع الوجودية في الصدأ (بوست)

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

RFC 2071 غامض ويسمح بتفسيرات متعددة لما تعنيه عبارة "النوع الوجودي" هناك.

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

type Foo: Bar = _;

لقد ناقشنا بناء الجملة هذا خلال RFC 2071. كما قلت هناك ، يعجبني أنه يوضح بوضوح أن Foo هو نوع واحد مستنتج وأنه يترك مجالًا للأنواع غير المستنبطة التي تُركت وجودية خارج الوحدة النمطية الحالية ( على سبيل المثال ، type Foo: Bar = u32; ). لم يعجبني جانبين منها: (1) لا تحتوي على كلمة رئيسية ، وبالتالي يصعب البحث عنها و (ب) لديها نفس مشكلة الإسهاب مقارنةً بـ type Foo = impl Trait abstract type Foo: Bar; بناء الجملة type Foo = impl Iterator<Item = impl Display>; يصبح type Foo: Iterator<Item = MyDisplay> = _; type MyDisplay: Display = _; . لا أعتقد أن أيًا من هؤلاء يفسد الصفقات ، لكنه ليس فوزًا واضحًا بطريقة أو بأخرى IMO.

cramertj يأتي الغموض هنا:

type Foo = impl Bar;
fn f() -> Foo { .. }
fn g() -> Foo { .. }

إذا كان Foo حقًا اسمًا مستعارًا لنوع وجودي ، فإن f و g سيدعمان أنواعًا مختلفة من الإرجاع. لقد قرأ العديد من الأشخاص هذا النحو بشكل غريزي بهذه الطريقة ، وفي الواقع ، أدرك بعض المشاركين في مناقشة بناء الجملة RFC 2071 أن هذه ليست الطريقة التي يعمل بها الاقتراح كجزء من مناقشة Discord الأخيرة.

تكمن المشكلة في أنه ، لا سيما في مواجهة موقف الحجة impl Trait ، ليس من الواضح على الإطلاق إلى أين من المفترض أن يذهب المحدد الكمي الوجودي. للحجج هو ضيق النطاق. بالنسبة لموقف العودة ، يبدو أنه ضيق النطاق ولكن اتضح أنه أوسع من ذلك ؛ مقابل type Foo = impl Bar كلا المنصبين مقبولان. تحفز البنية المستندة إلى _ تفسيرًا لا يتضمن حتى "وجوديًا" ، متجنباً هذه المشكلة بدقة.

إذا Foo كان حقا نوع الاسم المستعار لنوع وجودي

(التركيز لي). قرأت أن "an" كـ "محدد" مما يعني أن f و g لن يدعمان أنواع الإرجاع الملموسة المختلفة ، نظرًا لأنها تشير إلى نفس النوع الوجودي. لقد رأيت دائمًا type Foo = impl Bar; أنه يستخدم نفس المعنى مثل let foo: impl Bar; ، أي إدخال نوع وجودي مجهول جديد ؛ جعل مثالك معادلاً لـ

existential type _0: Bar;
type Foo = _0;
fn f() -> Foo { .. }
fn g() -> Foo { .. }

الذي آمل أن يكون واضحًا نسبيًا.


إحدى المشكلات هي أن معنى " impl Trait في الأسماء المستعارة من النوع" لم يتم تحديده مطلقًا في RFC. تم ذكره بإيجاز في قسم "البدائل" في

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

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

فيما يتعلق بـ type Foo: Bar = _; ، أعتقد أنه ربما يجب مناقشته مرة أخرى - لا ضرر من إعادة النظر في الأفكار القديمة بعيون جديدة. فيما يتعلق بمشاكلك معها:
(1) لا تحتوي على كلمة أساسية ، لكنها نفس بناء الجملة مثل استدلال الكتابة في أي مكان. البحث في الوثائق عن "شرطة سفلية" / "نوع الشرطة السفلية" / وما إلى ذلك يمكن أن يوفر بسهولة صفحة على نوع الاستدلال.
(2) نعم هذا صحيح. كنا نفكر في حل لهذا الأمر ، والذي أعتقد أنه يتناسب بشكل جيد مع تدوين الشرطة السفلية ، والتي نأمل أن تكون جاهزة لاقتراحها قريبًا.

مثل cramertj أنا لا أرى الحجة هنا حقًا.

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

يعد غموض النطاق الذي يصفه rpjohnst مشكلة خطيرة ، ولكن من المحتمل أن يكون كل بناء جملة مقترح مشوشًا مع أي من النوع alises أو الأنواع المرتبطة به. أي من هذه الارتباكات "أسوأ" أو "أكثر احتمالًا" هو بالتحديد ركوب الدراجة الذي لا ينتهي والذي فشلنا بالفعل في حله بعد عدة مئات من التعليقات. يعجبني أن type Foo: Bar = _; يبدو أنه يصلح مشكلة type Foo: Bar; المتمثلة في الحاجة إلى انفجار العديد من العبارات للإعلان عن أي وجودي غير تافه إلى حد ما ، لكنني لا أعتقد أن هذا كافٍ لتغيير حالة "ركوب الدراجات التي لا تنتهي أبدًا".

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

existential Foo = impl Trait;
fn f() -> Foo { .. }
fn g() -> Foo { .. }
existential Foo: Trait;
fn f() -> Foo { .. }
fn g() -> Foo { .. }



md5-b59626c5715ed89e0a93d9158c9c2535



existential Foo: Trait = _;
fn f() -> Foo { .. }
fn g() -> Foo { .. }

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

تضمين التغريدة
تمثل عبارة "النوع الوجودي" إشكالية على وجه التحديد _ بسبب_ غموض النطاق. لم أر أي شخص آخر يشير إلى أن النطاق مختلف تمامًا بالنسبة لـ APIT و RPIT. هذا يعني أن بناء جملة مثل type Foo = impl Bar ، حيث impl Bar هو "نوع وجودي" غامض بطبيعته.

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

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

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

ما أنا مقتنع به هو أنه أيا كان بناء الجملة الذي ننتهي إليه يحتاج إلى كلمة رئيسية أخرى غير type

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

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

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

لم أر أي شخص آخر يشير إلى أن النطاق مختلف تمامًا بالنسبة لـ APIT و RPIT.

اعتقدت أن تحديد النطاق كان دائمًا جزءًا واضحًا من مقترحات السمات الضمنية ، لذلك لم يكن بحاجة إلى "الإشارة". كل ما قلته حول تحديد النطاق يبدو أنه مجرد تكرار لما قبلناه بالفعل في طلبات التعليقات السابقة. لقد فهمت أنه ليس واضحًا للجميع من بناء الجملة وهذه مشكلة ، لكن الأمر ليس كما لو لم يفهم أحد هذا من قبل. في الواقع ، اعتقدت أن جزءًا كبيرًا من المناقشة حول RFC 2701 كان يدور حول ما يجب أن يكون عليه نطاق type Foo = impl Trait; ، بمعنى ما هو نوع الاستدلال الذي لا يُسمح له بالاطلاع عليه.

من الممكن التوصل إلى بناء جملة متسق لا يحتوي على هذا الالتباس.

هل تحاول أن تقول أن type Foo: Bar = _; هي تلك الصيغة ، أم أنك تعتقد أننا لم نعثر عليها بعد؟

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

لقد اخترعت تدوينًا جديدًا تمامًا

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

تعال إلى التفكير في الأمر ، نظرًا لأننا نسيء استخدام "الوجودية" طوال هذا الوقت ، فهذا يعني أن existential Foo: Trait / = impl Trait ربما لم يعد بناء جملة شرعيًا.

لذلك نحن بحاجة إلى كلمة رئيسية جديدة نضعها أمام الأسماء التي تشير إلى نوع كود غير معروف إلى خارجي ... وأنا أرسم فراغًا على هذا. alias ، secret ، internal ، إلخ ، فظيعًا جدًا ، ومن غير المحتمل أن يكون "ارتباكًا فريدًا" أقل من type .

تعال إلى التفكير في الأمر ، نظرًا لأننا نسيء استخدام "الوجودية" طوال هذا الوقت ، فهذا يعني أن existential Foo: Trait / = impl Trait ربما لم يعد بناء جملة شرعيًا.

نعم ، أوافق تمامًا - أعتقد أننا بحاجة إلى الابتعاد تمامًا عن مصطلح "وجودي" تمامًا * (كانت هناك بعض الأفكار المؤقتة حول كيفية القيام بذلك مع الاستمرار في شرح impl Trait جيدًا).

* (ربما حجز المصطلح لـ dyn Trait فقط)

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

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

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

هل يمكنك أن تطلعنا على ما يعنيه هذا بالضبط؟ لم أكن على علم بفكرة "الشفافية المرجعية" التي تأثرت بـ _ .

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

لست متأكدًا من أنها _أولوية_. بالنسبة لي كانت مجرد الحجة الموضوعية الوحيدة التي وجدناها والتي بدت وكأنها تفضل بناء جملة على الأخرى. ولكن من المحتمل أن يتغير هذا استنادًا إلى الكلمات الرئيسية التي يمكننا التوصل إليها لاستبدال type .

هل يمكنك أن تطلعنا على ما يعنيه هذا بالضبط؟ لم أكن على علم بفكرة "الشفافية المرجعية" التي تأثرت بـ _ .

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

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

fn foo() -> usize {
    println!("ey!");
    42
}

fn main() {
    let bar = foo();
    let baz = bar + bar;
}

إذا استبدلنا كل مرة بـ bar بـ foo() (تعريف bar ) ، فمن الواضح أننا نحصل على ناتج مختلف.

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

type Foo = Definition;

ثم يمكنك القيام (تجنب الالتقاط) باستبدال تكرارات Foo مقابل Definition واستبدال التكرارات Definition مقابل Foo دون تغيير دلالات برنامجك ، أو نوع صحتها.

مقدمة:

type Foo = impl Bar;

يعني أن كل تكرار لـ Foo هو نفس النوع يعني أنك إذا كتبت:

fn stuff() -> Foo { .. }
fn other_stuff() -> Foo { .. }

لا يمكنك استبدال تكرارات Foo بـ impl Bar والعكس صحيح. هذا إذا كتبت:

fn stuff() -> impl Bar { .. }
fn other_stuff() -> impl Bar { .. }

أنواع الإرجاع لن تتحد مع Foo . وبالتالي يتم كسر الشفافية المرجعية للأسماء المستعارة من خلال تقديم impl Trait مع دلالات RFC 2071 بداخلها.

حول الشفافية المرجعية و type Foo = _; ، يجب أن تستمر ... (بواسطةvarkor)

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

نقطة جيدة. لكن ألا يشير جزء التعيين = _ أنه نوع واحد فقط؟

لقد كتبت هذا من قبل ، لكن ...

إعادة الشفافية المرجعية: أعتقد أنه من المفيد أكثر أن ننظر إلى type باعتباره ملزمًا (مثل let ) بدلاً من الاستبدال الذي يشبه المعالج السابق. بمجرد النظر إلى الأمر بهذه الطريقة ، فإن type Foo = impl Trait يعني بالضبط ما يبدو عليه.

أتخيل أن المبتدئين سيكونون أقل احتمالاً أن يفكروا في impl Trait أنه أنواع وجودية مقابل أنواع جامعية ، ولكن "كشيء impl sa سمة . If they want to know more, they can read the impl Trait`. بمجرد أن تقوم بذلك إذا قمت بتغيير بناء الجملة ، فستفقد الاتصال بينه وبين الميزة الحالية مع عدم وجود فائدة كبيرة. _ أنت فقط تستبدل بناء جملة من المحتمل أن يكون مضللاً بآخر.

Re type Foo = _ ، فإنه يثقل كاهل _ بمعنى غير مرتبط تمامًا. قد يبدو من الصعب أيضًا العثور عليه في الوثائق و / أو Google.

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

أنا أيضًا لا أنظر إلى type كبديل للمعالج C لأنه يجب أن يتم التقاطه لتجنب واحترام الأدوية الجنيسة (بدون SFINAE). بدلاً من ذلك ، أفكر في type تمامًا كما أفكر في ربط بلغة مثل Idris أو Agda حيث تكون جميع الارتباطات نقية.

أتخيل أن المبتدئين سيكونون أقل احتمالية للتفكير في impl Trait أنه أنواع وجودية مقابل أنواع عالمية ، ولكن "كشيء يشير إلى سمة

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

Re type Foo = _ ، فإنه يثقل كاهل _ بمعنى غير مرتبط تمامًا.

كيف ذلك؟ يتوافق type Foo = _; هنا مع استخدام _ في سياقات أخرى حيث يُتوقع النوع.
وتعني "استنتج النوع الحقيقي" ، تمامًا كما تكتب .collect::<Vec<_>>() .

قد يبدو من الصعب أيضًا العثور عليه في الوثائق و / أو Google.

ألا يجب أن يكون الأمر بهذه الصعوبة؟ من المأمول أن تظهر "تسطير من نوع الاسم المستعار" النتيجة المطلوبة ...؟
لا يبدو أي شيء مختلفًا عن البحث عن "نوع الاسم المستعار ضمّن سمة".

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

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

كيف ذلك؟ يتوافق type Foo = _; هنا مع استخدام _ في سياقات أخرى حيث يُتوقع النوع.
تعني "استنتج النوع الحقيقي" ، تمامًا كما تكتب .collect ::> ().

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

لا يقوم محرك بحث Google بفهرسة الأحرف الخاصة.

لم يعد هذا صحيحًا (على الرغم من أنه ربما يعتمد على المسافة البيضاء ..؟).

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

الدلالات المقترحة لـ type Foo = _; هي بديل عن وجود اسم مستعار من النوع الوجودي ، يعتمد كليًا على الاستدلال. إذا لم يكن ذلك واضحًا تمامًا ، فسأتابع قريبًا بشيء يجب أن يشرح النوايا بشكل أفضل قليلاً.

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

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

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

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

آسف ، ما زلت لا أستطيع الالتفاف حول هذا الأمر لأن حدسي متخلف تمامًا.

على سبيل المثال ، إذا كان لدينا type Foo = Bar ، فإن حدسي يقول:
"نعلن عن Foo ، والذي سيصبح من نفس النوع مثل Bar ."

ثم ، إذا كتبنا type Foo = impl Bar ، فإن حدسي يقول:
"نعلن عن Foo ، والذي يصبح نوعًا يستخدم Bar ."

إذا كان Foo مجرد اسم مستعار نصي لـ impl Bar ، فسيكون ذلك غير بديهي للغاية بالنسبة لي. أحب التفكير في هذا على أنه أسماء مستعارة نصية مقابل دلالية .

لذلك إذا كان من الممكن استبدال Foo بـ impl Bar أي مكان يظهر ، فهذا اسم مستعار نصي ، بالنسبة لي أكثر ما يذكرنا بوحدات الماكرو و metaprogramming. ولكن إذا تم تعيين معنى Foo عند نقطة الإعلان ويمكن استخدامه في أماكن متعددة بهذا المعنى الأصلي (وليس المعنى السياقي!) ، فهذا اسم مستعار دلالي .

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

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

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

إذا Foo كان حقا نوع الاسم المستعار لنوع وجودي

(التركيز لي). قرأت أن "an" كـ "محدد" مما يعني أن f و g لا يدعمان أنواع مختلفة من الإرجاع الملموس ، نظرًا لأنهما يشيران إلى نفس النوع الوجودي.

هذه إساءة استخدام لمصطلح "النوع الوجودي" ، أو على الأقل طريقة تتعارض مع مشاركة varkor . يمكن أن يظهر type Foo = impl Bar لجعل Foo اسمًا مستعارًا للنوع ∃ T. T: Trait - وإذا استبدلت ∃ T. T: Trait كل مكان تستخدم فيه Foo ، حتى لو لم تقم بذلك - يمكنك الحصول على نوع خرساني مختلف في كل موضع.

تحديد نطاق هذا المحدد الكمي ∃ T (المعبر عنه في المثال الخاص بك كـ existential type _0 ) هو الشيء المعني. إنه ضيق مثل هذا في APIT - يمكن للمتصل تمرير أي قيمة ترضي ∃ T. T: Trait . ولكنها ليست في RPIT، وليس في RFC 2071 في existential type الإعلانات، وليس في desugaring بك example- هناك، ومحدد الكمية هي أبعد، في وظيفة كاملة أو مستوى كلها وحدة، والتي تتعامل مع نفس T كل مكان.

وبالتالي الغموض - لدينا بالفعل impl Trait وضع مُحدِّده الكمي في أماكن مختلفة اعتمادًا على موضعه ، إذن أي واحد يجب أن نتوقعه مقابل type T = impl Trait ؟ تثبت بعض استطلاعات الرأي غير الرسمية ، بالإضافة إلى بعض عمليات الإدراك ما بعد الحقيقة من قبل المشاركين في سلسلة RFC 2071 ، أنه ليس واضحًا بطريقة أو بأخرى.

هذا هو السبب في أننا نريد الابتعاد عن تفسير impl Trait كأي شيء يتعلق على الإطلاق بالوجودية ، وبدلاً من ذلك نصف دلالاتها من حيث نوع الاستدلال. type T = _ على نفس النوع من الغموض - فلا يزال هناك مستوى السطح "لا يمكن نسخ ولصق _ بدلاً من T ،" ولكن لم يعد هناك " النوع الفردي الذي يكون T اسمًا مستعارًا له يمكن أن يعني أنواعًا محددة متعددة." (السلوك الغامض / الذي لا يوحد هو الشيء الذي يتحدث

الشفافية المرجعية

فقط لأن الاسم المستعار من النوع متوافق حاليًا مع الشفافية المرجعية ، لا يعني أن الناس

وكمثال على ذلك، و const البند هو مرجعية شفافة (المذكورة في https://github.com/rust-lang/rust/issues/34511#issuecomment-402520768)، والتي تسبب الارتباك في الواقع إلى الجديد والقديم المستخدمون (rust-lang-Nursery / rust-clippy # 1560).

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

stjepangkennytm أنا لا أقول أن الجميع سوف نتوقع هذا النوع الأسماء المستعارة مع type Foo = impl Trait; سوف تعمل بطريقة شفافة مرجعيا. لكنني أعتقد أن عددًا غير ضئيل من المستخدمين سيفعلون ، كما يتضح من الارتباك في هذا الموضوع وفي أي مكان آخر (ما يشير إليه

لقد تحرك تفكيري الحالي حول ما يجب فعله في هذا الشأن بما يتماشى مع varkor و @ rpjohnst.

إعادة: الشفافية المرجعية

type Foo<T> = (T, T);

type Bar = Foo<impl Copy>;   // not equivalent to (impl Copy, impl Copy)

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

centril أرفع يدي عندما يتعلق الأمر بتوقع شفافية مرجعية لـ Foo في type Foo = impl Bar; . مع type Foo: Bar = _; مع ذلك ، لا أتوقع شفافية مرجعية.

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

لقد كتبت هذا على العناصر الداخلية هنا: https://internals.rust-lang.org/t/extending-impl-trait-to-allow-multiple-return-types/7921

مجرد ملاحظة عندما نحقق الاستقرار في الأنواع الوجودية الجديدة - كان المقصود دائمًا أن تكون كلمة "وجودي" كلمة رئيسية مؤقتة (وفقًا لـ RFC) و (IMO) فظيعة. يجب أن نتوصل إلى شيء أفضل قبل أن نستقر.

الحديث عن الأنواع "الوجودية" لا يبدو أنه يزيل الأمور. أود أن أقول إن impl Trait يشير إلى نوع معين ، مستدل عليه تنفيذ سمة. وصفًا بهذه الطريقة ، من الواضح أن type Foo = impl Bar نوع محدد ، ودائمًا ما يكون هو نفسه - وهذا أيضًا هو التفسير الوحيد المفيد بالفعل: لذلك يمكن استخدامه في سياقات أخرى إلى جانب السياق الذي تم استنتاجه منه ، كما في الهياكل.

بهذا المعنى ، سيكون من المنطقي كتابة impl Trait كـ _ : Trait .

rpjohnst ،

من الممكن أيضًا أن يمكننا تمديد وضع الإرجاع impl Trait لدعم أنواع متعددة

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

@ jan-hudec ظهرت هذه الأفكار في المناقشة على Discord ، وهناك بعض المشكلات ، تستند أساسًا إلى حقيقة أن التفسير الحالي لموضع الإرجاع وموقف الجدال impl Trait غير متسق.

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

على سبيل المثال ، بمجرد أن يعني impl Trait "استخدام هذا النوع الجديد من الاستدلال للعثور على نوع متعدد الأشكال قدر الإمكان يستخدم Trait ،" يبدأ type Foo = impl Bar في الإشارة إلى أشياء تتعلق بالوحدات النمطية. قواعد RFC 2071 حول كيفية الاستدلال على abstract type القول بأن جميع الاستخدامات يجب الاستدلال بشكل مستقل من نفس النوع، ولكن هذا الاستنتاج متعدد الأشكال على الأقل يعني أن أكثر من الممكن. وإذا كان لنا من أي وقت مضى حصلت وحدات parametrized (حتى ما يزيد

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

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

أخيرًا ، السبب في كتابتي أن اقتراح monomorphization كان من زاوية إيجاد طريقة أخرى لتوحيد معنى الوسيطة وموضع الإرجاع impl Trait . وبينما نعم ، فهذا يعني أن -> impl Trait لم يعد يضمن نوعًا واحدًا من الخرسانة ، ليس لدينا حاليًا طريقة للاستفادة من ذلك على أي حال. والحلول المقترحة كلها حلول مزعجة - حيل معيارية إضافية abstract type حيل ، typeof ، إلخ. إجبار كل من يريد الاعتماد على سلوك أحادي النوع على تسمية هذا النوع الفردي أيضًا عبر abstract type يمكن القول إن بناء الجملة

ظهرت هذه الأفكار في المناقشة على Discord ، وهناك بعض المشكلات ، تستند أساسًا إلى حقيقة أن التفسير الحالي لموضع الإرجاع وموقف الجدال impl Trait غير متسقين.

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

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

أنا شخصياً لا أجد أن هذا التناقض يمثل مشكلة في الممارسة.

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

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

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

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

mikeyhew أنت تسيء فهمي - إنه يعمل بشكل جيد للإغلاق أو الأنواع الأخرى غير اختراع اسم عبر بناء جملة RFC 2071 abstract type . لديك لاختراع اسم بغض النظر عما إذا كنت تريد الذهاب لاستخدام نوع واحد في أي مكان آخر.

rpjohnst أوه أرى ، شكرا

في انتظار let x: impl Trait بقلق.

كتصويت آخر لـ let x: impl Trait ، سوف يبسط بعض الأمثلة futures ، إليك مثال على ذلك ، حاليًا يستخدم دالة فقط للحصول على القدرة على استخدام impl Trait :

fn make_sink_async() -> impl Future<Output = Result<
    impl Sink<SinkItem = T, SinkError = E>,
    E,
>> { // ... }

بدلاً من ذلك يمكن كتابة هذا كالتزام عادي:

let future_sink: impl Future<Output = Result<
    impl Sink<SinkItem = T, SinkError = E>,
    E,
>> = // ...;

يمكنني إرشاد شخص ما من خلال تنفيذ let x: impl Trait إذا رغبت في ذلك. ليس من الصعب القيام بذلك بشكل مستحيل ، ولكن بالتأكيد ليس بالأمر السهل أيضًا. نقطة دخول:

على غرار الطريقة التي نزور بها نوع الإرجاع impl Trait في https://github.com/rust-lang/rust/blob/master/src/librustc/hir/lowering.rs#L3159 نحتاج إلى زيارة نوع السكان المحليين في https : //github.com/rust-lang/rust/blob/master/src/librustc/hir/lowering.rs#L3159 وتأكد من إرجاع العناصر الوجودية التي تم إنشاؤها حديثًا مع العناصر المحلية.

بعد ذلك ، عند زيارة نوع السكان المحليين ، تأكد من تعيين ExistentialContext إلى Return لتمكينه بالفعل.

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

rpjohnst ،

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

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

لقد فتحت RFC تقترح حلاً للصيغة الملموسة existential type ، بناءً على المناقشة في هذا الموضوع ، RFC الأصلي والمناقشات المتزامنة: https://github.com/rust-lang/rfcs/pull / 2515.

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

fn foo<T>(_: T) -> impl ::std::fmt::Display {
    5
}

existential type Bar<T>: ::std::fmt::Display;
fn bar<T>(_: T) -> Bar<T> {
    5
}

قد يكون هذا مهمًا لأن معلمات النوع يمكن أن يكون لها فترات حياة داخلية تقيد عمر impl Trait الذي تم إرجاعه على الرغم من أن القيمة نفسها غير مستخدمة ، قم بإزالة <T> من Bar في الملعب أعلاه لمعرفة أن استدعاء foo فشل ولكن bar يعمل.

لا يمكن استخدام تنفيذ النوع الوجودي الحالي لتمثيل كل موضع الإرجاع الحالي لتعريفات السمات

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

@ oli-obk شكرا على النصيحة الإضافية. مع نصيحتك السابقة وبعض النصائح من cramertj ، ربما يمكنني أن

fasihrana @ Nemo157 انظر أعلاه. ربما في غضون أسابيع قليلة! :-)

هل يمكن لأحد أن يوضح أن سلوك existential type لا يلتقط معلمات النوع ضمنيًا (التي ذكرها @ Nemo157 ) مقصود وسيظل كما هو؟ يعجبني لأنه يحل # 42940

لقد نفذته بهذه الطريقة عن قصد

Arnavion نعم ، هذا مقصود ، ويتطابق مع الطريقة التي تعمل بها تعريفات العناصر الأخرى (مثل الوظائف المتداخلة) في Rust.

هل تمت مناقشة التفاعل بين existential_type و never_type بالفعل؟

ربما يجب أن يكون ! قادرًا على ملء أي نوع وجودي بغض النظر عن السمات المعنية.

existential type Mystery : TraitThatIsHardToEvenStartImplementing;

fn hack_to_make_it_compile() -> Mystery { unimplemented!() }

أم يجب أن يكون هناك نوع خاص لا يمكن المساس به يعمل كمستوى نوع unimplemented!() قادر على تلبية أي نوع وجودي تلقائيًا؟

vi أعتقد أن هذا من شأنه أن يندرج تحت

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

existential type يعمل بالفعل مع طرق السمات. Wrt impl Trait ، هل يتم تغطية ذلك من خلال RFC؟

alexreg أعتقد أن ذلك يتطلب أن تكون GATs قادرة على إلغاء الصياغة إلى نوع مرتبط مجهول عندما يكون لديك شيء مثل fn foo<T>(..) -> impl Bar<T> (يصبح تقريبًا -> Self::AnonBar0<T> ).

Centril هل تقصد القيام بـ <T> على impl Bar هناك؟ يعني سلوك الالتقاط من النوع الضمني البالغ impl Trait أنك تحصل على نفس الحاجة إلى GATs حتى مع شيء مثل fn foo<T>(self, t: T) -> impl Bar; .

@ Nemo157 لا آسف لم أفعل. لكن مثالك يوضح المشكلة بشكل أفضل. شكرا لك :)

alexreg أعتقد أن هذا يتطلب أن تكون GATs قادرة على إلغاء الصياغة إلى نوع مرتبط مجهول عندما يكون لديك شيء مثل fn foo(..) -> شريط ضمني(يصبح تقريبًا -> ذاتي :: AnonBar0).

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

الفرز: https://github.com/rust-lang/rust/pull/53542 قد تم دمجها ، لذلك أعتقد أنه يمكن التحقق من مربعات التجزئة الخاصة بـ {let,const,static} foo: impl Trait .

هل سأتمكن من كتابة:

trait Foo {
    fn GetABar() -> impl Bar;
}

؟؟

على الاغلب لا. لكن هناك خطط جارية لإعداد كل شيء حتى نحصل عليه

trait Foo {
    type Assoc: Bar;
    fn get_a_bar() -> Assoc;
}

impl Foo for SomeType {
    fn get_a_bar() -> impl Bar {
        SomeThingImplingBar
    }
}

يمكنك تجربة هذه الميزة ليلا في شكل

impl Foo for SomeType {
    existential type Assoc;
    fn get_a_bar() -> Assoc {
        SomeThingImplingBar
    }
}

بداية جيدة للحصول على مزيد من المعلومات حول هذا هو https://github.com/rust-lang/rfcs/pull/2071 (وكل شيء مرتبط منه)

@ oli-obk على rustc 1.32.0-nightly (00e03ee57 2018-11-22) ، أحتاج أيضًا إلى إعطاء حدود السمات لـ existential type للعمل في كتلة impl هذا القبيل. هل هذا متوقع؟

تعد قدرة أمرًا مفيدًا لأنه يمكنك تقديم أكثر من مجرد السمات المطلوبة

impl Foo for SomeDebuggableType {
    existential type Assoc: Bar + Debug;
    fn get_a_bar() -> Assoc {
        SomeThingImplingBarAndDebug
    }
}

fn use_debuggable_foo<F>(f: F) where F: Foo, F::Assoc: Debug {
    println!("bar is: {:?}", f.get_a_bar())
}

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

@ Nemo157 آه ، آسف ، ما قصدته هو أنه يجب أن يكون لديك حدود في الوقت الحالي. وهذا يعني أن هذا لن يتم تجميعه:

impl A for B {
    existential type Assoc;
    // ...
}

في حين أن هذا سوف:

impl A for B {
    existential type Assoc: Debug;
    // ...
}

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

trait Foo {
    type Assoc;
    fn foo() -> Self::Assoc;
}

struct Bar;
impl Foo for Bar {
    existential type Assoc: ;
    fn foo() -> Self::Assoc { Bar }
}

يبدو هذا وكأنه حالة متطرفة بالنسبة لي ، فوجود نوع وجودي أقل تقييدًا يعني أنه يوفر _لا_ عمليات للمستخدمين (بخلاف السمات التلقائية) ، فما الذي يمكن استخدامه من أجله؟

وتجدر الإشارة أيضًا إلى أنه لا توجد طريقة لفعل الشيء نفسه مع -> impl Trait و -> impl () خطأ في بناء الجملة و -> impl بمفرده يعطي error: at least one trait must be specified ؛ إذا أصبح بناء جملة النوع الوجودي type Assoc = impl Debug; أو ما شابه ذلك ، فيبدو أنه لن تكون هناك طريقة لتحديد النوع المرتبط بدون ربط سمة واحدة على الأقل.

@ Nemo157 نعم ، لقد أدركت فقط لأنني جربت حرفياً الكود الذي اقترحته أعلاه ، ولم ينجح: p افترضت نوعًا ما أنه سيستنتج حدود السمة. فمثلا:

trait Foo {
    type Assoc: Future<Output = u32>;
}

struct Bar;
impl Foo for Bar {
    existential type Assoc;
}

بدا من المعقول ألا تضطر إلى تحديد Future<Output = u32> للمرة الثانية ، لكن هذا لا يعمل. أفترض أن existential type Assoc: ; (والذي يبدو أيضًا وكأنه بناء جملة غريب جدًا) لن يفعل هذا الاستنتاج أيضًا؟

trait Foo {
    type Assoc;
    fn foo() -> Self::Assoc;
}

struct Bar;
impl Foo for Bar {
    existential type Assoc: ;
    fn foo() -> Self::Assoc { Bar }
}

يبدو هذا وكأنه حالة متطرفة بالنسبة لي ، فوجود نوع وجودي أقل تقييدًا يعني أنه يوفر _لا_ عمليات للمستخدمين (بخلاف السمات التلقائية) ، فما الذي يمكن استخدامه من أجله؟

ألا يمكن استخدامها للاستهلاك في نفس تنفيذ السمات؟ شيء من هذا القبيل:

trait Foo {
    type Assoc;
    fn create_constructor() -> Self::Assoc;
    fn consume(marker: Self::Assoc) -> Self;
    fn consume_box(marker: Self::Assoc) -> Box<Foo>;
}

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

trait MarkupSystem {
    type Cache;
    fn create_cache() -> Cache;
    fn translate(cache: &mut Self::Cache, input: &str) -> String;
}

في كلتا الحالتين سيكون existential type Assoc; مفيدًا.

ما هي الطريقة الصحيحة لتحديد الأنواع المرتبطة بالسمات الضمنية؟

على سبيل المثال ، إذا كانت لدي سمة Action وأريد التأكد من أن تنفيذ النوع المرتبط بها قابل للإرسال ، فهل يمكنني القيام بشيء مثل هذا:

pub trait Action {
    type Result;
    fn call(&self) -> Self::Result;
}

impl MyStruct {
    pub fn new(name: String) -> impl Action 
    where 
        Return::Result: Send //This Return should be the `impl Action`
    {
        ActionImplementation::new()
    }
}

هل هذا شيء غير ممكن حاليا؟

acycliczebra أعتقد أن بناء الجملة لذلك هو -> impl Action<Result = impl Send> - هذا هو نفس بناء الجملة ، على سبيل المثال ، -> impl Iterator<Item = u32> فقط باستخدام نوع impl Trait آخر مجهول.

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

struct Iter<'a> {
    inner: std::collections::hash_map::Iter<'a, i32, i32>,
}

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

لكن IIRC ، لا أعتقد أن هناك طريقة لتحديد عدة حدود بـ impl Trait في الوقت الحالي ، لذلك سأفقد بعض الأشياء المفيدة مثل Clone .

AGausmann آخر مناقشة حول هذا الموضوع موجودة في https://github.com/rust-lang/rfcs/pull/2515. سيسمح لك ذلك بقول type Foo = impl Bar; struct Baz { field: Foo } ... . أعتقد أننا قد نرغب في اعتبار field: impl Trait سكرًا لذلك بعد الاستقرار type Foo = impl Bar; . إنه يبدو وكأنه امتداد ملائم ملائم للماكرو.

Centril ،

أعتقد أننا قد نرغب في اعتبار field: impl Trait سكرًا

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

يمكن أن يستنتج ذلك ، ولكن إذا كان لديك وظائف متعددة ، فلن يكون من السهل العثور على أي منها

سوف تظهر مطلب تحديد الاستخدامات للنوع الأصل. عندئذ تكون كل تلك الوظائف في نفس الوحدة هي التي ترجع النوع الأصلي. لا يبدو لي أنه من الصعب العثور عليها. أعتقد مع ذلك أننا نريد تسوية القصة على type Foo = impl Bar; قبل المضي قدمًا في الامتدادات.

أعتقد أنني وجدت خطأً في تنفيذ existential type .


رمز

trait Collection {
    type Element;
}
impl<T> Collection for Vec<T> {
    type Element = T;
}

existential type Existential<T>: Collection<Element = T>;

fn return_existential<I>(iter: I) -> Existential<I::Item>
where
    I: IntoIterator,
    I::Item: Collection,
{
    let item = iter.into_iter().next().unwrap();
    vec![item]
}


خطأ

error: type parameter `I` is part of concrete type but not used in parameter list for existential type
  --> src/lib.rs:16:1
   |
16 | / {
17 | |     let item = iter.into_iter().next().unwrap();
18 | |     vec![item]
19 | | }
   | |_^

error: defining existential type use does not fully define existential type
  --> src/lib.rs:12:1
   |
12 | / fn return_existential<I>(iter: I) -> Existential<I::Item>
13 | | where
14 | |     I: IntoIterator,
15 | |     I::Item: Collection,
...  |
18 | |     vec![item]
19 | | }
   | |_^

error: could not find defining uses
  --> src/lib.rs:10:1
   |
10 | existential type Existential<T>: Collection<Element = T>;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ملعب

يمكنك العثور على هذا في stackoverflow أيضًا.

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

https://play.rust-lang.org/؟version=nightly&mode=debug&edition=2018&gist=b4e53972e35af8fb40ffa9a735c6f6b1

fn return_existential<I, J>(iter: I) -> Existential<J>
where
    I: IntoIterator<Item = J>,
{
    let item = iter.into_iter().next().unwrap();
    vec![item]
}

شكرا!
نعم ، هذا ما فعلته كما تم نشره في منشور stackoverflow:

fn return_existential<I, T>(iter: I) -> Existential<T>
where
    I: IntoIterator<Item = T>,
    I::Item: Collection,
{
    let item = iter.into_iter().next().unwrap();
    vec![item]
}

هل هناك خطط لـ impl Trait لتكون متاحة في سياق السمة؟
ليس فقط كنوع مرتبط ، ولكن أيضًا كقيمة إرجاع في الطرق.

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

cramertj تم حل بناء الجملة تقريبًا. أعتقد أن المانع الرئيسي هو GAT الآن.

alexreg : https://github.com/rust-lang/rfcs/pull/2515 لا يزال ينتظر علىwithoutboats.

varkor نعم ، أنا فقط متفائل بأنهم سيرون الضوء مع RFC هذا قريبًا. ؛-)

هل شيء من هذا القبيل يكون ممكنا؟

#![feature(existential_type)]

trait MyTrait {}

existential type Interface: MyTrait;

struct MyStruct {}
impl MyTrait for MyStruct {}

fn with<F, U>(cb: F) -> U
where
    F: FnOnce(&mut Interface) -> U
{
    let mut s = MyStruct {};
    cb(&mut s)
}

يمكنك القيام بذلك الآن ، على الرغم من استخدام وظيفة hint لتحديد النوع الملموس لـ Interface

#![feature(existential_type)]

trait MyTrait {}

existential type Interface: MyTrait;

struct MyStruct {}
impl MyTrait for MyStruct {}

fn with<F, U>(cb: F) -> U
where
    F: FnOnce(&mut Interface) -> U
{

    fn hint(x: &mut MyStruct) -> &mut Interface { x }

    let mut s = MyStruct {};
    cb(hint(&mut s))
}

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

CryZe ما تبحث عنه لا علاقة له بـ impl Trait . راجع https://github.com/rust-lang/rfcs/issues/2413 لمعرفة كل ما أعرفه عنها.

من المحتمل أن يبدو شيئًا من هذا القبيل:

trait MyTrait {}

struct MyStruct {}
impl MyTrait for MyStruct {}

fn with<F, U>(cb: F) -> U
where
    F: for<I: Interface> FnOnce(&mut I) -> U
{
    let mut s = MyStruct {};
    cb(hint(&mut s))
}

KrishnaSannasi آه ، مثير للاهتمام. شكرا!

هل هذا من المفترض أن يعمل؟

#![feature(existential_type)]

trait MyTrait {
    type AssocType: Send;
    fn ret(&self) -> Self::AssocType;
}

impl MyTrait for () {
    existential type AssocType: Send;
    fn ret(&self) -> Self::AssocType {
        ()
    }
}

impl<'a> MyTrait for &'a () {
    existential type AssocType: Send;
    fn ret(&self) -> Self::AssocType {
        ()
    }
}

trait MyLifetimeTrait<'a> {
    type AssocType: Send + 'a;
    fn ret(&self) -> Self::AssocType;
}

impl<'a> MyLifetimeTrait<'a> for &'a () {
    existential type AssocType: Send + 'a;
    fn ret(&self) -> Self::AssocType {
        *self
    }
}

هل يتعين علينا الاحتفاظ بالكلمة الرئيسية existential باللغة لميزة existential_type ؟

jethrogb نعم. حقيقة أنه ليس خطأ حاليًا.

تضمين التغريدة هل يجب علي تقديم مشكلة منفصلة لذلك أم أن رسالتي هنا كافية؟

رفع قضية سيكون رائعا ، شكرا! :)

هل يتعين علينا الاحتفاظ بالكلمة الرئيسية existential باللغة لميزة existential_type ؟

أعتقد أن القصد هو إهمال هذا فورًا عند تنفيذ ميزة type-alias-impl-trait (على سبيل المثال ، وضع نسالة) وإزالتها في النهاية من بناء الجملة.

ربما يمكن لشخص ما أن يوضح بالرغم من ذلك.

إغلاق هذا لصالح مشكلة وصفية تتعقب impl Trait بشكل عام: https://github.com/rust-lang/rust/issues/63066

لا يوجد مثال جيد واحد في أي مكان حول كيفية استخدام سمة ضمنية ، حزين جدًا

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات