Rust: مشكلة في تتبع RFC 1937: `؟` in `main`

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

هذه مشكلة تتبع لـ RFC " ? in main " (rust-lang / rfcs # 1937).

خطوات:

استقرار:

  • [x] تحقيق الاستقرار في main مع أنواع إرجاع بخلاف () (https://github.com/rust-lang/rust/issues/48453) مدمجة في https://github.com/rust-lang/ الصدأ / السحب / 49162
  • [x] موازنة اختبارات الوحدة باستخدام أنواع إرجاع غير () (https://github.com/rust-lang/rust/issues/48854)

القضايا ذات الصلة:

  • [x] رسالة الخطأ الخاصة باختبارات الوحدة ليست رائعة (https://github.com/rust-lang/rust/issues/50291)

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

B-RFC-approved C-tracking-issue E-mentor T-compiler T-lang WG-compiler-middle

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

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

TL؛ DR - أعتقد أن إظهار رسالة Debug لخطأ كان خطأ ، وأن الخيار الأفضل سيكون استخدام رسالة Display لخطأ.

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

من خلال إظهار تمثيل الخطأ Debug افتراضيًا ، أعتقد أيضًا أننا نشجع الممارسة السيئة. على وجه الخصوص ، من الشائع جدًا أثناء كتابة برنامج Rust CLI ملاحظة أنه حتى Display للخطأ ليس جيدًا بما يكفي ليتم استهلاكه من قبل المستخدمين النهائيين ، ويجب القيام بهذا العمل الحقيقي من أجل اصلحه. مثال ملموس على ذلك هو io::Error . إن إظهار io::Error بدون مسار ملف مطابق (بافتراض أنه جاء من قراءة / كتابة / فتح / إنشاء ملف) يعد خطأ في الأساس ، لأنه من الصعب على المستخدم النهائي أن يفعل أي شيء به. باختيار إظهار تمثيل الخطأ Debug افتراضيًا ، فقد جعلنا من الصعب على الأشخاص الذين ينشئون برامج CLI اكتشاف هذه الأنواع من الأخطاء. (علاوة على ذلك ، فإن Debug من io::Error أقل فائدة بكثير من Display ، لكن هذا بحد ذاته ليس نقطة ألم كبيرة في تجربتي. )

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

use std::error::Error;
use std::process;

fn try_main() -> Result<(), Box<Error>> {
    // do stuff with `?`
}

fn main() {
    if let Err(err) = try_main() {
        eprintln!("{}", err);
        process::exit(1);
    }
}

ظاهريًا ، سيكون من _l Lovely_ استبدال هذا بـ ?-in-main ، لكن لا يمكنني ذلك ، لأنه لن يظهر خطأ Display . أي عند كتابة برنامج CLI حقيقي ، سأستخدم في الواقع النهج أعلاه ، لذلك إذا كنت أريد أن تعكس الأمثلة الخاصة بي الواقع ، فأعتقد أنه يجب علي إظهار ما أفعله في البرامج الحقيقية وعدم اتباع طرق مختصرة (إلى حد معقول ). أعتقد في الواقع أن هذا النوع من الأشياء مهم حقًا ، وكان أحد الآثار الجانبية لهذا التاريخ هو أنه أظهر للناس كيفية كتابة كود Rust الاصطلاحي دون رش unwrap في كل مكان. ولكن إذا عدت إلى استخدام ?-in-main في الأمثلة الخاصة بي ، فقد تراجعت للتو عن هدفي: أنا الآن أقوم بإعداد الأشخاص الذين قد لا يعرفون أيًا منهم بشكل أفضل لكتابة البرامج التي ، بشكل افتراضي ، تنبعث منها رسائل خطأ غير مفيدة.

يتم استخدام نمط "إرسال رسالة خطأ والخروج برمز خطأ مناسب" بالفعل في البرامج المصقولة. على سبيل المثال ، إذا استخدم $ ?-in-main Display ، فيمكنني حينئذٍ دمج الدالتين main و run في ripgrep اليوم:

https://github.com/BurntSushi/ripgrep/blob/64317bda9f497d66bbeffa71ae6328601167a5bd/src/main.rs#L56 -L86

بالطبع ، يمكنني استخدام ?-in-main في المستقبل من خلال تقديم الضمانة الخاصة بي للسمة Termination بمجرد أن تستقر ، لكن لماذا أزعج نفسي بفعل ذلك إذا كان بإمكاني كتابة main وظيفة لدي؟ ومع ذلك ، فإن هذا لا يساعد في حل اللغز عند كتابة الأمثلة. سأحتاج إلى تضمين ذلك impl في الأمثلة لجعلها تتطابق مع الواقع ، وفي هذه المرحلة ، قد ألتزم أيضًا بالأمثلة التي لدي اليوم (باستخدام main و a try_main ).

من مظهره ، يبدو أن إصلاح هذا سيكون تغييرًا جذريًا. وهذا يعني أن هذا الرمز يتم تجميعه على Rust Stable اليوم:

#[derive(Debug)]
struct OnlyDebug;

fn main() -> Result<(), OnlyDebug> {
    Err(OnlyDebug)
}

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

ال 183 كومينتر

كيف سيتم التعامل مع حالات الخروج؟

يبدو أن هذا التعليق من Screwtapello قد تم قريبًا جدًا من نهاية FCP لإجراء أي تعديلات على RFC ردًا عليه.

باختصار: تقترح RFC إعادة 2 عند الفشل على أسس غامضة ، رغم أنها تستند إلى أسس جيدة ، وتؤدي إلى نتيجة غير عادية إلى حد ما ؛ الشيء الأقل إثارة للدهشة هو العودة 1 عندما لا يكون للبرنامج ما يشير إلى أنه يريد أي تفاصيل أكثر من مجرد النجاح أو الفشل. هل هذا bikesheddy كافٍ بحيث يمكن مناقشته دون الشعور بأننا نحرف عملية RFC ، أم أننا الآن محبوسون في تفاصيل التنفيذ المحددة هذه؟

إنها ليست تفاصيل تنفيذية ، أليس كذلك؟

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

يتعلق هذا تحديدًا بالحالة التي لا تحتوي فيها عملية فرعية (تم تنفيذها في Rust) على معلومات تقدمها ، بخلاف "كل شيء على ما يرام" / "حدث خطأ ما".

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

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

لا أعتقد أن SemVer لديها استثناء "معظم التفاصيل غير مهمة" بالرغم من ذلك.

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

يتفق الكثير من الناس على أن رمز الخروج يجب أن يكون 1 عند الفشل (بدلاً من 2 ):
https://www.reddit.com/r/rust/comments/6nxg6t/the_rfc_using_in_main_just_got_merged/

@ arielb1 هل ستقوم بتنفيذ هذا التردد الراديوي؟

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

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

آه جميل ، سأكون مهتمًا بفعل ذلك :)
لكن ليس لدي أي فكرة من أين أبدأ: د

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

لهذا السبب أنا هنا :-). يجب أن أكتب تعليمات التوجيه في وقت قريب بما فيه الكفاية.

حسنًا ، فأنا في انتظار تعليماتك.

تعليمات التوجيه

هذه مشكلة [WG-compiler-middle]. إذا كنت ترغب في طلب المساعدة ، يمكنك الانضمام إلى #rustc على irc.mozilla.org (أنا arielby) أو https://gitter.im/rust-impl-period/WG-compiler-middle (أنا @ arielb1 هناك).

يوجد ملف تمهيدي لمترجم ويب في # 44505 - يصف بعض الأشياء في المترجم.

خطة العمل لهذا RFC:

  • [] - أضف عنصر lang Termination إلى libcore
  • [] - السماح باستخدام Termination في main
  • [] - السماح باستخدام Termination في الاطباء
  • [] - السماح باستخدام Termination في #[test]

أضف العنصر lang Termination إلى libcore

أولاً ، تحتاج إلى إضافة سمة Termination إلى libcore/ops/termination.rs ، جنبًا إلى جنب مع بعض الوثائق. ستحتاج أيضًا إلى تمييزه على أنه غير مستقر بسمة #[unstable(feature = "termination_trait", issue = "0")] - سيمنع هذا الأشخاص من استخدامه قبل أن يستقر.

بعد ذلك ، تحتاج إلى وضع علامة عليه كعنصر lang في src/librustc/middle/lang_items.rs . هذا يعني أن المترجم يمكنه اكتشافه عند فحص النوع main (على سبيل المثال ، انظر 0c3ac648f85cca1e8dd89dfff727a422bc1897a6).
هذا يعني:

  1. إضافته إلى قائمة العناصر lang (في librustc/middle/lang_items.rs )
  2. إضافة #[cfg_attr(not(stage0), lang = "termination")] إلى السمة Termination . السبب في أنه لا يمكنك فقط إضافة سمة #[lang = "termination"] هو أن المحول البرمجي "stage0" (أثناء التمهيد) لن يعرف أن termination شيء موجود ، لذلك لن يكون قادرًا على ذلك تجميع libstd. سنقوم يدويًا بإزالة cfg_attr عندما نقوم بتحديث مترجم stage0.
    راجع مستندات bootstrapping في XXX للحصول على التفاصيل.

السماح باستخدام Termination بشكل رئيسي

هذا هو الجزء المثير للاهتمام الذي أعرف كيف أتعامل معه. هذا يعني أن إجراء main يُرجع () بدون فحص نوع (حاليًا تحصل على main function has wrong type خطأ) والعمل.

لإجراء فحص النوع ، تحتاج أولاً إلى إزالة الخطأ الموجود في:
https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/librustc_typeck/lib.rs#L171 -L218

بعد ذلك ، تحتاج إلى إضافة تحقق من أن نوع الإرجاع ينفذ السمة Termination (يمكنك إضافة التزام سمة باستخدام register_predicate_obligation - ابحث عن استخدامات ذلك). يمكن القيام بذلك هنا:
https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/librustc_typeck/check/mod.rs#L1100 -L1108

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

lang_start معرّف حاليًا هنا:
https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/libstd/rt.rs#L32

لذلك ستحتاج إلى تغييره ليكون عامًا ومطابقًا لـ RFC:

#[lang = "start"]
fn lang_start<T: Termination>
    (main: fn() -> T, argc: isize, argv: *const *const u8) -> !
{
    use panic;
    use sys;
    use sys_common;
    use sys_common::thread_info;
    use thread::Thread;

    sys::init();

    sys::process::exit(unsafe {
        let main_guard = sys::thread::guard::init();
        sys::stack_overflow::init();

        // Next, set up the current Thread with the guard information we just
        // created. Note that this isn't necessary in general for new threads,
        // but we just do this to name the main thread and to give it correct
        // info about the stack bounds.
        let thread = Thread::new(Some("main".to_owned()));
        thread_info::set(main_guard, thread);

        // Store our args if necessary in a squirreled away location
        sys::args::init(argc, argv);

        // Let's run some code!
        let exitcode = panic::catch_unwind(|| main().report())
            .unwrap_or(101);

        sys_common::cleanup();
        exitcode
    });
}

وبعد ذلك ستحتاج إلى الاتصال به من create_entry_fn . حاليًا ، يقوم بإنشاء مثيل أحادي الشكل lang_start باستخدام Instance::mono ، وستحتاج إلى تغييره لاستخدام monomorphize::resolve مع المحطات الفرعية الصحيحة.

https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/librustc_trans/base.rs#L697

السماح باستخدام Termination في الطب

أنا لا أفهم حقًا كيف تعمل الدكتاتور. ربما اسأل alexcrichton (هذا ما سأفعله)؟

السماح باستخدام Termination in #[test]

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

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

هل يمكنك على الأقل الانضمام إلى IRC / gitter؟

bkchr فقط سجل الوصول - رأيتك أنت و @ arielb1 تتحدثان عن gitter منذ بعض الوقت ، أي تقدم؟ تمتص في مكان ما؟

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

bkchr إذا كنت بحاجة إلى بعض المساعدة أخبرني!

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

bkchr يجب إضافة السمة إلى قائمة عناصر lang ، على سبيل المثال: https://github.com/rust-lang/rust/blob/ade0b01ebf18550e41d24c6e36f91afaccd7f389/src/librustc/middle/lang_items.rs#L312
واحصل على علامة #[termination_trait] ، على سبيل المثال: https://github.com/rust-lang/rust/blob/ade0b01ebf18550e41d24c6e36f91afaccd7f389/src/libcore/fmt/mod.rs#L525 -L526

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

حسنًا ، كل ما تحتاجه هو tcx.require_lang_item(TerminationTraitLangItem) .

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

آسف ، مشغول في الوقت الحالي: / حتى الآن ، حصلت على كل المساعدة التي احتاجها :)

هذا هو الكود للتحقق من TerminationTrait: https://github.com/bkchr/rust/blob/f185e355d8970c3350269ddbc6dfe3b8f678dc44/src/librustc_typeck/check/mod.rs#L1108

أعتقد أنني لا أتحقق من نوع الإرجاع للوظيفة؟ أحصل على الخطأ التالية:

error[E0277]: the trait bound `Self: std::ops::Termination` is not satisfied
  --> src/rustc/rustc.rs:15:11
   |
15 | fn main() { rustc_driver::main() }
   |           ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::ops::Termination` is not implemented for `Self`
   |
   = help: consider adding a `where Self: std::ops::Termination` bound

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

bkchr أوصي بالانضمام إلى مجموعة عمل compiler-middle gitter على https://gitter.im/rust-impl-period/WG-compiler-middle للحصول على تعليقات ، بالإضافة إلى تجربة # rust-internals قناة IRC على https : //chat.mibbit.com/؟ server = irc.mozilla.org٪ 3A٪ 2B6697 & channel =٪ 23rust-internals. :)

bstrie نعم شكرًا ، أنا بالفعل جزء من الدردشة العميقة ويمكنني حل مشكلتي. :)

bkchr مشكلتك على هذا الخط . مرجع السمة الذي تريد إنشاءه هناك شيء مثل R: Termination حيث R هو نوع إرجاع الوظيفة. يتم تحديد ذلك من خلال تكوين "عناصر فرعية" مناسبة ، وهي مجموعة القيم لتحل محل معلمات نوع السمة (في هذه الحالة ، Self ).

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

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

يمكنك عمل المحطات الفرعية التي تريدها بمجرد استدعاء طريقة mk_substs() من tcx. في هذه الحالة ، هناك معامل واحد فقط ، النوع ، لذا أعتقد أن شيئًا مثل let substs = fcx.tcx.mk_substs(&[ret_ty]); سيعمل.

أعتقد أن الشيء الذي يجب استخدامه هو tcx.mk_substs_trait(ret_ty, &[]) .

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

نعم ، يمكنني حل المشكلة مع gitter :)

bkchr كيف يذهب؟ مجرد التدقيق في.

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

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

@ U007D

هذه ميزة صغيرة وكاد ينتهيbkchr معها.

آه ، حسنًا - من الجيد معرفة ذلك ، شكرًا. سأراقب شيئًا آخر يمكنني المساعدة فيه.

@ U007D هل رأيت https://www.rustaceans.org/findwork ؟

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

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

شكرا على الاقتراح ، lnicola. هذا مصدر جيد.

bkchr أي تحديثات؟

أنا موجود عليه (https://github.com/rust-lang/rust/pull/46479). الآن لدي إجازات ووقت للعمل في التعليقات في طلب السحب. آسف على كل التأخيرات: /

أوه ، آسف ، لم ألاحظ أن لديك طلب سحب. عبر ربطها.

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

  • [] اسم السمة التي يتم تقديمها

ماذا عن Exit ؟ إنه قصير ومباشر ، يناسب مفردات Rust الموجودة. الخروج كاسم هو المقابل الطبيعي للخروج كفعل والذي ، بالنسبة لمعظم الناس ، هو الكلمة المألوفة لإنهاء عملية "من الداخل" بطريقة مضبوطة.

بالنسبة لمبرمج C ++ على وجه التحديد ، "الإنهاء" يذكرنا بـ std::terminate الذي يتحول إلى إنهاء غير طبيعي (استدعاء abort ) وهو أساسًا C ++ معادل للذعر (ولكن على عكس الذعر ، لا يريح أبدًا كومة).

انتظر ، تجاهل هذا التعليق ، يبدو أن RFC ترك مفتوحًا بشكل صريح للمناقشة.

أنا أحب Exit كاسم سمة.

أظن أن الميزة ستستقر قبل السمة بوقت طويل ، كما حدث مع Carrier .

FWIW هذه حالة أخرى يسعدني فيها حقًا تغيير الاسم المؤقت قبل التثبيت: D

بصفتي مؤلف RFC ، ليس لدي أي اعتراض على تغيير اسم السمة إلى Exit ، أو أي شيء آخر بالفعل. لست جيدًا بشكل خاص في تسمية الأشياء ويسعدني أن يكون لدى شخص آخر فكرة أفضل.

https://github.com/rust-lang/rust/blob/5f7aeaf6e2b90e247a2d194d7bc0b642b287fc16/src/libstd/lib.rs#L507

هي السمة المفترض أن تكون

  1. وضعها في libstd بدلاً من libcore ، و
  2. اتصلت للتو بـ std::Termination ، وليس std::ops::Termination ؟

لا يمكن وضع السمة في libcore ، لأن تنفيذ Result يتطلب الطباعة إلى stderr ولا يمكن القيام بذلك في libcore .

bkchr لا يعني التواجد في libstd أن السمة يجب أن تكون في libstd أيضًا.

أعرف @ kennytm ، لكن النتيجة معرّفة أيضًا في libcore ، لذا لا يمكن تنفيذ الإنهاء للنتيجة في libstd.

zackw +1 صوت آخر لـ Exit كاسم سمة.

@ U007D : هل يمكنك استخدام زر ردود الفعل (على سبيل المثال ، 👍) بدلاً من إرسال مثل هذه الرسالة؟ سيتيح لك ذلك تجنب مشكلة المشتركين المزعجين من خلال اختبار اتصالهم بلا داع.

هل يمكنني تسجيل الوصول إلى libtest / libsyntax ، إذا تم تنشيط language_feature (في صندوق)؟ MustafaHosny اللهم امين

bkchr في libsyntax ، قد تضطر إلى تمريره ولكنه ممكن من الناحية النظرية ، لكن في libtest نفسه في وقت التشغيل لا أعتقد أنه يمكنك التحقق منه.

bkchr كيف يذهب هنا؟

ما زلت أعمل عليها ، لكن ليس لدي حاليًا المزيد من الأسئلة :)

أعتقد أن هذا الضمني صارم للغاية:

#[unstable(feature = "termination_trait", issue = "43301")]
impl<T: Termination, E: Error> Termination for Result<T, E> {
    fn report(self) -> i32 {
        match self {
            Ok(val) => val.report(),
            Err(err) => {
                print_error(err);
                exit::FAILURE
            }
        }
    }
}


#[unstable(feature = "termination_trait", issue = "43301")]
fn print_error<E: Error>(err: E) {
    eprintln!("Error: {}", err.description());

    if let Some(ref err) = err.cause() {
        eprintln!("Caused by: {}", err.description());
    }
}

هناك عدة أخطاء شائعة الاستخدام لا تنفذ Error ، وأهمها Box<::std::error::Error> و failure::Error . أعتقد أيضًا أنه من الخطأ استخدام طريقة description بدلاً من عرض هذا الخطأ.

أقترح استبدال هذا الضمني بهذا الضمني الأوسع:

#[unstable(feature = "termination_trait", issue = "43301")]
impl<T: Termination, E: Display> Termination for Result<T, E> {
    fn report(self) -> i32 {
        match self {
            Ok(val) => val.report(),
            Err(err) => {
                eprintln!("Error: {}", err)
                exit::FAILURE
            }
        }
    }
}

هذا لا يفقد سلسلة السبب وهو المشكله.

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

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

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

  • {} يطبع هذا الخطأ فقط
  • {:?} يطبع هذا الخطأ وسببه (بشكل متكرر)

يمكننا أن نقرر استخدام :? هنا وربطه Debug بدلاً من Display. غير متأكد.

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

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

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

withoutboats أوافق.

nikomatsakis أفترض أنك تقصد " ليس بالضرورة في أجمل شكل"؟ إذا كان الأمر كذلك ، نعم ، أوافق.

تحديث: بعد العمل على هذا لبضعة أيام ، لقد انقلبت على هذا. انظر أدناه.

: +1: على Debug هنا؛ أحب "نوعًا مشابهًا لاستثناء غير معروف" الخاص بـ nikomatsakis من https://github.com/rust-lang/rfcs/pull/1937#issuecomment -284509933. تعليق من Diggsey يقترح أيضًا Debug : https://github.com/rust-lang/rfcs/pull/1937#issuecomment -289248751

لمعلوماتك ، لقد انقلبت على المشكلة الافتراضية "أكثر اكتمالًا" مقابل "أكثر سهولة في الاستخدام" (على سبيل المثال ، Debug مقابل Display مرتبطة بالسمة).

TL ؛ DR أعتقد الآن أنه يجب علينا تعيين الحد ليكون على Display (وفقًا للمنشور الأصلي withoutboats ) لتوفير إخراج أنظف وملخص في حالة "عدم فعل أي شيء".

هذا هو الأساس المنطقي الخاص بي:

في قضية RFC الخاصة بالسمة termination ، يشيرzackw إلى أن Rust لديه النظام المزدوج panic / Result لأن panic s للأخطاء و Result s للأخطاء. من هذا ، أعتقد أنه يمكن تقديم حالة مقنعة لتقييم عرض الخطأ الافتراضي بشكل مستقل عن عرض الذعر الافتراضي.

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

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

  • كما أشار nikomatsakis ، بصرف النظر عن الإعداد الافتراضي الذي نختاره ، يمكن لأي مطور يرغب في تغيير السلوك إما استخدام نمط newtype أو تطوير تنفيذ مخصص في main ().

وأخيرًا على الجانب الذاتي ، أثناء العمل مع هذه الميزة خلال اليومين الماضيين ، وجدت أن الناتج Debug جعلني أشعر بأن "تطبيق Rust" الخاص بي شعرت بأنه غير مصقول :

$ foo
Error: Custom { kind: Other, error: StringError("returned Box<Error> from main()") }
$

ضد

$ foo
Error: returned Box<Error> from main()
$

يبدو أن السمة Dispay مجرد تقصير أكثر تحضرًا لخطأ ما (على عكس الخطأ) ، أليس كذلك؟

@ U007D انتظر ، أي من هذين المخرجين تفضل؟

(أ) Error: Custom { kind: Other, error: StringError("returned Box<Error> from main()") }

أو

(ب) Error: returned Box<Error> from main()

يفضل الخيار (ب).

nikomatsakis في الأصل ، كنت على ما يرام مع أ) Debug كمفهوم في رأسي ، ولكن بعد العمل معه لبضعة أيام أرى المخرجات بالفعل ، أفضل الآن b) Display كـ افتراضي. أعتقد أن تفضيلي لـ ب) سيصبح أكثر وضوحًا إذا كنت أنمذجة خطأ متسلسل.

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

في هذه الحالات ، بالنسبة لي ، "المفاجأة الأقل" هي ناتج موجه للمطورين تمامًا مثل unwrap .

هل يستحق الأمر مناقشة {:#?} هنا ، إذا كان هناك قلق بشأن خطأ طويل الأمد؟

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

أعتقد أن جوهر هذا النقاش هو حقًا "من هو الجمهور المستهدف للرسالة الافتراضية؟"

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

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

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

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

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

(هل يبدو الإخراج حرفياً مثل @ U007D الذي تم لصقه أعلاه؟ لماذا يطبع "Box \ المرتجع"المربع <خطأ>؟)

كم مرة تكون رسالة الخطأ Display سهلة الاستخدام بدرجة كافية؟ على سبيل المثال ، البرنامج التالي:

fn main() {
    if let Err(e) = std::fs::File::open("foo") {
        println!("{}", e)
    }
}

ترسل الرسالة التالية:

No such file or directory (os error 2)

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

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

هل الإخراج يبدو حرفيا مثل @ U007D الذي تم لصقه أعلاه؟ لماذا ستتم طباعة "Box المرتجعمن main () "بدلاً من ... المحتويات الفعلية لذلك المربع؟

glaebhoerl أنت محق - في هذه الحالة ، "المحتويات الفعلية لذلك Box<Error> " كانت حقل message مخصصًا قمت بإنشائه لاختبار termination_trait ، المعروض حرفيًا . كان بإمكاني كتابة "foo bar baz" أو أي شيء آخر هناك بدلاً من ذلك (ولكن ربما لم يكن ذلك مفيدًا لمستخدم يجري اختبارات المترجم).

باستخدام مثالjdahlstrom ، إليك الإخراج الفعلي لملف Box ed للمكتبة القياسية " Error (ملاحظة ، كما أشرت بشكل صحيح ، لا يوجد ذكر للملاكمة في أي مكان):
Debug :

$ foo
Error { repr: Os { code: 2, message: "No such file or directory" } }
$

و Display :

$ foo
No such file or directory (os error 2)
$

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

إن تقديم Display لمطور البرامج له جميع الجوانب السلبية لـ Debug بالإضافة إلى أنه يفقد خصوصية نوع الخطأ الذي يتم عرضه.

يؤدي توفير Debug للمستخدم إلى جميع الجوانب السلبية لـ Display plus ويضيف المزيد من المعلومات التقنية التي لا يحتاجها المستخدم وقد لا يكون قادرًا على فهمها.

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

أحتاج إلى بعض المساعدة لتنفيذ دعم ? في #[test] . يمكن العثور على التنفيذ الحالي الخاص بي هنا: https://github.com/rust-lang/rust/compare/master...bkchr : termination_trait_in_tests

يؤدي تجميع اختبار مع تغييراتي إلى حدوث الخطأ التالي:

error: use of unstable library feature 'test' (see issue #27812)
  |
  = help: add #![feature(test)] to the crate attributes to enable

قال eddyb إنني لا يجب أن أستخدم quote_item!/expr! بعد الآن ، لأنها إرث.
ماذا علي أن أفعل الآن ، التبديل إلى الماكرو quote! الجديد أو إعادة صياغة كل شيء إلى مبنى ast اليدوي؟

أنا أقدر أي مساعدة :)

أعتقد أن إنشاء استدعاء ماكرو لبعض الماكرو المحدد في libtest يمكن أن يعمل جيدًا.

eddyb لست متأكدًا من أنني أفهم اقتراحك هنا:

أعتقد أن إنشاء استدعاء ماكرو لبعض الماكرو المحدد في libtest يمكن أن يعمل جيدًا.

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


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

يؤدي تجميع اختبار مع تغييراتي إلى حدوث الخطأ التالي:

هل لديك أي فكرة عن سبب حدوث هذا الخطأ؟ فقط من قراءة الفرق ، لا أفعل ذلك ، لكن يمكنني محاولة البناء محليًا ومعرفة ذلك.

يجب ألا أستخدم quote_item!/expr! بعد الآن ، لأنها قديمة.

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

من المؤكد أن بناء AST اليدوي هو أمر مؤلم ، على الرغم من أنني أعتقد أن لدينا بعض المساعدين لذلك.

في الغالب نحتاج فقط إلى إجراء تعديل بسيط ، أليس كذلك؟ على سبيل المثال ، التغيير من استدعاء الوظيفة إلى اختبار نتيجة report() ؟

ملاحظة ، ربما نريد إنشاء شيء مثل Termination::report(...) بدلاً من استخدام تدوين .report() ، لتجنب الاعتماد على السمة Termination في النطاق؟

لا ، ليس لدي أي فكرة عن مصدر هذا الخطأ :(

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

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

alexcrichton هل لديك فكرة من أين يأتي هذا الخطأ؟

nikomatsakis libtest غير مستقر ولا يمكننا أيضًا وضع علامة على وحدات الماكرو على أنها غير مستقرة حتى لو لم تكن كذلك؟

eddyb أوه ، نقطة جيدة.

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

وظيفة المجمع تبدو جيدة بالنسبة لي.

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

bkchr وضع سمة #[unstable(...)] على تعريف الماكرو ، على سبيل المثال: https://github.com/rust-lang/rust/blob/3a39b2aa5a68dd07aacab2106db3927f666a485a/src/libstd/thread/local.rs#L159 -L165

لذا ، هل يجب أن يكون هذا المربع الأول ...

تنفيذ RFC

... التحقق الآن بعد أن تم دمج العلاقات العامة المرتبطة؟

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

حسنًا ، إنه حاليًا فقط نصف rfc الذي يتم تنفيذه باستخدام pr ^^ المدمج

مقسمة إلى 3 مربعات اختيار :)

كنت أحاول استخدام هذه الميزة في main ووجدتها محبطة للغاية. لا تسمح لي الضمانات الحالية لسمة الإنهاء "بتجميع" أنواع متعددة من الأخطاء بشكل ملائم - على سبيل المثال ، لا يمكنني استخدام failure::Fail ، لأنها لا تنفذ Error ؛ لا يمكنني استخدام Box<Error> لنفس السبب. أعتقد أننا يجب أن نعطي الأولوية للتغيير إلى Debug . =)

مرحبًا nikomatsakis ،

شعرت بالإحباط نفسه تمامًا كما شعرت به عندما حاولت استخدام termination_trait .

هذا ، جنبًا إلى جنب مع مشاركاتك حول القرصنة على المترجم ، ألهمني لأخذ حل لهذه المشكلة في وقت سابق من هذا الشهر. لقد قمت بنشر الضميمة مقابل DisplayDebug في الالتزام السابق) جنبًا إلى جنب مع الاختبارات هنا: https://github.com/rust-lang/rust/pull/47544. (إنه بسيط جدًا ، لكنه لا يزال ، أول مترجم لي Rust PR!: tada :) :)

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

سؤال ما زلت مهتمًا به: لنفترض أنك لا تريد الاعتماد على مخرجات رسالة الخطأ الافتراضية (سواء كان Debug أو Display ) ، وتريد بنفسك بدلاً من ذلك ، كيف تفعل الذي - التي؟ (أعتذر إذا كان هذا مكتوبًا بالفعل في مكان ما وقد فاتني ذلك.) لن تضطر إلى التوقف عن استخدام ? -in- main بالكامل ، أليس كذلك؟ إنه شيء مثل كتابة النتيجة الخاصة بك و / أو نوع الخطأ و / أو impl ؟ (يبدو الأمر مؤسفًا بالنسبة لي إذا كان ? -in- main مجرد لعبة ، وبمجرد رغبتك في "أن تصبح جادًا" ، عليك العودة إلى طرق أقل راحة.)

glaebhoerl الأمر واضح جدًا:

  1. قم بإنشاء نوع جديد.
  2. نفذ نوع الخطأ القديم From .
  3. تنفيذ Debug (أو Display ).
  4. استبدل النوع الموجود في توقيع main .

شكرا!

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

glaebhoerl لهذا السبب يجب أن تستخدم الأداة Result Display بدلاً من Debug IMO.

لا يمكن أن تحتوي السمة Termination على طريقة إضافية تسمى message ، أو error_message أو شيء من هذا القبيل ، لديها تطبيق افتراضي يستخدم Debug / Display لإظهار الرسالة؟ ثم عليك فقط تنفيذ طريقة واحدة بدلاً من إنشاء نوع جديد.

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

zackw موافق ، سأكون على ما يرام شخصيًا مع عدم وجود رسائل ، فقط رقم ، دعنا نقول 0 و 1 لرموز الخطأ. ولكن إذا أردنا الحصول على الرسائل في التكرار الأول ، أعتقد أنني سأكون أكثر تفضيلًا لـ Termination::message أكثر من أي شيء على Debug أو Display .

هل تؤدي هذه الطريقة message إلى إرجاع String ؟ ألن يكون هذا غير متوافق مع وجود Termination في libcore؟

SimonSapin Termination معرّف حاليًا بـ libstd .

حسنًا ، لكنني لا أعتقد أن إضافة طريقة message إلى السمة ستكون أفضل تطبيق. ما الذي ستعيده الطريقة لأنواع مثل i32 ؟ كيف ستقرر متى ستطبع هذه الرسالة؟ حاليًا ، يؤدي تنفيذ Termination لـ Result إلى ظهور الخطأ في الدالة report . هذا يعمل ، لأن Result يعلم أنه Err . يمكننا دمج شيك في مكان ما report() != 0 ثم طبعه ، لكن هذا لا يبدو صحيحًا.
ستكون المشكلة التالية ، أننا نريد توفير تطبيق قياسي لـ Result ، ولكن ما الذي يحتاجه النوع Error ليتم طباعته على الأرجح؟ سيعيدنا هذا إلى السؤال الحالي Debug أو Display .

صداع آخر يظهر إذا كنت تريد استخدام ? بشكل رئيسي في برنامج "جاد" هو أنه ، في بعض الظروف ، تريد برامج سطر الأوامر الخروج بحالة غير صفرية ولكن بدون طباعة _ أي شيء_ (ضع في اعتبارك grep -q ). إذن أنت الآن بحاجة إلى ضمانة Termination لشيء _ ليس _ Error ، لا يطبع شيئًا ، يتيح لك التحكم في حالة الخروج ... وعليك أن تقرر ما إذا كنت تريد " إعادة هذا الشيء _after_ بتحليل وسيطات سطر الأوامر.

هذا ما أفكر فية:

يجب أن يستخدم إرجاع Result<T, E> الضمني لتصحيح الأخطاء لـ E. هذه هي السمة الأكثر ملاءمة - يتم تنفيذها على نطاق واسع وتلبي حالة استخدام "الإخراج السريع والقذر" بالإضافة إلى حالة استخدام اختبار الوحدة. أفضل عدم استخدام Display على حد سواء لأنه أقل تطبيقًا على نطاق واسع ولأنه يعطي انطباعًا بأن هذا ناتج مصقول ، وهو ما أعتقد أنه غير محتمل إلى حد كبير.

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

fn main() -> ProfessionalLookingResult {
    ...
}

ثم قم بتنفيذ Try مقابل ProfessionalLookingResult . ثم يمكنك تنفيذ Terminate أيضًا ، أيا كان:

impl Terminate for ProfessionalLookingResult {
    fn report(self) -> i32 {
        ...
        eprintln!("Something very professional here.");
        return 1;
        ...
    }
}

أتفق مع nikomatsakis على أن هذا يجب أن يستخدم Debug .

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

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

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

أود أن أرى ما يلي

fn main() -> i32 {
    1
}

والتي يمكن كتابتها بشكل عام على النحو التالي:

fn main() -> impl Display {
    1
}

يجب أن تُرجع هاتان الوظيفتان الرئيسيتان رمز خروج 0 و println! Display من 1.

يجب أن يكون هذا بسيطًا مثل ما يلي (على ما أعتقد).

impl<T> Termination for T where T: Display {
    fn report(self) -> i32 {
        println!("{}", self);
        EXIT_SUCCESS
    }
}

ثم للأخطاء يمكن أن يكون لدينا:

impl<T: Termination, E: Debug> Termination for Result<T, E> { ... }

حيث يكون التنفيذ هو نفسه كما في RFC فقط باستخدام "{:?}" للاستخدام
تنسيق Debug .

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

fn main() -> Result<i32, MyError> { ... }
impl Termination for Result<i32, MyError> { ... }

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

fn main() -> MyResult { ... }
impl Termination for MyResult { ... }
or, if you want something more general.
impl<T, E> Termination for MyResult<T, E> { ... }

أعلم أن هذا يعيد ذكر الأشياء التي قيلت جزئيًا ، لكنني اعتقدت أنني سأقدم رؤيتي تمامًا ، وأظهر أن الحل الأكثر عمومية لعرض القيم المرتجعة ، وليس النتائج فقط. يبدو أن الكثير من هذا التعليق يناقش ماهية تطبيقات Termination التي نشحنها افتراضيًا. هذا التعليق أيضًا يتعارض مع التنفيذ مثل impl Termination for bool كما هو موضح في RFC. أنا شخصياً أعتقد أنه يجب التعامل مع أكواد الخروج غير الصفرية حصريًا بواسطة Results أو الأنواع المخصصة التي تطبق Termination .

إنه سؤال مثير للاهتمام حول كيفية التعامل بعد ذلك مع أنواع ? على Option بشكل رئيسي نظرًا لعدم وجود تطبيق مقابل Display .

TL ؛ DR: أنا موافق مع Debug .

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

الافتراض الأساسي: من بين جميع الأنواع المختلفة للتطبيقات التي يمكن للمرء كتابتها في Rust ، أعتقد أن تطبيق وحدة التحكم سيستفيد أكثر / يتأثر بهذا القرار أكثر من غيره. تفكيري هو أنه عند كتابة مكتبة أو عنوان لعبة AAA أو IDE أو نظام تحكم خاص ، فمن المحتمل ألا يتوقع المرء سمة إنهاء رئيسية افتراضية لتلبية احتياجات المرء خارج الصندوق (في الواقع ، قد لا يكون لدى المرء حتى رئيسي). لذا فإن تحيزي تجاه Display كإعداد افتراضي يأتي مما نتوقع رؤيته عند استخدام تطبيق صغير لسطر الأوامر - على سبيل المثال:

$ cd foo
bash: cd: foo: No such file or directory

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

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

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

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

بالتمسك بافتراض "المستفيد الأساسي هو تطبيق cli" ، هناك عدد كبير من تطبيقات وحدة التحكم التي توفر المساعدة ومعلومات الاستخدام. علي سبيل المثال:

$ git foo
git: 'foo' is not a git command. See 'git --help'.

The most similar command is
    log

لذا ، حتى في تطبيقات وحدة التحكم ، يصعب علي تحديد "مجموعة مصابة" من خلال استخدام Debug .

وأخيرًا ، سأكون أكثر سعادة مع ضمانة Debug من الميزة التي تم تعليقها لمدة 6 أشهر أخرى ، أيضًا ، لأنانية ، هناك هذا :).

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

مثل الكثيرين منكم ، أنا متأكد من أنني أتمنى أن يكون هناك تطبيق شعرت بمزيد من الحماس بشأنه - مثل ، "نعم ، هذا كل شيء !!!" ، TBH. ولكن ربما تكون هذه مجرد توقعاتي غير واقعية ... ربما بمجرد أن يكون لدينا حل يعمل بـ failure لخفض النموذج المعياري في مشاريعي ، سوف ينمو عليّ. :)

ملاحظة فتحت PR لدعم سمة terminatio في الاختبارات: # 48143 (بناء على عمل bkchr ).

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

يجب إعادة تسمية $ # Termination Terminate بعد تفضيلنا العام للأفعال للسمات في libstd.

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

التعشية غير المبررة للدراجات: هذه سمة أحادية الأسلوب. إذا أردنا منحهم نفس الاسم ، فربما يكون ToExitCode / to_exit_code ؟

يمكن تحقيق الاستقرار العائد Result بشكل مستقل عن تثبيت السمة ، أليس كذلك؟

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

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

SimonSapin أعتقد أن To يشير إلى تحويل من النوع ، وهذا لا يحدث. لكن يمكننا تسمية الطريقة terminate (كما أنني لا أعتقد أن هذا القيد على وقت تسمية أفعال الصفات صالح. Try هو مثال مضاد واضح.)

لقد اقترحت أن نحقق الاستقرار في fn main() -> T حيث T ليست وحدة. هذا يترك العديد من التفاصيل - خاصة الاسم / الموقع / تفاصيل السمة - غير مستقرة ، ولكن تم إصلاح بعض الأشياء. التفاصيل هنا:

https://github.com/rust-lang/rust/issues/48453

يرجى تقديم ملاحظاتك!

يبدو أن terminate أكثر وصفًا من report . نحن دائمًا terminate ، لكن يمكننا حذف report ing.

ولكن على عكس على سبيل المثال std::process::exit هذه الطريقة لا تنهي أي شيء. يقوم فقط بتحويل قيمة الإرجاع main() إلى رمز خروج (بعد طباعة Result::Err اختياريًا إلى stderr).

صوت آخر لـ Exit . يعجبني أنه موجز وصفي تمامًا ومتسق مع المفهوم التقليدي لرموز الخروج / حالة الخروج.

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

لقد أضفت https://github.com/rust-lang/rust/issues/48854 لاقتراح اختبارات وحدة الاستقرار التي تعيد النتائج.

أوه ، لقد وجدت المكان المناسب للحديث عن هذا.

استخدام ? في الاطباء

الطريقة التي تعمل بها العقيدة حاليًا هي شيء من هذا القبيل:

  • يفحص rustdoc الدليل لمعرفة ما إذا كان يعلن عن fn main

    • (في الوقت الحالي ، يقوم فقط بالبحث عن نص سطر بسطر عن "fn main" الذي لا يأتي بعد // )

  • إذا تم العثور على fn main ، فلن يمس ما هو موجود بالفعل
  • إذا لم يتم العثور على fn main ، فسيتم التفاف معظم * الدستور في fn main() { } أساسي

    • * النسخة الكاملة: ستستخرج إعلانات #![inner_attributes] و extern crate وتضعها خارج الوظيفة الرئيسية التي تم إنشاؤها ، لكن كل شيء آخر يذهب إلى الداخل.

  • إذا لم يتضمن الدليل أي عبارات صندوق خارجي (والصندوق الذي يتم توثيقه لا يسمى std ) ، فسيقوم rustdoc أيضًا بإدراج عبارة extern crate my_crate; قبل الوظيفة الرئيسية التي تم إنشاؤها مباشرةً.
  • ثم يقوم rustdoc بتجميع وتشغيل النتيجة النهائية كثنائي مستقل ، كجزء من أداة الاختبار.

(لقد تركت بعض التفاصيل ، لكنني قمت بكتابة كتابة كاملة هنا بشكل ملائم.)

لذلك ، لاستخدام ? بسلاسة في العقيدة ، فإن الجزء الذي يجب تغييره هو الجزء الذي يضيف فيه fn main() { your_code_here(); } إعلان fn main() -> Result<(), Error> الخاص بك سيعمل بأسرع ما يمكنك القيام به ذلك في الكود العادي - لا يحتاج rustdoc إلى التعديل هناك. ومع ذلك ، فإن جعله يعمل دون الإعلان عن main يدويًا سيتطلب تعديلًا صغيرًا. نظرًا لعدم اتباع هذه الميزة عن كثب ، لست متأكدًا مما إذا كان هناك حل واحد يناسب الجميع. هل fn main() -> impl Termination ممكن؟

هل fn main () -> الإنهاء الضمني ممكن؟

بمعنى سطحي ، نعم: https://play.rust-lang.org/؟gist=8e353379f77a546d152c9113414a88f7&version=nightly

لسوء الحظ ، أعتقد أن -> impl Trait مزعج بشكل أساسي مع ? بسبب تحويل الخطأ الذي يحمل في ثناياه عوامل ، والذي يحتاج إلى سياق الاستدلال لإخباره بالنوع الذي يجب استخدامه: https: //play.rust- lang.org/؟gist=23410fa4fa684710bc75e16f0714ec4b&version=nightly

أنا شخصياً كنت أتخيل ? -in-doctorests يعمل عبر شيء مثل https://github.com/rust-lang/rfcs/pull/2107 كـ fn main() -> Result<(), Box<Debug>> catch { your_code_here(); } (باستخدام بناء الجملة من https: // github.com/rust-lang/rust/issues/41414#issuecomment-373985777).

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

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

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

أنا قلق من أن هذا مستقر الآن ولكن الحالات البسيطة لا تزال على ما يبدو ICE: https://github.com/rust-lang/rust/issues/48890#issuecomment -375952342

fn main() -> Result<(), &'static str> {
    Err("An error message for you")
}
assertion failed: !substs.has_erasable_regions(), librustc_trans_utils/symbol_names.rs:169:9

https://play.rust-lang.org/؟gist=fe6ae28c67e7d3195a3731839d4aac84&version=nightly

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

frewsxcv أعتقد أن المشاكل تم إصلاحها الآن ، أليس كذلك؟

nikomatsakis المشكلة التي أثرتها في https://github.com/rust-lang/rust/issues/48389 تم حلها في 1.26-beta ، لذا نعم من وجهة نظري.

نعم ، تم إصلاح ICE الذي كنت قلقًا بشأنه الآن!

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

TL؛ DR - أعتقد أن إظهار رسالة Debug لخطأ كان خطأ ، وأن الخيار الأفضل سيكون استخدام رسالة Display لخطأ.

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

من خلال إظهار تمثيل الخطأ Debug افتراضيًا ، أعتقد أيضًا أننا نشجع الممارسة السيئة. على وجه الخصوص ، من الشائع جدًا أثناء كتابة برنامج Rust CLI ملاحظة أنه حتى Display للخطأ ليس جيدًا بما يكفي ليتم استهلاكه من قبل المستخدمين النهائيين ، ويجب القيام بهذا العمل الحقيقي من أجل اصلحه. مثال ملموس على ذلك هو io::Error . إن إظهار io::Error بدون مسار ملف مطابق (بافتراض أنه جاء من قراءة / كتابة / فتح / إنشاء ملف) يعد خطأ في الأساس ، لأنه من الصعب على المستخدم النهائي أن يفعل أي شيء به. باختيار إظهار تمثيل الخطأ Debug افتراضيًا ، فقد جعلنا من الصعب على الأشخاص الذين ينشئون برامج CLI اكتشاف هذه الأنواع من الأخطاء. (علاوة على ذلك ، فإن Debug من io::Error أقل فائدة بكثير من Display ، لكن هذا بحد ذاته ليس نقطة ألم كبيرة في تجربتي. )

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

use std::error::Error;
use std::process;

fn try_main() -> Result<(), Box<Error>> {
    // do stuff with `?`
}

fn main() {
    if let Err(err) = try_main() {
        eprintln!("{}", err);
        process::exit(1);
    }
}

ظاهريًا ، سيكون من _l Lovely_ استبدال هذا بـ ?-in-main ، لكن لا يمكنني ذلك ، لأنه لن يظهر خطأ Display . أي عند كتابة برنامج CLI حقيقي ، سأستخدم في الواقع النهج أعلاه ، لذلك إذا كنت أريد أن تعكس الأمثلة الخاصة بي الواقع ، فأعتقد أنه يجب علي إظهار ما أفعله في البرامج الحقيقية وعدم اتباع طرق مختصرة (إلى حد معقول ). أعتقد في الواقع أن هذا النوع من الأشياء مهم حقًا ، وكان أحد الآثار الجانبية لهذا التاريخ هو أنه أظهر للناس كيفية كتابة كود Rust الاصطلاحي دون رش unwrap في كل مكان. ولكن إذا عدت إلى استخدام ?-in-main في الأمثلة الخاصة بي ، فقد تراجعت للتو عن هدفي: أنا الآن أقوم بإعداد الأشخاص الذين قد لا يعرفون أيًا منهم بشكل أفضل لكتابة البرامج التي ، بشكل افتراضي ، تنبعث منها رسائل خطأ غير مفيدة.

يتم استخدام نمط "إرسال رسالة خطأ والخروج برمز خطأ مناسب" بالفعل في البرامج المصقولة. على سبيل المثال ، إذا استخدم $ ?-in-main Display ، فيمكنني حينئذٍ دمج الدالتين main و run في ripgrep اليوم:

https://github.com/BurntSushi/ripgrep/blob/64317bda9f497d66bbeffa71ae6328601167a5bd/src/main.rs#L56 -L86

بالطبع ، يمكنني استخدام ?-in-main في المستقبل من خلال تقديم الضمانة الخاصة بي للسمة Termination بمجرد أن تستقر ، لكن لماذا أزعج نفسي بفعل ذلك إذا كان بإمكاني كتابة main وظيفة لدي؟ ومع ذلك ، فإن هذا لا يساعد في حل اللغز عند كتابة الأمثلة. سأحتاج إلى تضمين ذلك impl في الأمثلة لجعلها تتطابق مع الواقع ، وفي هذه المرحلة ، قد ألتزم أيضًا بالأمثلة التي لدي اليوم (باستخدام main و a try_main ).

من مظهره ، يبدو أن إصلاح هذا سيكون تغييرًا جذريًا. وهذا يعني أن هذا الرمز يتم تجميعه على Rust Stable اليوم:

#[derive(Debug)]
struct OnlyDebug;

fn main() -> Result<(), OnlyDebug> {
    Err(OnlyDebug)
}

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

أوافق على أن عرض Display على Debug أفضل لتطبيقات CLI. لا أوافق على أنه يجب أن يكون Display بدلاً من Debug ، لأن ذلك يحد بشكل كبير من الأخطاء التي يمكن أن تكون في الواقع ? -ed ويتجاهل الغرض من ?-in-main . بقدر ما أستطيع أن أقول (قد أكون مخطئًا تمامًا لأنني لا أملك الوقت لتجميع stdlib ولا أعرف ما إذا كان التخصص يغطي هذا) لا يوجد سبب يمنعنا من إضافة الضمانة التالية إلى الإنهاء. سيوفر هذا طريقة غير منقطعة لاستخدام Display عندما يكون ذلك متاحًا والعودة إلى Debug عندما لا يكون كذلك.

#[unstable(feature = "termination_trait_lib", issue = "43301")]
impl<E: fmt::Display> Termination for Result<!, E> {
    fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {}", err);
        ExitCode::FAILURE.report()
    }
}

أخيرًا ، لاستكمال حجتي ، أجد صعوبة أيضًا في تصور الظروف التي سأستخدم فيها؟ -in-main حتى في الأمثلة.

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

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

كنت أحاول استخدام هذه الميزة بشكل رئيسي ووجدتها محبطة للغاية. لا تسمح لي الضمانات الحالية لسمة الإنهاء "بتجميع" أنواع متعددة من الأخطاء بشكل ملائم - على سبيل المثال ، لا يمكنني استخدام فشل :: فشل ، لأنه لا ينفذ خطأ ؛ لا يمكنني استخدام Box، نفس السبب. أعتقد أننا يجب أن نعطي الأولوية للتغيير إلى Debug. =)

إذا استخدمنا Display ، فيمكننا تقديم نوع سريع وقذر مثل MainError لتراكم أنواع متعددة من الأخطاء:

pub struct MainError {
    s: String,
}

impl std::fmt::Display for MainError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.s.fmt(f)
    }
}

impl<T> From<T> for MainError where T: std::error::Error {
    fn from(t: T) -> Self {
        MainError {
            s: t.to_string(),
        }
    }
}

سيسمح هذا بشيء مثل أدناه مشابه لـ Box<Error> :

fn main() -> Result<(), MainError> {
    let _ = std::fs::File::open("foo")?;
    Ok(())
}

المناقشة السابقة لـ Display vs Debug موجودة في الجزء المخفي هنا ، بدءًا من https://github.com/rust-lang/rust/issues/43301#issuecomment -362020946.

BurntSushi أتفق معك. إذا تعذر إصلاح ذلك ، فقد يكون هناك حل بديل:

use std::fmt;
struct DisplayAsDebug<T: fmt::Display>(pub T);

impl<T: fmt::Display> Debug for DisplayAsDebug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

impl<T: fmt::Display> From<T> for DisplayAsDebug {
    fn from(val: T) -> Self {
        DisplayAsDebug(val)
    }
}

إذا كان هذا في std::fmt ، فيمكننا استخدام شيء مثل هذا:

use std::{fmt, io};

fn main() -> Result<(), fmt::DisplayAsDebug<io::Error>> {
    let mut file = File::open("/some/file")?;
    // do something with file
}

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

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

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

لكنني أعتبر أن عرض تنفيذ Debug للمستخدم هو خطأ ، وليس أفضل بكثير من مجرد استدعاء unwrap في كل مكان في برنامج CLI. لذا حتى في مثال الكود ، لا يمكنني تخيل استخدام هذا الإصدار من الميزة. هذا سيء للغاية ، لأن إزالة النموذج المعياري من main كان من شأنه أن يبسط التعلم لمطوري Rust الجدد في العمل ، ولن نحتاج إلى شرح quick_main! أو ما يعادله بعد الآن.

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

إذا استخدمنا Display ، فيمكننا تقديم نوع سريع وقذر مثل MainError لتراكم أنواع متعددة من الأخطاء:

شخصيًا ، سيضيف هذا النوع من الحل البديل تعقيدًا كافيًا بحيث يكون من الأسهل تجنب ? -in- main تمامًا.

Aaronepower :

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

إذا كان هذا سيمكنني من كتابة fn main() -> Result<(), failure::Error> والحصول على خطأ لطيف يمكن قراءته باستخدام Display ، فإنه بالتأكيد سوف يرضي مخاوفي الرئيسية. ومع ذلك ، أفترض أن هذا سيترك مسألة عرض التتبع الخلفي اختياريًا في failure::Error عندما يتم تعيين RUST_BACKTRACE=1 - ولكن قد يكون ذلك خارج النطاق ، على أي حال ، أو على الأقل مشكلة يجب يتم تناولها بـ failure .

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

على سبيل المثال ، يمكنك تحديد "الغلاف" الخاص بك لنوع الخطأ ، وتنفيذ From من أجله:

struct PrettyPrintedError { ... }
impl<E: Display> From<E> for PrettyPrintedError { }

impl Debug { /* .. invoke Display .. */ }

يمكنك الآن كتابة شيء مثل هذا ، مما يعني أنه يمكنك استخدام ? في main :

fn main() -> Result<(), PrettyPrintedError> { ... }

ربما يجب أن يكون هذا النوع جزءًا من Quick-cli أو شيء من هذا القبيل؟

nikomatsakis نعم ، لقد فهمت هذا الحل تمامًا ، لكنني أشعر أن هذا يتعارض مع الغرض من إيجاز استخدام ?-in-main . أشعر أن "من الممكن استخدام ?-in-main باستخدام هذا الحل البديل" يقوض ?-in-main نفسه للأسف. على سبيل المثال ، لن أكتب عملك في أمثلة موجزة ولن أفرض تبعية على quicli لكل مثال أكتبه أيضًا. أنا بالتأكيد لن أستخدمه للبرامج السريعة أيضًا ، لأنني أريد الناتج Display لخطأ في كل برنامج CLI أكتبه. رأيي هو أن ناتج تصحيح الخطأ هو خطأ في برامج CLI التي تضعها أمام المستخدمين.

البديل لـ ?-in-main هو دالة إضافية من 4 أسطر. لذلك إذا كان الحل لإصلاح ?-in-main لاستخدام Display أكثر من ذلك بكثير ، فأنا شخصياً لا أرى الكثير من الأسباب لاستخدامه على الإطلاق (خارج اختبارات المستندات أو اختبارات الوحدة ، مثل المذكور أعلاه).

هل هذا شيء يمكن تغييره في الإصدار؟

BurntSushi ما هو شعورك حيال الحل البديل بـ std ، لذلك عليك فقط كتابة بيان نوع الإرجاع أطول قليلاً على main() ؟

Kixunil شعوري الأولي هو أنه قد يكون مستساغًا. لم تعطها الكثير من التفكير رغم ذلك.

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

البديل لـ ?-in-main هو دالة إضافية من 4 أسطر.

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

بعد قولي هذا ، ما زلت أجد الإعداد الحالي لطيفًا جدًا ويفضل أن يكون Display ، لكن أعتقد أن الأمر يتعلق بما تريد أن يكون "الافتراضي" - أي ، يمكن إجراء أي إعداد للعمل مثل أخرى عبر أنواع جديدة مختلفة. لذا فإما أن نفضل بشكل افتراضي البرامج النصية "quick-n-dirty" ، التي تريد Debug ، أو منتجات نهائية مصقولة. أميل إلى الاعتقاد بأن المنتج النهائي المصقول هو المكان الذي يمكنني فيه تحمل تكلفة استيراد إضافية بسهولة كافية ، وأيضًا المكان الذي أرغب في الاختيار من بين العديد من التنسيقات الممكنة المختلفة (على سبيل المثال ، هل أريد الخطأ فقط ، أم أريد تضمين argv [0] ، إلخ).

لكي أكون أكثر واقعية ، أتخيل أنه سيبدو كما يلي:

use failure::format::JustError;

fn main() -> Result<(), JustError> { .. }

أو للحصول على تنسيق مختلف ، ربما أفعل هذا:

use failure::format::ProgramNameAndError;

fn main() -> Result<(), ProgramNameAndError> { .. }

كلاهما يبدو لطيفًا بشكل معقول مقارنة بوظيفة الأسطر الأربعة التي كان عليك كتابتها من قبل:

use std::sys;

fn main() {
  match inner_main() {
    Ok(()) => { }
    Err(error) => {
      println!("{}", error);
      sys::exit(1);
    }
}

fn inner_main() -> Result<(), Error> {
  ...
}

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

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

ملاحظة العملية: حاولت صياغة ملاحظاتي الأولية هنا بـ "هل هناك أي شيء يمكن القيام به" بدلاً من "دعنا نغير شيئًا استقر بالفعل" ، في محاولة للتركيز على الهدف عالي المستوى بدلاً من طلب شيء ما بالتأكيد لا تستطيع أن تفعل. :-)

حسنًا ، لم يستغرق ذلك وقتًا طويلاً: https://crates.io/crates/exitfailure 😆

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

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

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

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

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

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

.unwrap() هو حل سريع وقذر لا يتم إنشاؤه ، لذلك لا يمكنك تضمين الكود داخل وخارج main() أثناء رسم البرنامج.

وبالمقارنة ، فإن ? هو حل سريع وقذر يتم إنشاؤه بالفعل ، وجعله غير سريع وقذر يتعلق بوضع نوع الإرجاع الصحيح على main() ، وليس لتعديل الكود نفسه.

Screwtapello آه ، الآن من المنطقي بالنسبة لي. شكرا!

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

oblitum ليس فقط للاختبار. ليس فقط (افتراضيًا) سيستخدم تنسيق Display . قد يكون هذا هو الناتج الذي تبحث عنه وقد لا يكون ، ولكنه سيكون بالتأكيد أفضل من الناتج .unwrap . مع ذلك ، قد يجعل شفرتك "أجمل" لاستخدام ? . على أي حال ، لا يزال من الممكن تخصيص ناتج أخطاء ? في main كما ذكرnikomatsakis بالتفصيل أعلاه.

هل من الممكن تقديم نوع جديد في stdlib لحالة العرض؟ شيء مثل:

#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use]
struct DisplayResult<E: fmt::Display>(Result<(), E>)

impl<E> Termination for DisplayResult<E> {
    // ... same as Result, but use "{}" instead of "{:?}"
}

impl<E> From<Result<(), E>> for DisplayResult<E> {}
impl<E> Deref<Result<(), E>> for DisplayResult<E> {}
impl<E> Try for DisplayResult<E> {}
// ...

يجب أن يسمح هذا للمستخدمين بكتابة:

fn main() -> DisplayResult<MyError> {
    // Ordinary code; conversions happen automatically via From, Try, etc.
}

الدوافع الرئيسية هنا هي:

  • يتم استخدام العرض عند إرجاع خطأ
  • لا يتعين على المستخدمين القيام بأي التفاف إضافي لأنواع الأخطاء أو النتائج الخاصة بهم ؛ يمكنهم فقط استخدام ? في وظيفتهم الرئيسية.

متابعة: يبدو لي أن دلالات ? و From قد لا تسمح لهذا العمل ضمنيًا كما أريد. أعلم أن ? سيتحول بين أنواع الأخطاء عبر From ، لكني لا أعرف ما إذا كان يفعل نفس الشيء لمنفذين مختلفين Try . أي أنه سيتم التحويل من Result<?, io::Error> إلى Result<?, FromIoError> ، ولكن ليس بالضرورة من Result<?, Err> إلى DisplayResult<Result<?, Err>> . في الأساس ، أنا أبحث عن شيء مثل هذا للعمل:

fn main() -> DisplayResult<io::Error> {
    let f = io::File::open("path_to_file")?;
    let result = String::new();
    f.read_to_string(result)?
}

بافتراض أن هذا لا يعمل ، فربما بدلاً من ذلك يمكن عمل علامة تجميع شرطية بسيطة في Cargo.toml؟

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

أفترض أن المبرمجين الذين يقومون بإرجاع Result<(), E> where E: Error من main مهتمون بطباعة رسالة خطأ إلى وحدة التحكم ، نعم. حتى السلوك الحالي الذي يتم طباعته عبر Debug هو "طباعة رسالة خروج إلى وحدة التحكم".

يبدو أن هناك اتفاقًا واسعًا في هذا الموضوع على أنه يجب طباعة شيء ما إلى stderr ؛ القرار الأساسي الذي يجب اتخاذه هو "هل يجب أن تتم طباعته باستخدام "{}" أو "{:?}" ، أو أي شيء آخر ، وما مدى قابلية التهيئة ، إن وجدت".

Lucretiel ، بالنسبة لي ، فإن قيمة إرجاع Result تتحكم في حالة الخروج بشكل جيد. هل من الأفضل ترك طباعة شيء ما لمعالج الذعر؟ ليس هناك فقط مسألة ما إذا كنت تريد طباعة شيء ما إلى stderr ، وكذلك ما الذي يجب طباعته (backtrace ، أو رسالة الخطأ ، أو ما إلى ذلك؟). راجع https://github.com/rust-lang/rust/issues/43301#issuecomment -389099936 ، خاصةً. الجزء الأخير.

لقد استخدمت هذا مؤخرًا وتمنيت أن يطلق عليه Display . متأكد من أننا أجرينا المكالمة الخاطئة هنا.

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

withoutboats هل فات أوان التغيير؟ لقد لاحظت أن الميزة لا تزال تحمل علامة "تجريبية / ليلية فقط" في المستندات ، ولكن من المحتمل أنني لا أفهم بعض التفاصيل الدقيقة وراء عملية التثبيت.

أود أيضًا أن أرى هذا التغيير وأندم على عدم كوني من أشد المدافعين عنه عندما طلب مني الفريق تغيير التنفيذ من Display إلى Debug .

Lucretiel Termination سمة ليلية ، لكن الميزة نفسها (إرجاع Termination عمليات التنفيذ من main / الاختبارات) مستقرة بالفعل.

هل هناك تذكرة أو جدول زمني لتثبيت اسم السمة؟

xfix هذا يعني أنه من الممكن تغيير التنفيذ ، أليس كذلك؟ لم يتغير السلوك (تم إرجاع Termination من main ) ، لكن السمة نفسها ستتغير من:

impl<E: Debug> Termination for Result<(), E> ...

ل:

impl<E: Display> Termination for Result<(), E> ...

من الممكن تغيير التطبيق ، أليس كذلك؟

ليس بدون كسر التوافق مع الإصدارات السابقة. يتم تجميع هذا الرمز في Rust المستقر اليوم:

#[derive(Debug)]
struct X;

fn main() -> Result<(), X> {
    Ok(())
}

مع التغيير المقترح الذي يتطلب Display ، فإنه سيتوقف عن التجميع.

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

trait ResultTerm {
    fn which(&self);
}
impl<T: Debug> ResultTerm for T {
    default fn which(&self) {
        println!("{:?}", self)
    }
}
impl<T: Debug + Display> ResultTerm for T {
    fn which(&self) {
        println!("{}", self)
    }
}

enum MyResult<T, E> {
    Ok(T),
    Err(E),
}

impl<T, E> Termination for MyResult<T, E>
where
    E: ResultTerm,
{
    fn report(self) -> i32 {
        match self {
            MyResult::Err(e) => {
                e.which();
                1
            }
            _ => 0,
        }
    }
}

ملعب

قد يكون هذا كافيًا لتغطية جميع الحالات الشائعة ، لكنه يكشف التخصص علنًا.

آه ، أرى ما تعنيه بشأن الصدأ المستقر. لسبب ما لم أكن أعلم أن هذا كان متاحًا في مستقر.

هل هذا شيء يمكن تبديله في وقت التحويل بعلامة الشحن؟ بنفس الطريقة التي يمكن بها تبديل الأشياء الأخرى ، مثل panic = "abort" .

يمكن؟ يمكنني أن أرى أنه من الممكن أن يكون لديك سلوك مختلف لـ ? في main في إصدار Rust آخر. ربما ليس 2018 ولكن ربما 2021؟

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

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

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

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

أما بالنسبة للنقطة 3 ، فهو أمر غريب بعض الشيء (ربما كان علينا أن نعيد trait Display: Debug مرة أخرى في 1.0؟) ، ولكن إذا واجه شخص ما مشكلة في كتابة عرض ضمني ، فليس من الضروري أن نطلب منه الصفع مشتق واحد (أظن أنه من المحتمل أن يكون لديهم على أي حال ... هل هناك أي شخص يعترض من حيث المبدأ على وجود تصحيح في نوع العرض؟).

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

الحالة التي يكون فيها Display مفضلًا على Debug في البرامج النصية السريعة تستخدم &'static str أو String كنوع الخطأ. إذا كان لديك نوع خطأ مخصص على الإطلاق لصفع #[derive(Debug)] على أنك تنفق بالفعل بعض الطاقة غير التافهة في معالجة الأخطاء.

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

ما تم تنسيقه ليس القيمة Result<_, E> ، فقط القيمة E بالداخل. لذلك لن ترى Err( في الإخراج. ومع ذلك ، فإن الكود الحالي يضيف بادئة Error: ، والتي من المفترض أن تبقى إذا تم استخدام Display . بالإضافة إلى عدم طباعة عروض الأسعار ، فإن Display لن يهرب أيضًا بالخط المائل العكسي من محتويات السلسلة التي تعتبر IMO مرغوبة عندما يتعلق الأمر بإظهار رسالة (خطأ) للمستخدمين.

https://github.com/rust-lang/rust/blob/cb6eeddd4dcefa4b71bb4b6bb087d05ad8e82145/src/libstd/process.rs#L1527 -L1533

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

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

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

use std::cell::Cell;
use std::fmt::*;

struct Foo<'a, 'b> {
     a: &'a Cell<&'a i32>,
     b: &'b i32,
}

impl<'a, 'b> Debug for Foo<'a, 'b> {
    fn fmt(&self, _: &mut Formatter) -> Result {
        Ok(())
    }
}

impl<'a> Display for Foo<'a, 'a> {
    fn fmt(&self, _: &mut Formatter) -> Result {
        self.a.set(self.b);
        Ok(())
    }
}

نظرًا للطريقة التي يعمل بها التخصص حاليًا ، يمكنني الاتصال بـ report على Foo<'a, 'b> حيث 'b لا يتجاوز 'a ، ومن الممكن حاليًا تمامًا أن يكون المترجم سيحدد الضمني الذي يشير إلى أن المكالمات إلى مثيل Display على الرغم من عدم تلبية متطلبات العمر ، مما يسمح للمستخدم بتمديد عمر المرجع بشكل غير صالح.

لا أعرف شيئًا عنكم يا رفاق ، لكن عندما أكتب "نصوصًا" سريعة ، فأنا فقط unwrap() الهراء من كل شيء. إنه أفضل 100 مرة ، لأنه يحتوي على آثار خلفية توفر لي معلومات سياقية. بدونها ، في حالة وجود let ... = File::open() أكثر من مرة في الكود الخاص بي ، لا يمكنني تحديد الرمز الذي فشل.

لا أعرف شيئًا عنكم يا رفاق ، لكن عندما أكتب "نصوصًا" سريعة ، فأنا فقط unwrap() الهراء من كل شيء. إنه أفضل 100 مرة ، لأنه يحتوي على آثار خلفية توفر لي معلومات سياقية. بدونها ، في حالة وجود let ... = File::open() أكثر من مرة في الكود الخاص بي ، لا يمكنني تحديد الرمز الذي فشل.

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

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

أعتقد أنه يمكن وضع علامة "تنفيذ RFC" ، أليس كذلك؟ على الأقل حسب هذا ! :ابتسامة:

اعذرني إذا كان هذا جاهلاً تمامًا ، لكن لا يمكنك استخدام التخصص للإبلاغ عن سلسلة الأخطاء عندما يكون ذلك ممكنًا:

use std::error::Error;

impl<E: fmt::Debug> Termination for Result<!, E> {
    default fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {:?}", err);
        ExitCode::FAILURE.report()
    }
}

impl<E: fmt::Debug + Error> Termination for Result<!, E> {
    fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {:?}", err);

        for cause in Error::chain(&err).skip(1) {
            eprintln!("Caused by: {:?}", cause);
        }
        ExitCode::FAILURE.report()
    }
}

https://github.com/rust-lang/rfcs/blob/f4b8b61a414298ba0f76d9b786d58ccdc34a44bb/text/1937-ques-in-main.md#L260 -L270

impl<T: Termination, E: Display> Termination for Result<T, E> {
    fn report(self) -> i32 {
        match self {
            Ok(val) => val.report(),
            Err(ref err) => {
                print_diagnostics_for_error(err);
                EXIT_FAILURE
            }
        }
    }
}

هل هناك سبب لعدم تنفيذ هذا الجزء من RFC؟

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

ما هي حالة هذه الميزة؟ هل هناك اقتراح لتحقيق الاستقرار؟

GrayJack يجب إغلاق هذه المشكلة ، حيث هبطت منذ فترة طويلة.

صحيح ، وقد أصبت بالحيرة قليلاً ، لقد وصلت هنا من الرابط في السمة Termination وكنت أسأل عن ذلك ، هل هناك مشكلة مناسبة لـ termination_trait_lib ؟

يجب إغلاق هذه القضية

لا تزال هذه المشكلة مفتوحة لتتبع استقرار Termination .

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات