Rust: مشكلة تتبع `ops :: Try` (ميزة` try_trait`)

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

السمة Try من https://github.com/rust-lang/rfcs/pull/1859؛ تم تنفيذه في العلاقات العامة https://github.com/rust-lang/rust/pull/42275.

انفصل عن https://github.com/rust-lang/rust/issues/31436 من أجل الوضوح (لكل https://github.com/rust-lang/rust/pull/42275#discussion_r119167966)

  • [] سيؤدي تثبيت هذا إلى السماح للأشخاص بتنفيذ Iterator::try_fold

    • [] كجزء من التثبيت ، أعد فتح PR # 62606 لتوثيق تنفيذ try_fold للمكررات

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

A-error-handling B-RFC-implemented B-unstable C-tracking-issue Libs-Tracked T-lang T-libs

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

ما هو الوضع الحالي لهذه الميزة؟

ال 99 كومينتر

قطعتان من الدرّاجات:

  • هل لدينا دافع خاص لاستدعاء النوع المرتبط Error بدلاً من Err ؟ تسميته Err سيجعله يتماشى مع Result : الآخر يسمى بالفعل Ok .

  • هل لدينا دافع خاص لوجود طرق منفصلة عن from_error و from_ok ، بدلاً من from_result والتي ستكون أكثر تناسقًا مع into_result وهو الآخر نصف السمة؟

( نسخة محدثة من رابط الملعب من RFC )

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

  • تمت مناقشة Error مقابل Err فيما يتعلق بـ TryFrom في https://github.com/rust-lang/rust/issues/33417#issuecomment -269108968 و https: // github.com/rust-lang/rust/pull/40281 ؛ أفترض أن الاسم تم اختياره هنا لأسباب مماثلة.
  • أعتقد أنهما منفصلان لأن لهما استخدامات مختلفة ، وأتوقع أنه من النادر أن يكون لدى شخص ما Result يحاول تحويله إلى T:Try . أفضل Try::from_ok و Try::from_error للاتصال دائمًا بـ Try::from_result(Ok( و Try::from_result(Err( ، ويسعدني فقط تضمين الطريقتين عند كتابة المطابقة. ربما هذا لأنني أعتقد أن into_result ليس كـ Into<Result> ، ولكن "هل نجحت أم فشلت؟" ، مع النوع المحدد هو Result كتفاصيل تنفيذ غير مهمة. (لا أريد اقتراح أو إعادة فتح "يجب أن يكون هناك نوع جديد لقيمة الإنتاج مقابل العائد المبكر ، على الرغم من ذلك). وبالنسبة للوثائق ، أحب أن يتحدث from_error عن ? (أو أخيرًا throw ) ، بينما يتحدث from_ok عن التفاف النجاح (# 41414) ، بدلاً من جعلهما كليهما بنفس الطريقة.

لست متأكدًا مما إذا كان هذا هو المنتدى الصحيح لهذا التعليق ، فيرجى إعادة توجيهي إذا لم يكن كذلك: smiley :. ربما كان يجب أن يكون هذا على https://github.com/rust-lang/rfcs/pull/1859 وقد فاتني فترة التعليق ؛ وجه الفتاة!


كنت أتساءل عما إذا كانت هناك قضية لتقسيم السمة Try أو إزالة طريقة into_result ؛ بالنسبة لي حاليًا Try يشبه إلى حد ما مجموع السمات FromResult (يحتوي على from_error و from_ok ) و IntoResult (يحتوي على into_result ).

يتيح FromResult الخروج المبكر المريح للغاية مع عامل التشغيل ? ، والذي أعتقد أنه حالة الاستخدام القاتلة لهذه الميزة. أعتقد أنه يمكن بالفعل تنفيذ IntoResult بدقة باستخدام طرق كل حالة استخدام أو كـ Into<Result> ؛ هل أفتقد بعض الأمثلة المفيدة؟

باتباع سمة RFC Try ، يمكن أن يكون expr? النحو التالي:

match expr { // Removed `Try::into_result()` here.
    Ok(v) => v,
    Err(e) => return Try::from_error(From::from(e)),
}

الأمثلة المحفزة التي أخذتها في الاعتبار هي Future و Option .

مستقبل

يمكننا تنفيذ FromResult بشكل طبيعي مقابل struct FutureResult: Future النحو التالي:

impl<T, E> FromResult for FutureResult {
    type Ok = T;
    type Error = E;
    fn from_error(v: Self::Error) -> Self {
        future::err(v)
    }
    fn from_ok(v: Self::Ok) -> Self {
        future::ok(v)
    }
}

بافتراض تنفيذ معقول لـ ? والذي سيعود من دالة قيمة impl Future عند تطبيقه على Result::Err ثم يمكنني كتابة:

fn async_stuff() -> impl Future<V, E> {
    let t = fetch_t();
    t.and_then(|t_val| {
        let u: Result<U, E> = calc(t_val);
        async_2(u?)
    })
}
fn fetch_t() -> impl Future<T, E> {}
fn calc(t: T) -> Result<U,E> {}

هذا هو بالضبط ما كنت أحاول تنفيذه في وقت سابق اليوم و Try أزاله! ولكن إذا حاولنا تطبيق Try لـ Future فربما لا يوجد خيار أساسي لـ into_result ؛ قد يكون من المفيد الذعر أو الحظر أو الاستطلاع مرة واحدة ، ولكن لا يبدو أن أيًا من ذلك مفيد عالميًا. إذا لم يكن هناك into_result على Try يمكنني تنفيذ الخروج المبكر على النحو الوارد أعلاه ، وإذا كنت بحاجة إلى تحويل Future إلى Result (ومن ثم إلى أي Try ) يمكنني تحويله بطريقة مناسبة (استدعاء طريقة wait للحظر ، استدعاء بعض poll_once() -> Result<T,E> ، إلخ).

خيار

Option حالة مماثلة. نطبق from_ok ، from_err بشكل طبيعي كما في RFC Try ، ويمكن تحويل Option<T> إلى Result<T, Missing> ببساطة بـ o.ok_or(Missing) أو طريقة ملائمة ok_or_missing .


امل ان يساعد!

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

fn fun() -> SearchResult<Socks> {
    search_drawer()?;
    search_wardrobe()
}

ولكن في هذه الحالة ، فإن تسمية طرق السمات Try غير مناسبة.

ومع ذلك ، فإنه يوسع معنى عامل التشغيل ? .

في النماذج الأولية لـ try_fold للحرير الصناعي ، وجدت نفسي أريد شيئًا مثل Try::is_error(&self) Consumer::full() لطرق Result<T::Ok, T::Err> لذلك يمكنني الاتصال بـ Result::is_err() .

من هناك تمنيت أيضًا أن يعود Try::from_result إلى T في النهاية ، لكن تعيين match إلى T::from_ok و T::from_error ليس _too_ سيئا.

و Try سمة يمكن أن توفر from_result طريقة لبيئة العمل إذا from_ok و from_error مطلوبة. أو العكس. من الأمثلة المقدمة أرى حالة لتقديم كليهما.

منذ أن تم دمج PR # 42275 ، هل هذا يعني أنه تم حل هذه المشكلة؟

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

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

ErichDonGubler هذه مشكلة تتبع ، لذا لا يتم حلها حتى تصبح الشفرة المقابلة مستقرة.

تقرير الخبرة:

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

مثال:

في الحل الجديد لـ Chalk VM ، كنت أرغب في الحصول على تعداد يشير إلى نتيجة حل "حبلا". كان لهذا أربعة احتمالات:

enum StrandFail<T> {
    Success(T),
    NoSolution,
    QuantumExceeded,
    Cycle(Strand, Minimums),
}

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

enum StrandFail {
    NoSolution,
    QuantumExceeded,
    Cycle(Strand, Minimums),
}

ولكن بمجرد حصولي على هذا النوع ، فقد أجعل أيضًا StrandResult<T> اسمًا مستعارًا:

type StrandResult<T> = Result<T, StrandFail>;

وهذا ما فعلته.

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

    /// Invoked when a strand represents an **answer**. This means
    /// that the strand has no subgoals left. There are two possibilities:
    ///
    /// - the strand may represent an answer we have already found; in
    ///   that case, we can return `StrandFail::NoSolution`, as this
    ///   strand led nowhere of interest.
    /// - the strand may represent a new answer, in which case it is
    ///   added to the table and `Ok` is returned.

لاحظ أنني لم أقل "نعيد Err(StrandFail::NoSolution) . هذا لأن Err يبدو وكأنه قطعة أثرية مزعجة يجب أن أضيفها.

(من ناحية أخرى ، سيساعد التعريف الحالي القراء على معرفة سلوك ? بدون استشارة الضمني Try .)

أعتقد أن هذه النتيجة ليست مفاجئة: فالضمانة الحالية Try تجبرك على استخدام ? على الأشياء المتشابهة إلى Result . هذا التوحيد ليس من قبيل الصدفة ، ولكن نتيجة لذلك ، فإنه يجعل من المزعج استخدام ? مع أشياء ليست في الأساس "مجرد Result ". (بالنسبة لهذه المسألة ، فهي في الأساس نفس المشكلة التي تؤدي إلى ظهور NoneError - الحاجة إلى تحديد نوع بشكل مصطنع لتمثيل "فشل" Option .)

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

هل تم عرض / استخدام النوع Try::Error المرتبط من قبل المستخدم مباشرة؟ أم أنه مطلوب فقط كجزء من عملية إزالة الصغر من ? ؟ إذا كان الأخير ، لا أرى أي مشكلة حقيقية في مجرد تعريفه "هيكليًا" - type Error = Option<Option<(Strand, Minimums)>> أو أيًا كان. (لا يعد الاضطرار إلى اكتشاف المعادل البنيوي لـ "نصف الفشل" لتعريف enum أمرًا رائعًا ، ولكنه يبدو أقل إزعاجًا من الاضطرار إلى إعادة تنظيم واجهة برمجة التطبيقات العامة بالكامل.)

أنا لا أتبع أيضًا. لقد نجحت في تنفيذ المحاولة لنوع يحتوي على تمثيل خطأ داخلي وشعرت أنه من الطبيعي أن يكون Ok و Error من نفس النوع. يمكنك مشاهدة التنفيذ هنا: https://github.com/kevincox/ecl/blob/8ca7ad2bc4775c5cfc8eb9f4309b2666e5163e02/src/lib.rs#L298 -L308

في الواقع يبدو أن هناك نمطًا بسيطًا إلى حد ما لإنجاح هذا العمل.

impl std::ops::Try for Value {
    type Ok = Self;
    type Error = Self;

    fn from_ok(v: Self::Ok) -> Self { v }
    fn from_error(v: Self::Error) -> Self { v }
    fn into_result(self) -> Result<Self::Ok, Self::Error> {
        if self.is_ok() { Ok(val) } else { Err(val) }
    }
}

إذا كنت تريد التخلص من النجاح ، فيجب أن ينجح شيء كهذا:

impl std::ops::Try for StrandFail<T> {
    type Ok = T;
    type Error = Self;

    fn from_ok(v: Self::Ok) -> Self { StrandFail::Success(v) }
    fn from_error(v: Self::Error) -> Self { v }
    fn into_result(self) -> Result<Self::Ok, Self::Error> {
        match self {
            StrandFail::Success(v) => Ok(v),
            other => Err(other),
        }
    }
}

تحديد النوع الهيكلي أمر ممكن ولكنه أمر مزعج للغاية. أوافق على أنه يمكنني استخدام Self . يبدو الأمر غريبًا بالنسبة لي على الرغم من أنه يمكنك بعد ذلك استخدام ? للتحويل من StrandResult إلى Result<_, StrandResult> إلخ.

تقرير تجربة رائعة ،nikomatsakis! لقد كنت أيضًا غير راضٍ عن Try (من الاتجاه الآخر).

TL / DR : أعتقد أن FoldWhile حصل على هذا الأمر بشكل صحيح ، ويجب علينا مضاعفة التفسير Break -vs- Continue ? بدلاً من الحديث عنه من حيث الأخطاء.

طويل:

أستمر في استخدام Err لشيء أقرب إلى "النجاح" من "الخطأ" لأن ? مناسب جدًا.

  • عند استخدام try_fold لتنفيذ position (ومجموعة من طرق التكرار الأخرى) ، شعرت بالحيرة التي كانت عند استخدام Result (لم يعجب عقلي find العثور على الشيء على أنه Err ) أنشأت تعدادي الخاص

  • عند كتابة tree_fold1 في أدوات itertools ، انتهى بي الأمر بـ match غريب None هو "خطأ" من Iterator::next() ، لكنه في الوقت نفسه "نجاح" من fold ، نظرًا لأنك بحاجة للوصول إلى النهاية أن يتم. ومن الملائم جدًا استخدام ? (حسنًا ، try! في هذا الكود نظرًا لأنه يحتاج إلى تجميع على Rust 1.12) للتعامل مع نشر الحالة النهائية.

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

الشيء الآخر الذي كنت أفكر فيه هو أننا يجب أن نفكر في بعض الضمانات المجنونة قليلاً مقابل Try . على سبيل المثال ، يمكن أن يسمح Ordering: Try<Ok = (), Error = GreaterOrLess> ، مع "وظائف try" ، بتفعيل cmp مقابل struct Foo<T, U> { a: T, b: U }

fn cmp(&self, other: &self) -> Ordering try {
    self.a.cmp(&other.a)?;
    self.b.cmp(&other.b)?;
}

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

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

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

تحرير 2: هذا لا يختلف عن https://github.com/rust-lang/rust/issues/42327#issuecomment -318923393 أعلاه

تحرير 3: يبدو أنه تم اقتراح متغير Continue أيضًا في https://github.com/rust-lang/rfcs/pull/1859#issuecomment -273985250

سنتى:

أحب اقتراحfluffysquirrels بتقسيم السمة إلى into_result أو ما يعادله كجزء من عملية الإزالة. وأعتقد في هذه المرحلة أنه يتعين علينا استخدام Option حيث استقر Try .

تعجبني أيضًا فكرة scottmcm في استخدام الأسماء التي تقترح كسر / متابعة بدلاً من الخطأ / حسنًا.

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

https://github.com/rust-lang/rust/blob/ab8b961677ac5c74762dcea955aa0ff4d7fe4915/src/libcore/iter/iterator.rs#L1738 -L1746

إنها بالطبع ليست جميلة تمامًا مثل الحلقة المستقيمة ، ولكن مع "طرق التجربة" ستكون قريبة:

self.try_for_each(move |x| try { 
    if predicate(&x) { return LoopState::Break(x) } 
}).break_value() 

للمقارنة ، أجد نسخة "مفردات الخطأ" مضللة حقًا:

self.try_for_each(move |x| { 
    if predicate(&x) { Err(x) } 
    else { Ok(()) } 
}).err() 

هل يمكننا تنفيذ العرض لـ NoneError؟ سيسمح لصندوق الفشل باشتقاق From<NoneError> for failure::Error تلقائيًا. راجع https://github.com/rust-lang-nursery/failure/issues/61
يجب أن يكون تغييرًا مكونًا من 3 أسطر ، لكنني لست متأكدًا من عملية RFCs وما شابه.

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

تضمين التغريدة أرى وجهة نظرك. أرغب في النهاية في طريقة نظيفة لتلوين نمط إرجاع Err عندما ترجع وظيفة المكتبة بلا. ربما تعرف واحدًا آخر غير Try ؟ مثال:

fn work_with_optional_types(pb: &PathBuf) -> Result<MyStruct, Error> {
    if let Some(filestem) = pb.file_stem() {
        if let Some(filestr) = filestem.to_str() {
            return Ok(MyStruct {
                filename: filestr.to_string()
            });
        }
     }
    Err(_)
}

بمجرد أن وجدت هذه الميزة التجريبية والصندوق failure ، انجذبت بشكل طبيعي إلى:

use failure::Error;
fn work_with_optional_types(pb: &PathBuf) -> Result<MyStruct, Error> {
    Ok({
        title: pb.file_stem?.to_str()?.to_string()
    })
}

الذي _ تقريبًا_ يعمل ، باستثناء عدم وجود impl Display for NoneError كما ذكرت من قبل.
ولكن ، إذا لم تكن هذه هي الصيغة التي نرغب في اتباعها ، فربما تكون هناك وظيفة / ماكرو آخر يبسط النمط:

if option.is_none() {
    return Err(_);
}

@ cowang4 أعتقد أن هذا From<NoneError> مقابل failure::Error ، والذي استخدم النوع الخاص بك الذي نفذ Display .

ومع ذلك ، ربما يكون من الأفضل استخدام opt.ok_or(_)? حتى تتمكن من تحديد الخطأ الذي يجب أن يكون عليه الخيار بوضوح إذا كان الخيار لا شيء. في المثال الخاص بك ، على سبيل المثال ، قد تريد خطأ مختلفًا إذا كان pb.file_stem لا شيء مما إذا كان to_str() يُرجع بلا.

tmccombs لقد حاولت إنشاء نوع الخطأ الخاص بي ، لكن

#[macro_use] extern crate failure_derive;

#[derive(Fail, Debug)]
#[fail(display = "An error occurred.")]
struct SiteError;

impl From<std::option::NoneError> for SiteError {
    fn from(_err: std::option::NoneError) -> Self {
        SiteError
    }
}

fn build_piece(cur_dir: &PathBuf, piece: &PathBuf) -> Result<Piece, SiteError> {
    let title: String = piece
        .file_stem()?
        .to_str()?
        .to_string();
    Ok(Piece {
        title: title,
        url: piece
            .strip_prefix(cur_dir)?
            .to_str()
            .ok_or(err_msg("tostr"))?
            .to_string(),
    })
}

ثم حاولت استخدام نوع الخطأ الخاص بي ...

error[E0277]: the trait bound `SiteError: std::convert::From<std::path::StripPrefixError>` is not satisfied
   --> src/main.rs:195:14
    |
195 |           url: piece
    |  ______________^
196 | |             .strip_prefix(cur_dir)?
    | |___________________________________^ the trait `std::convert::From<std::path::StripPrefixError>` is not implemented for `SiteError`
    |
    = help: the following implementations were found:
              <SiteError as std::convert::From<std::option::NoneError>>
    = note: required by `std::convert::From::from`

error[E0277]: the trait bound `SiteError: std::convert::From<failure::Error>` is not satisfied
   --> src/main.rs:195:14
    |
195 |           url: piece
    |  ______________^
196 | |             .strip_prefix(cur_dir)?
197 | |             .to_str()
198 | |             .ok_or(err_msg("tostr"))?
    | |_____________________________________^ the trait `std::convert::From<failure::Error>` is not implemented for `SiteError`
    |
    = help: the following implementations were found:
              <SiteError as std::convert::From<std::option::NoneError>>
    = note: required by `std::convert::From::from`

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

impl<E: failure::Fail> From<E> for SiteError {
    fn from(_err: E) -> Self {
        SiteError
    }
}

لا...

error[E0119]: conflicting implementations of trait `std::convert::From<SiteError>` for type `SiteError`:
   --> src/main.rs:183:1
    |
183 | impl<E: failure::Fail> From<E> for SiteError {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: conflicting implementation in crate `core`:
            - impl<T> std::convert::From<T> for T;

حسنًا ، ماذا عن std::error::Error ؟

impl<E: std::error::Error> From<E> for SiteError {
    fn from(_err: E) -> Self {
        SiteError
    }
}

هذا لا يعمل أيضًا. جزئيًا لأنه يتعارض مع From<NoneError>

error[E0119]: conflicting implementations of trait `std::convert::From<std::option::NoneError>` for type `SiteError`:
   --> src/main.rs:181:1
    |
175 | impl From<std::option::NoneError> for SiteError {
    | ----------------------------------------------- first implementation here
...
181 | impl<E: std::error::Error> From<E> for SiteError {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `SiteError`
    |
    = note: upstream crates may add new impl of trait `std::error::Error` for type `std::option::NoneError` in future versions

وهو أمر غريب لأنني اعتقدت أن NoneError لم يطبق std::error::Error . عندما أعلق على حسابي غير العام impl From<NoneError> أحصل على:

error[E0277]: the trait bound `std::option::NoneError: std::error::Error` is not satisfied
   --> src/main.rs:189:25
    |
189 |       let title: String = piece
    |  _________________________^
190 | |         .file_stem()?
    | |_____________________^ the trait `std::error::Error` is not implemented for `std::option::NoneError`
    |
    = note: required because of the requirements on the impl of `std::convert::From<std::option::NoneError>` for `SiteError`
    = note: required by `std::convert::From::from`

هل يجب علي كتابة كل From s يدويًا. أعتقد أن صندوق الفشل كان من المفترض أن يشتقهم؟

ربما يجب أن ألتزم بـ option.ok_or()

هل يجب علي كتابة جميع العناصر يدويًا. أعتقد أن صندوق الفشل كان من المفترض أن يشتقهم؟

لا أعتقد أن صندوق الفشل يفعل ذلك. ولكنني يمكن أن تكون خاطئة.

حسنًا ، لقد أعدت فحص صندوق الفشل ، وإذا كنت أقرأ الوثائق والإصدارات المختلفة بشكل صحيح ، فقد تم تصميمه دائمًا لاستخدام failure::Error كنوع الخطأ في Result s ، انظر هنا . وهو يطبق سمة impl Fail لمعظم أنواع الأخطاء:

impl<E: StdError + Send + Sync + 'static> Fail for E {}

https://github.com/rust-lang-nursery/failure/blob/master/failure-1.X/src/lib.rs#L218

ثم impl From بحيث يمكن Try / ? أخطاء أخرى (مثل الأخطاء من الأمراض المنقولة جنسياً) إلى النوع failure::Error الشامل.
rust impl<F: Fail> From<F> for ErrorImpl
https://github.com/rust-lang-nursery/failure/blob/d60e750fa0165e9c5779454f47a6ce5b3aa426a3/failure-1.X/src/error/error_impl.rs#L16

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

شكرا لكم جميعا على المساعدة. أنا أتعلم ببطء Rust! :ابتسامة:

سألتزم بتقنيات التخلص من السكر التي تعلمتها.

: +1: بصراحة ، أعتقد أن .ok_or(...)? سيظل الطريق للذهاب (أو .ok_or_else(|| ...)? بالطبع). حتى لو كان NoneError _had_ a Display ضمنيًا ، ماذا سيقول؟ "شيء ما لم يكن هناك"؟ هذا ليس خطأ كبير ...

محاولة الاقتراب من اقتراح ملموس ...

لقد بدأت في الإعجاب بـ TrySuccess البديل. ومن المثير للاهتمام ، أعتقد أن _لا أحد _ ، بمن فيهم أنا ، أحب ذلك الشخص في الأصل - إنه ليس حتى في النسخة النهائية من RFC. لكن لحسن الحظ أنها تعيش في التاريخ: https://github.com/rust-lang/rfcs/blob/f89568b1fe5db4d01c4668e0d334d4a5abb023d8/text/0000-try-trait.md#using -an-related-type-for-the-Success -القيمة

أشعر أن أكبر اعتراض على ذلك كان الشكوى المعقولة من أن سمة أساسية كاملة لنوع مرتبط فقط ( trait TrySuccess { type Success; } ) كانت مبالغة. ولكن مع عودة الكتل catch try (https://github.com/rust-lang/rust/issues/41414#issuecomment-373985777) للقيام بالتغليف الجيد (كما في RFC) وفجأة أصبح لها استخدام مباشر ومهم: هذه هي السمة التي تتحكم في هذا الالتفاف. إلى جانب الهدف " ? يجب أن ينتج دائمًا نفس النوع" الذي أعتقد أنه مرغوب فيه بشكل عام ، يبدو أن السمة تحمل وزنها بشكل أفضل. رجل القش:

trait TryContinue {
    type Continue;
    fn from_continue(_: Self::Continue) -> Self;
}

هذا النوع المرتبط ، باعتباره النوع الذي سيتم إرجاعه من عامل التشغيل ? ، هو أيضًا النوع الذي يجب أن يكون موجودًا بوضوح. هذا يعني أنه لا يضرب "يجب أن يحدد نوعًا من النوع" المتبقي "الذي عبر عنه نيكو . وكونها () أمر معقول ، بل شائع ، لذلك يتجنب الالتواءات المشابهة لـ NoneError .

تحرير: بالنسبة إلى الدراجين ، قد تكون كلمة "العودة" كلمة جيدة ، لأن هذا ما يحدث عندما تحصل على return من طريقة try (تُعرف أيضًا باسم التفاف جيد). return هو أيضًا اسم مشغل monad لهذا ، iiuc ...

تحرير 2: أفكاري حول السمة (السمات) / الطريقة (الطرق) الأخرى لم تستقر بعد ،tmccombs.

scottmcm فقط السمتين التاليتين؟

trait TryContinue {
  type Continue;
  fn from_continue(_: Self::Continue) -> Self;
}

trait Try<E>: TryContinue {
  fn try(self) -> Result<Self::Continue, E>
}

و desugging x? سيبدو مثل:

x.try() match {
    Ok(c) => c,
   Err(e) => throw e // throw here is just a placeholder for either returning or breaking out of a try block
}

و try { ...; expr} ستنتقل إلى شيء مثل:

{ 
    ...
    TryContinue::from_continue(expr);
}

scottmcm أنا أيضًا أجد أن هذا البديل أكثر جاذبية عند التفكير في تغليف جيد =)

بالانتقال إلى السمة التالية ، أعتقد أن السمة التي تساوي ? تصبح هذه (modulo الضخمة للدراجات الهوائية):

trait Try<Other: TryContinue = Self>: TryContinue + Sized {
    fn check(x: Other) -> ControlFlow<Other::Continue, Self>;
}

enum ControlFlow<C, B> {
    Continue(C),
    Break(B),
}

تبرير متنوع:

  • يتطلب هذا TryContinue لنوع الوسيطة الخاصة به بحيث يكون نوع التعبير x? هو نفسه دائمًا
  • يؤدي هذا إلى تعيين معلمة النوع افتراضيًا على Self للحالات البسيطة مثل try_fold التي تقوم بفحص نفس النوع وإرجاعه ، لذلك لا بأس من استخدام T: Try .
  • تمتد هذه السمة TryContinue لأنه إذا كان النوع المستخدم كنوع إرجاع يسمح ? في جسمه ، فيجب أن يدعم أيضًا التغليف الجيد.
  • يعتبر التعداد الجديد متماثلًا للنتيجة ، ولكنه يتجنب الحديث من حيث "الأخطاء" كما تمت مناقشته أعلاه ، ونتيجة لذلك أعتقد أنه يوفر معنى واضحًا جدًا لكل من المتغيرات.
  • الوسيطة لـ ? هي النوع العام ، لذا ، مثل TryContinue ، فإنها "تنتج Self من شيء ما"

العرض التوضيحي الكامل لإثبات المفهوم ، بما في ذلك وحدات الماكرو لـ try{} / ? و تضمين لـ Option و Result و Ordering : https : //play.rust-lang.org/؟ gist = 18663b73b6f35870d20fd172643a4f96 & version = مستقر (شكرًا nikomatsakis لعمل النسخة الأصلية من هذه قبل عام 🙂)

سلبيات:

  • لا يزال يحتوي على معلمات النوع الوهمي في Result ضمنيًا لا يعجبني حقًا

    • ولكن ربما تكون هذه مقايضة جيدة لعدم الحاجة إلى نوع إضافي LessOrGreater في ضمانة Ordering .

    • ومعلمات النوع الوهمي في الضمانات أقل صعوبة من الأنواع على أي حال

  • لا يعطي تحويل ثابت From

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

  • ليس من الواضح ما سيكون عليه بناء الجملة throw

    • الذي يفقد تفسير ? كـ Ok(x) => x, Err(r) => throw e.into() الذي أعجبني حقًا

    • ولكن يمكن أيضًا السماح لـ throw; أن يكون وسيلة لإنتاج None (عبر شيء مثل impl<T> Throw<()> for Option<T> ) ، وهو أفضل بكثير من throw NoneError;

    • وقد يكون فصل ذلك جيدًا على أي حال ، نظرًا لأن throw LessOrGreater::Less كان سيبدو سخيفًا.

تحرير: Pingglaebhoerl ، الذي أرغب في رأيه في هذا كمشارك كبير في RFC 1859.

تحرير 2: أيضًا cc @ colin-kiegel ، لهذا البيان من https://github.com/rust-lang/rfcs/pull/1859#issuecomment -287402652:

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

أنا حقا أحب هذا الاقتراح.

ليس من الواضح كيف سيكون بناء الجملة

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

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

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

على الرغم من أنها تأتي مع أمتعة مماثلة ، إلا أنني أظن أن "الزيادة" هي الأفضل:

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

رفع (ت): للانتقال إلى مركز أعلى ؛ ارفع؛ رفع

قد تكون هناك طريقة لدمج "زيادة" مع منطق ? ، نظرًا لأن الزيادة يمكن أن تعني أيضًا "جمع". شيء من هذا القبيل: Ok(v) => v, Err(e) => raise From::from(e) ، حيث raise يحاكي النمط المطابق (على سبيل المثال ، عند إعطاء نمط Err(e) ، فهو سحر نحوي لـ ControlFlow::Break(Err(...)) ).

أظن أن "الزيادة" هي الأفضل

نقطة جيدة

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

ليس من الواضح كيف سيكون بناء الجملة

هل هناك سبب لعدم امتلاكنا from_err(value: Other) ؟

تحديث: ربما أكون في حيرة من أمري بشأن دور Other ، في الواقع. لا بد لي من دراسة هذه السمة أكثر قليلا. =)

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

هل هناك سبب لعدم امتلاكنا from_err(value: Other) ؟

حسنًا ، Other هو نوع ? كامل (مثل Result ) ، لذلك لا أريد throw Ok(4) للعمل. (ويجب أن يكون الفصل بالكامل لتجنب فرض إدخال نوع خطأ مصطنع.) على سبيل المثال ، أعتقد أن خيار التشغيل المتداخل الحالي الخاص بنا سيكون مكافئًا لهذا (والعكس):

impl<T, F, U> Try<Option<U>> for Result<T, F>
   where F: From<NoneError>
{
    fn check(x: Option<U>) -> ControlFlow<U, Self> {
        match x {
            Some(x) => ControlFlow::Continue(x),
            None => ControlFlow::Break(Err(From::from(NoneError))),
        }
    }
}

ميولي الحالية لـ throw ستكون لهذا:

trait TryBreak<T> : TryContinue { from_break(_: T) -> Self }

مع

throw $expr  ==>  break 'closest_catch TryBreak::from_break($expr)
  • يمتد TryContinue بحيث يكون التغليف الجيد متاحًا أيضًا لنوع الإرجاع للطريقة التي يتم استخدامه فيها
  • ليس نوعًا مرتبطًا ، لذا يمكنك تضمين كليهما ، على سبيل المثال ، TryBreak<TryFromIntError> و TryBreak<io::Error> للنوع إذا أردت.

    • و impl<T> TryBreak<()> for Option<T> لتمكين throw; يبدو معقولًا بطريقة لم يفعلها نوع الخطأ المرتبط وهو () .

    • impl<T> TryBreak<!> for T سيكون لطيفًا أيضًا ، لكن ربما يكون غير متماسك.

(جانبا: أسماء السمات والطريقة هذه مروعة ؛ الرجاء المساعدة !)

إن أفكاري حول المشكلات الأخرى التي أثيرت هنا لم تتبلور في شكل يسهل التعبير عنه حتى الآن ، ولكن فيما يتعلق بمشاكل الاستدلال حول النوع From::from() desugaring (لا أتذكر ما إذا كان هذا قد تمت مناقشته أيضًا في مكان آخر مؤخرًا ، أو هنا فقط؟):

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

وهذا هو، وأعتقد أن: كلما نرى From::from() [اختياريا: واحد التي تم إدراجها من قبل ? desugaring، بدلا من المكتوب يدويا]، ونحن نعرف بالضبط واحد من هذين النوعين (المدخلات مقابل الإخراج) ، في حين أن الآخر غامض ، فإننا نفترض أن يكون الآخر هو نفسه. بعبارة أخرى ، أعتقد أنه عندما لا يكون من الواضح ما هو impl From يجب استخدامه ، فإننا نختار impl<T> From<T> for T . هذا ، كما أعتقد ، دائمًا ما تريده بالفعل؟ ربما يكون الأمر مخصصًا بعض الشيء ، ولكن إذا نجح ، فستبدو الفوائد تستحق تكاليف IMHO.

(فكرت أيضا From كان بالفعل عنصر لانج، على وجه التحديد نظرا ل ? desugaring، ولكن لا يبدو أن تكون؟ وعلى أي حال، انها بالفعل في بعض الطرق الخاصة لهذا السبب .)

في اقتراح scottmcm ، فإن From::from() _not_ جزء من عملية الفصل ، بل هو جزء من تنفيذ Try مقابل Result .

tmccombs لم أكن أقترح تعديلاً على اقتراحه.

يناقش RFC try{} (https://github.com/rust-lang/rfcs/pull/2388) كتل try حيث لا يهتم المرء بالنتيجة. يبدو أن هذا البديل يتعامل مع ذلك بشكل لائق ، حيث يمكنه اختيار تجاهل أنواع الأخطاء تمامًا إذا رغب في ذلك ، والسماح بذلك

let IgnoreErrors = try {
    error()?;
    none()?;
};

تنفيذ إثبات المفهوم باستخدام نفس السمات كما في السابق: https://play.rust-lang.org/؟gist=e0f6677632e0a9941ed1a67ca9ae9c98&version=stable

أعتقد أن هناك بعض الاحتمالات المثيرة للاهتمام هناك ، خاصة وأن التطبيق المخصص ، على سبيل المثال ، يمكن أن يأخذ النتائج فقط ويقيد E: Debug لذلك فإنه يسجل تلقائيًا أي خطأ يحدث. أو يمكن عمل إصدار مخصص كنوع إرجاع مقابل main بالاقتران مع Termination الذي "يعمل فقط" للسماح لك باستخدام ? بدون توقيع من النوع المعقد (https : //github.com/rust-lang/rfcs/issues/2367).

لقد واجهت مشكلات مماثلة لتلك التي تم إثباتها من خلال Try . للمشكلات المحددة ، راجع https://github.com/SergioBenitez/Rocket/issues/597#issuecomment -381533108.

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

#[derive(Debug, Copy, Clone)]
enum ControlFlow<C, B> {
    Continue(C),
    Break(B),
}

// Used by `try { }` expansions.
trait FromTry: Try {
    fn from_try(value: Self::Continue) -> Self;
}

// Used by `?`.
trait Try<T = Self>: Sized {
    type Continue;
    fn check(x: T) -> ControlFlow<Self::Continue, Self>;
}

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

شاهد الملعب الكامل مع تطبيقات مقابل Result و Option بالإضافة إلى Rocket's Outcome في هذا الملعب .

شكرًا على التقرير ،SergioBenitez! يطابق هذا التنفيذ الإصدار البديل "قلب معلمات النوع" من اقتراح السمات الأصلي في RFC 1859: https://github.com/rust-lang/rfcs/blob/f89568b1fe5db4d01c4668e0d334d4a5abb023d8/text/0000-try-trait.md#unresolved -أسئلة

أكبر شيء يخسره هو الخاصية التي يعتمد عليها typeof(x?) فقط على typeof(x) . كان الافتقار إلى هذه الخاصية أحد اهتمامات الأصل ("أنا قلق قليلاً بشأن سهولة القراءة في الشفرة على طول هذه الأسطر" https://github.com/rust-lang/rfcs/pull/1859#issuecomment-279187967) وميزة من اقتراح الاختزال النهائي ("لأي نوع T ،؟ يمكن أن ينتج نوعًا واحدًا بالضبط من قيمة ok / error" https://github.com/rust-lang/rfcs/pull/1859#issuecomment-283104310). بالطبع ، كانت هناك أيضًا حجج مفادها أن الخاصية المذكورة غير ضرورية أو مقيدة للغاية ، ولكن في الملخص النهائي قبل FCP كانت لا تزال موجودة كميزة (https://github.com/rust-lang/rfcs/pull/1859#issuecomment -295878466).

إلى جانب تبسيط التعريفات ، يتمتع هذا بميزة أنه يمكن تنفيذ Try بشكل مستقل عن FromTry ، والذي أفترض أنه أكثر شيوعًا

اليوم بالتأكيد from_ok أقل شيوعًا ، حيث Try و do catch غير مستقرين. وحتى إذا كان do catch ثابتًا ، فأنا أوافق على أنه سيتم استخدامه أقل من ? بشكل عام (نظرًا لأن معظم هذه الكتل تحتوي على عدة ? s).

من منظور السمة وعملياتها ، مع ذلك ، أعتقد أن "التفاف قيمة" استمرارية "في نوع الناقل" هو جزء أساسي تمامًا من ? . نادرًا ما يمر المرء بهذه السمة لهذا اليوم - فقط باستخدام Ok(...) أو Some(...) بدلاً من ذلك - لكنه مهم للاستخدام العام. على سبيل المثال ، try_fold :

https://github.com/rust-lang/rust/blob/8728c7a726f3e8854f5a80b474d1a8bacab10304/src/libcore/iter/iterator.rs#L1478 -L1482

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

كما أشار Centril من قبل إلى أن "لف العدد القياسي في ناقل" هو أيضًا بناء أبسط من الناحية النظرية. على سبيل المثال ، يسميها هاسكل فئة الطباعة Pointed ، على الرغم من أنني لا أعتقد أننا نريد تعميمها _that_ بعيدًا في Rust: السماح try { 4 }vec![4] يبدو أنه مبالغة بالنسبة لي .

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

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

تحرير : نعتذر إذا تلقيت بريدًا إلكترونيًا يحتوي على نسخة مبكرة من هذا ؛ لقد نقرت على "تعليق" مبكرًا جدًا ...

أكبر شيء يخسره هو الخاصية التي تعتمد typeof (x؟) على typeof (x) فقط.

آه نعم بالتأكيد. شكرا لتوضيح ذلك

بالطبع ، كانت هناك أيضًا حجج مفادها أن الخاصية المذكورة غير ضرورية أو مقيدة للغاية ، ولكن في الملخص النهائي قبل FCP كانت لا تزال موجودة كميزة (rust-lang / rfcs # 1859 (تعليق)).

هل هناك أمثلة محددة على الأماكن التي قد تكون شديدة التقييد؟

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

أعتقد أن هذا تحليل عادل. أنا موافق.

SergioBenitez من https://github.com/rust-lang/rfcs/pull/1859#issuecomment -279187967

إذن السؤال هو ، هل القيود المقترحة كافية؟ هل هناك استخدامات جيدة لسياق حالة الخطأ في تحديد نوع النجاح؟ هل هناك انتهاكات محتملة؟

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

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

لذلك يمكن للمرء أن يتخيل سمة تحركت كلا النوعين لكتابة المعلمات ، مثل

trait Try<T,E> {
    fn question(self) -> Either<T, E>;
}

واستخدام ذلك يفعل كل من

let x: Result<_,_> = blah.poll()?; // early-return if NotReady
let x: Async<_> = blah.poll()?; // early-return if Error
let x: i32 = blah.poll()?; // early-return both NotReady and Errors

لكنني أعتقد أن هذه بالتأكيد فكرة سيئة ، لأنها تعني أن هذه لا تعمل

println!("{}", z?);
z?.method();

نظرًا لعدم وجود سياق للنوع ليقول ما يجب إنتاجه.

سيكون الإصدار الآخر هو تمكين أشياء مثل هذا:

fn foo() -> Result<(), Error> {
    // `x` is an Async<i32> because NotReady doesn't fit in Result
    let x = something_that_returns_poll()?;
}
fn bar() -> Poll<(), Error> {
    // `x` is just i32 because we're in a Poll-returning method
    let x = something_that_returns_poll()?;
}

غريزتي هناك أن الاستدلال "يتدفق من؟" هناك مفاجأة كبيرة ، وبالتالي هذا في دلو "ذكي جدا".

بشكل حاسم ، لا أعتقد أن عدم الحصول عليها مقيد للغاية. my_result? في دالة -> Poll لا تحتاجها ، لأن نوع النجاح هو نفسه كالعادة (مهم للحفاظ على عمل الكود المتزامن في السياقات غير المتزامنة) ويتحول متغير الخطأ بشكل جيد أيضًا . استخدام ? على Poll بطريقة تُرجع Result يبدو وكأنه مضاد للنمط على أي حال ، وليس شيئًا يجب أن يكون شائعًا ، لذا فإن استخدام طرق مخصصة (افتراضية) مثل .wait(): T (للحظر للنتيجة) أو .ready(): Option<T> (للتحقق مما إذا كان قد تم ذلك) لاختيار الوضع ربما يكون أفضل على أي حال.

هذا "مثير للاهتمام" https://play.rust-lang.org/؟gist=d3f2cd403981a631f30eba2c3166c1f4&version=nightly&mode=debug

لا أحب هذه الكتل التي تحاول (لا تصطاد) ، فهي لا تبدو ملائمة جدًا للوافدين الجدد.

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

في وقت مبكر من هذا الموضوع ، رأيت تعليقًا حول تقسيم Try إلى سمات خطأ منفصلة إلى / من - هل هناك أي خطط لتنفيذ هذا الانقسام؟

قد يكون من المفيد الحصول على تحويل شفاف من Result<T, E> إلى أي نوع Other<T2, E> على علامة الاستفهام - سيسمح ذلك باستدعاء وظائف IO الحالية بصيغة لطيفة من داخل دالة ذات أكثر تخصصًا (على سبيل المثال كسول) نوع الإرجاع.

pub fn async_handler() -> AsyncResult<()> {
    let mut file = File::create("foo.txt")?;
    AsyncResult::lazy(move || {
        file.write_all(b"Hello, world!")?;
        AsyncResult::Ok(())
    })
}

من الناحية الدلالة ، يبدو هذا وكأنه From::from(E) -> Other<T2, E> ، لكن استخدام From مقيد حاليًا بالتطبيق الحالي Result ما يعادل Try .

أعتقد حقًا أن NoneError يجب أن يكون لديه مشكلة تتبع منفصلة. حتى إذا لم تستقر السمة Try أبدًا ، فيجب أن تستقر NoneError لأنها تجعل استخدام ? على Option أكثر راحة. ضع في اعتبارك هذا لأخطاء مثل struct MyCustomSemanthicalError; أو أخطاء تنفيذ Default . يمكن بسهولة تحويل None إلى MyCustomSeemanthicalError عبر From<NoneError> .

أثناء العمل على https://github.com/rust-analyzer/rust-analyzer/ ، قابلت ورقة مختلفة قليلاً عن حالات القصور في المشغل ? ، خاصة عندما يكون نوع الإرجاع Result<Option<T>, E> .

بالنسبة لهذا النوع ، من المنطقي أن تقوم ? بإلغاء الأمر بشكل فعال:

match value? {
    None => return Ok(None),
    Some(it)=>it,
}

حيث يكون value من النوع Result<Option<V>, E> ، أو:

value?

حيث value هو Result<V, E> . أعتقد أن هذا ممكن إذا نفذت المكتبات القياسية Try بالطريقة التالية مقابل Option<T> ، على الرغم من أنني لم أختبر هذا صراحة وأعتقد أنه قد تكون هناك مشكلات في التخصص.

enum NoneError<E> {
    None,
    Err(E),
}

impl From<T> for NoneError<T> {
    fn from(v: T) -> NoneError<T> {
        NoneError:Err(v)
    }
}

impl<T, E> std::Ops::Try for Result<Option<T>, E> {
    type OK = T;
    type Error = NoneError<E>;
    fn into_result(self) -> Result<Self::OK, Self::Error> {
        match self {
            Ok(option) => {
                if let Some(inner) = option {
                    Ok(inner)
                } else {
                    Err(NoneError::None)
                }
            }
            Err(error) => {
                Err(NoneError::Err(error));
            }
        }
    }
    fn from_error(v: Self::Error) -> Self {
        match v {
            NoneError::Err(error) => Err(error),
            None => Some(None),
        }
    }
    fn from_ok(v: Self::OK) -> Self {
        Ok(Some(v))
    }
}

impl<T> std::Ops::Try for Option<T> {
    type OK = T;
    type Error = NoneError<!>;
    fn into_result(self) -> Result<Self::OK, Self::Error> {
        match self {
            None => Err(NoneError::None),
            Some(v) => Ok(v),
        }
    }
    fn from_error(v: Self::Error) -> Self {
        match v {
            NoneError::None => Some(None),
            _ => unreachable!("Value of type ! obtained"),
        }
    }
    fn from_ok(v: Self::OK) -> Self {
        Ok(v)
    }
}

عندما سأل matklad عن سبب عدم تمكنه من إنشاء تعداد مخصص يطبق Try ، والذي سيُطلق عليه Cancellable في هذه الحالة ، أشار إلى أن std::ops::Try غير مستقر ، لذلك لا يمكن استخدامه على أي حال نظرًا لأن rust-analyzer (حاليًا) يستهدف صدأًا ثابتًا.

إعادة النشر من https://github.com/rust-lang/rust/issues/31436#issuecomment -441408288 لأنني أردت التعليق على هذا ، لكنني أعتقد أن هذه كانت مشكلة خاطئة:

بشكل أساسي ، الموقف الذي أواجهه هو عمليات الاسترجاعات في إطار عمل واجهة المستخدم الرسومية - بدلاً من إرجاع Option أو Result ، يحتاجون إلى إرجاع UpdateScreen ، لإخبار الإطار إذا كانت الشاشة يحتاج إلى تحديث أم لا. غالبًا لا أحتاج إلى تسجيل الدخول على الإطلاق (من غير العملي ببساطة تسجيل الدخول لكل خطأ طفيف) وإرجاع UpdateScreen::DontRedraw عند حدوث خطأ. ومع ذلك ، مع عامل التشغيل الحالي ? ، يجب أن أكتب هذا طوال الوقت:

let thing = match fs::read(path) {
    Ok(o) => o,
    Err(_) => return UpdateScreen::DontRedraw,
};

نظرًا لأنني لا أستطيع التحويل من Result::Err إلى UpdateScreen::DontRedraw عبر عامل التشغيل Try ، فإن هذا يصبح مملًا للغاية - غالبًا ما يكون لدي عمليات بحث بسيطة في خرائط التجزئة التي يمكن أن تفشل (وهذا ليس خطأ ) - في كثير من الأحيان في رد اتصال واحد لدي من 5 إلى 10 استخدامات لعامل التشغيل ? . نظرًا لأن ما ورد أعلاه مطول جدًا للكتابة ، فإن الحل الحالي هو impl From<Result<T>> for UpdateScreen مثل هذا ، ثم استخدم وظيفة داخلية في رد الاتصال مثل هذا:

fn callback(data: &mut State) -> UpdateScreen {
     fn callback_inner(data: &mut State) -> Option<()> {
         let file_contents = fs::read_to_string(data.path).ok()?;
         data.loaded_file = Some(file_contents);
         Some(())
     }

    callback_inner(data).into()
}

نظرًا لاستخدام رد الاتصال كمؤشر دالة ، لا يمكنني استخدام -> impl Into<UpdateScreen> (لسبب ما ، لا يُسمح حاليًا بإرجاع impl لمؤشرات الوظيفة). لذا فإن الطريقة الوحيدة بالنسبة لي لاستخدام عامل التشغيل Try على الإطلاق هي القيام بخدعة الوظيفة الداخلية. سيكون من الرائع أن أفعل شيئًا كهذا:

impl<T> Try<Result<T>> for UpdateScreen {
    fn try(original: Result<T>) -> Try<T, UpdateScreen> {
        match original {
             Ok(o) => Try::DontReturn(o),
             Err(_) => Try::Return(UpdateScreen::DontRedraw),
        }
    }
}

fn callback(data: &mut State) -> UpdateScreen {
     // On any Result::Err, convert to an UpdateScreeen::DontRedraw and return
     let file_contents = fs::read_to_string(data.path)?;
     data.loaded_file = Some(file_contents);
     UpdateScreen::Redraw
}

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

@ joshtriplett آسف لأخذ بعض الوقت للعودة إلى هذا. لقد جمعت نموذجًا أوليًا عمليًا للاقتراح على https://github.com/rust-lang/rust/compare/master...scottmcm : try-trait-v2 ليكون ملموسًا. آمل أن أجرب بعض الأشياء معها.

scottmcm هل لديك شرح عالي المستوى للتغييرات؟

scottmcm FWIW جربت تغييراتك في الرايون أيضًا:
https://github.com/rayon-rs/rayon/compare/master...cuviper : try-trait-v2
(لا يزال يستخدم النسخ الخاصة بدلاً من العناصر غير المستقرة)

إذن ما هو الحل لتحويل Option NoneError؟ يبدو أنه ، نظرًا لأنه ينفذ سمة Tryit ، فلن يتم تجميعها إلا إذا قمت بتمكين استخدام هذه الميزة (غير المستقرة؟).

لذلك ، في الأساس ، لا يمكنك استخدام؟ عامل مع الخيار بقدر ما أعرف؟

omarabid ، عامل التشغيل ثابت للاستخدام مع Option أو Result ، لكن لا يمكنك استخدام Try كقيد عام حتى يصبح مستقرًا. من الجيد تمامًا استخدام ? على Option في وظيفة تعيد Option ، حيث لا يتعين عليك استخدام NoneError على الإطلاق. يمكنك أيضًا إرجاع Result إذا قمت بمسح الأنواع:

use std::fmt::Debug;

pub struct Error(Box<dyn Debug>);

impl<T: Debug + 'static> From<T> for Error {
    fn from(error: T) -> Self {
        Error(Box::new(error))
    }
}

pub fn try_get<T>(slice: &[T], index: usize) -> Result<&T, Error> {
    Ok(slice.get(index)?)
}

( ملعب )

scottmcm ، try-trait-v2 في هذا المثال!

إذا لم نرغب في كسر المثال الخاص بي ، فسيحتاج try-trait-v2 إلى شيء مثل:

#[unstable(feature = "try_trait_v2", issue = "42327")]
impl<T, U, E: From<NoneError>> ops::Bubble<Result<U, E>> for Option<T> {
    fn bubble(self) -> ops::ControlFlow<T, Result<U, E>> {
        match self {
            Some(x) => ops::ControlFlow::Continue(x),
            None => ops::ControlFlow::Break(Err(E::from(NoneError))),
        }
    }
}

ما هو الوضع الحالي لهذه الميزة؟

يجب إعادة فتح PR # 62606 لتوثيق تنفيذ try_fold للمكررات بمجرد أن يصبح هذا مستقرًا.

تحرير: تم تحديث المرجع مع عنصر تتبع لذلك ~ scottmcm

هل هناك أي خطط لاستبدال السمة Try بأي من البدائل المقترحة في هذا الموضوع؟ النسخة المقترحة من scottmcm تبدو جيدة. أريد الاستمرار في استخدام عامل التشغيل ? مع Option ، وأعتقد أن السمة Try يجب تغييرها بحيث لا تفرض Result على الدلالات Option .

سيسمح لنا استخدام بديل scottmcm باستخدام ? مع Option والتخلص من NoneError . أتفق مع nikomatsakis ( تعليق ) على أن السمة Try يجب ألا تعزز الحاجة إلى "تحديد نوع بشكل مصطنع لتمثيل" فشل " Option ".

pub struct Error(Box<dyn std::fmt::Debug>);
impl<T: std::fmt::Debug + 'static> From<T> for Error { fn from(error : T) -> Self { Error(Box::new(error)) } }
type Result<T> = std::result::Result<T, Error>;

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

  • استخدام التصحيح (شائع لـ StdError و NoneError) بدلاً من StdError:
    عام From<T: StdError> for Error ومتخصص From<NoneError> for Error تعارضات
    لأنه سيكون من الغموض إذا نفذت NoneError StdError (على ما يبدو ، حتى الافتراضي لا يسمح بالتجاوزات ويفرض الحصرية)
    يمكن حل هذا من خلال "تقييد سلبي" غير مدعوم ، ربما لأنه سيكون حالة الاستخدام الوحيدة؟ (فوق التخصص)
    لا يمكن تعريف impl StdError لـ NoneError إلا جنبًا إلى جنب مع StdError أو NoneError نفسه ، بحيث يكون متسقًا عبر أي اتجاه في اتجاه مجرى النهر.
  • لا يمكن أن يكون الخطأ اسمًا مستعارًا:
    type Error = Box<Debug> يربط التصحيح مما يجعل From<T:Debug> for Error يتعارض مع From<T> for T (انعكاسي من أجل idempotence)

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

fn from<T>(result : Result<T>) -> std::result::Result<T, Box<dyn std::fmt::Debug>> { match result { Ok(t) => Ok(t), Err(e) => Err(e.0) } }

لا يمكن أن يكون impl From<Result<T>> for StdResult<T> ولا Into<StdResult> for Result<T> (لا يمكن ضم سمة المنبع لنوع المنبع)

على سبيل المثال ، يمكنك استخدامه للحصول على تصحيح إرجاع للإنهاء:

fn main() -> std::result::Result<(), Box<dyn std::fmt::Debug>> { from(run()) }

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

let x: i32 = something?;
rest of function body

يصبح

Monad::bind(something, fn(x) {
    rest of function body
})

فكرة رهيبة ، لكنها ترضي المهوس الداخلي.

derekdreery لا أعتقد أن هذا سيعمل بشكل جيد مع تدفق التحكم الإلزامي مثل return و continue

ضع في اعتبارك أن دلالات عامل التشغيل ? مستقرة بالفعل. السمة الفعلية Try هي فقط غير مستقرة ، وأي تغييرات يجب أن تحافظ على نفس التأثير المستقر لـ Option و Result .

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

derekdreery لا أعتقد أن ذلك سيعمل بشكل جيد مع تدفق التحكم الإلزامي مثل العودة والمتابعة

انا اوافق علي هذا البيان.

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

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

هل هذا صحيح عبر العصور أيضًا؟

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

نأسف لكونك OT.

لست متأكدًا مما إذا كان سيتم السماح لعصر / إصدار بتغيير ? desugaring ، لكن هذا بالتأكيد سيعقد الكثير من المخاوف عبر الصناديق مثل وحدات الماكرو.

تفسيري لضمانات الاستقرار والعهود RFC هو أنه يمكن تغيير سلوك ? (سكر أو غير ذلك) ، لكن سلوكه الحالي على أنواع Option / Result يجب أن يظل نفس الشيء لأن كسر ذلك سيؤدي إلى حدوث اضطراب أكبر بكثير مما نأمل في تبريره.

ماذا لو كان Option بطريقة ما اسمًا مستعارًا من النوع لـ Result ؟ أي type Option<T> = Result<T, NoValue> ، Option::<T>::Some(x) = Result::<T, NoValue>::Ok(x) ، Option::<T>::None = Result::<T, NoValue>::Err(NoValue) ؟ قد يتطلب ذلك بعض السحر للتنفيذ وغير واقعي في بيئة اللغة الحالية ، ولكن ربما يستحق ذلك الاستكشاف.

لا يمكننا إجراء هذا التغيير نظرًا لوجود إشارات ضمنية تعتمد على أنواع مختلفة من Option و Result .

إذا تجاهلنا ذلك ، فيمكننا اتخاذ خطوة أخرى إلى الأمام وحتى جعل bool اسمًا مستعارًا لـ Result<True, False> ، حيث True و False نوعان من الوحدات.

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

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

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

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

يجب عليك استخدام أسلوب فك أو أحد أشكاله.

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

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

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

كيف سيعمل هذا؟ فقط قم دائمًا بالتقييم إلى Ok (()) ويتم تجاهلك؟

أيضًا ، هل يمكنك إعطاء مثال على المكان الذي تريد استخدام هذا فيه؟

نعم ، كانت الفكرة أن شيئًا مثل MyCollection :: push قد يكون ، بناءً على تكوين وقت الترجمة ، إما نتيجة <() ، أو AllocError> قيمة إرجاع أو قيمة إرجاع () إذا تم تكوين المجموعة لمجرد الذعر عند حدوث خطأ. لا ينبغي أن يفكر الكود الوسيط الذي يستخدم المجموعة في ذلك ، لذا إذا كان بإمكانه ببساطة _ دائمًا_ استخدام ? حتى عندما يكون نوع الإرجاع () فسيكون ذلك مفيدًا.

بعد ما يقرب من 3 سنوات ، هل هذا أقرب إلى الحل؟

Lokathor الذي سيعمل فقط إذا كان نوع الإرجاع Result<Result<X,Y>,Z> أو ما شابه غير ممكن. و لكنها. وبالتالي غير ممكن.

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

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

https://internals.rust-lang.org/t/a-slightly-more-general-easier-to-implement-alternative-to-the-try-trait/12034

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

استخدام تعليق توضيحي

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

#[debug_result]
fn f() -> Result<X, Y>;

#[debug_result]
fn f2() -> Result<Result<A, B>, Y>;

#[debug_result]
fn g() -> Result<X, Y> {
    // #[debug_result_spoiled]
    let w = f();
    // w could have type X or `Result<X,Y>` based on a #[cfg(debug_...)]
    // the following line would currently only work half of the time
    // we would modify the behavoir of `?` to a no-op if #[cfg(not(debug_...))]
    // and `w` was marked as `#[debug_result]`-spoiled
    let x = w?;

    // but it gets worse; what's with the following:
    let y = f2();
    let z = y?;
    // it has completely different sematics based on a #[cfg(debug_...)],
    // but would (currently?) print no warnings or errors at all,
    // and the type of z would be differently.

    Ok(z)
}

وبالتالي ، فإنه سيجعل قراءة الشيفرة أكثر صعوبة.
لا يمكننا ببساطة تعديل سلوك ? ببساطة إلى no-op ،
خاصة إذا تم حفظ القيمة المرجعة لـ #[debug_result] في متغير مؤقت ثم "try-propagated" لاحقًا باستخدام عامل التشغيل ? . قد يجعل دلالات الـ ? مربكة حقًا ، لأنه سيعتمد على العديد من "المتغيرات" ، والتي لا تُعرف بالضرورة في "وقت الكتابة" أو قد يكون من الصعب تخمينها بمجرد قراءة الكود المصدري. سيحتاج #[debug_result] إلى إفساد المتغيرات التي تم تعيينها لقيم وظيفية ، ولكنها لن تعمل إذا تم تمييز دالة واحدة بـ #[debug_result] وأخرى غير ذلك ، على سبيل المثال ، سيكون ما يلي خطأ في النوع.

// f3 is not annotated
fn f3() -> Result<X, Y>;

// later inside of a function:
   // z2 needs to be both marked spoiled and non-spoiled -> type error
   let z2 = if a() {
       f3()
   } else {
       f()
   };
   // althrough the following would be possible:
   let z2_ = if a() {
       f3()?
   } else {
       f()?
   };

بديل: استخدام نوع جديد

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

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

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

// the result of the fn *must* have Try<Ok=()>
// btw. the macro could be already implemented with a proc_macro today.
#[debug_result]
fn x() -> Result<(), XError> {
  /* ..{xbody}.. */
}

// e.g. evaluates roughly to:
//..begin eval
fn x_inner() -> Result<(), XError> {
  /* ..{xbody}.. */
}

#[cfg(panic_on_err)]
fn x() {
  let _: () = x_inner().unwrap();
}
#[cfg(not(panic_on_err))]
fn x() -> Result<(), XError> {
  x_innner()
}

//..end eval

fn y() -> Result<(), YError> {
  /* ... */
  // #[debug_result] results can't be matched on and can't be assigned to a
  // variable, they only can be used together with `?`, which would create
  // an asymetry in the type system, but could otherwise work, althrough
  // usage would be extremely restricted.
  x()?;
  // e.g. the following would fail to compile because capturing the result
  // is not allowed
  if let Err(z) = x() {
    // ...
  }
}

zserik هل من الممكن أن يتخذ شكلًا كهذا بالفعل؟

#[cfg(panic_on_err)]
fn x() -> Result<(), !> {
  let _: () = x_inner().unwrap();
}

#[cfg(not(panic_on_err))]
fn x() -> Result<(), XError> {
  x_innner()
}

حسنا فكره جيده.

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

لديك سمة تستخدمها لتمرير معلومات التعقب التي تستخدم التخصص والتطبيق الافتراضي لـ T للحفاظ على التوافق مع الإصدارات السابقة

pub trait Track {
    fn track(&mut self, location: &'static Location<'static>);
}

default impl<T> Track for T {
    fn track(&mut self, _: &'static Location<'static>) {}
}

ثم تقوم بتعديل تنفيذ Try لـ Result لاستخدام track_caller وتمرير هذه المعلومات إلى النوع الداخلي ،

    #[track_caller]
    fn from_error(mut v: Self::Error) -> Self {
        v.track(Location::caller());
        Self::Err(v)
    }

ثم بالنسبة للأنواع التي تريد جمعها backtraces لك ، قم بتنفيذ Track

#[derive(Debug, Default)]
pub struct ReturnTrace {
    frames: Vec<&'static Location<'static>>,
}

impl Track for ReturnTrace {
    fn track(&mut self, location: &'static Location<'static>) {
        self.frames.push(location);
    }
}

ينتهي الاستخدام بهذا الشكل

#![feature(try_blocks)]
use error_return_trace::{MyResult, ReturnTrace};

fn main() {
    let trace = match one() {
        MyResult::Ok(()) => unreachable!(),
        MyResult::Err(trace) => trace,
    };

    println!("{:?}", trace);
}

fn one() -> MyResult<(), ReturnTrace> {
    try { two()? }
}

fn two() -> MyResult<(), ReturnTrace> {
    MyResult::Err(ReturnTrace::default())?
}

وإخراج نسخة سيئة للغاية من backtrace يبدو هكذا

ReturnTrace { frames: [Location { file: "examples/usage.rs", line: 18, col: 42 }, Location { file: "examples/usage.rs", line: 14, col: 16 }] }

وهنا دليل على مفهومها في العمل https://github.com/yaahc/error-return-traces

اعتقدت أن نوع خطأ واحد فقط يمكننا تحويله لمحاولة تنفيذ السمات قد يكون غير كافٍ ، لذلك ، يمكننا توفير واجهة مثل هذه:

trait Try{
     type Error;
     type Ok;
     fn into_result(self)->Result<Self::Ok,Self::Error>;
     fn from_ok(val: Self::Ok)->Self;
     fn from_error<T>(val: T)->Self;
}

ملاحظة ، يمكن للمترجم أن يحدد قيمة from_error ، مع تجنب استدعاء From::from ، ويمكن للمرء أن يوفر أسلوبًا لأنواع الأخطاء المختلفة يدويًا ، مما ينتج عنه قدرة عامل تشغيل ? على "إلغاء التفاف" الاختلاف أنواع الأخطاء.

 fn from_error<T>(val: T)->Self;

كما هو مكتوب ، يجب أن يقبل المنفذ _ أي _ بحجم T ، غير مقيد. إذا كنت تقصد جعل هذا مقيدًا بشكل مخصص ، فيجب أن يكون معلمة سمة ، مثل Try<T> . هذا مشابه لمجموعة TryBlock / Bubble<Other> التي يمتلكهاscottmcm في https://github.com/rust-lang/rust/issues/42327#issuecomment-457353299 .

كما هو مكتوب ، يجب أن يقبل المنفذ _ أي _ بحجم T ، غير مقيد. إذا كنت تقصد أن يكون هذا مقيدًا بشكل مخصص ، فيجب أن يكون معلمة سمة ، مثل Try<T> . هذا مشابه لتركيبة TryBlock / Bubble<Other> التي استخدمتهاscottmcm في # 42327 (تعليق) .

قصدت أن الاستخدام يجب أن يبدو كالتالي:

trait Try{
     //same from above
}
struct Dummy {
    a: u8,
}
struct Err1();
struct Err2();
impl Try for Dummy {
    type Ok=();
    type Error=();

    fn into_result(self)->Result<Self::Ok,Self::Error>{
        std::result::Result::Ok(())
    }
    fn from_ok(val: Self::Ok)->Self{
        Self{a: 0u8}
    }
    fn from_error<T>(val: Err1)->Self where T == Err1{
        Self{a: 1u8}
    }
    fn from_error<T>(val: Err2)->Self where T == Err2{
        Self{a: 2u8}
    }
}

ستحتاج إلى تقسيم Try و TryFromError. يعجبني ذلك أكثر من الاقتراح الأصلي fwiw لكنني أعتقد أنه سيحتاج إلى RFC جديد.

(وما زلت أعتقد أنه كان يجب تسميتها "نشر" بدلاً من "محاولة" ، لكني استطرادي)

@ tema3210 أعتقد أنني أفهم

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

ستحتاج إلى تقسيم Try و TryFromError.

حسنًا ، لهذا السبب ذكرت تصميم TryBlock / Bubble<Other> . يمكننا مناقشة اختيار التسمية هذا ، لكن الفكرة كانت أن العودة المبكرة لا تتعلق دائمًا بـ "الأخطاء" في حد ذاتها. على سبيل المثال، الكثير من الداخلية Iterator الأساليب تستخدم عادة LoopState نوع. لشيء مثل find ، ليس من الخطأ العثور على ما تبحث عنه ، لكننا نريد إيقاف التكرار هناك.

https://github.com/rust-lang/rust/blob/3360cc3a0ea33c84d0b0b1163107b1c1acbf2a69/src/libcore/iter/mod.rs#L375 -L378

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

سبب عدم إعجابي بالاسم "try" بالتحديد وأفضّل الاسم "propagate" ، لأنه "ينشر" عودة "مبكرة": p

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

هل ستكون هذه السمة مفيدة عند محاولة الكتابة فوق السلوك الافتراضي ? fe لإضافة خطاف للسجل إلى معلومات تصحيح الأخطاء (مثل رقم الملف / السطر)؟

يتم دعمه حاليًا للكتابة فوق وحدات ماكرو stdlib ، ولكن يبدو أن عامل التشغيل ? لا يتم تحويله إلى الماكرو try! بشكل صريح. هذا مؤسف.

stevenroose لإضافة دعم لذلك فقط إلى السمة Try ، سيتطلب تعديل سمة Try لتضمين معلومات موقع الملف حول الموقع الذي حدث فيه ? " .

stevenroose لإضافة دعم لذلك فقط إلى السمة Try ، سيتطلب تعديل سمة Try لتضمين معلومات موقع الملف حول الموقع الذي حدث فيه ? " .

هذا ليس صحيحًا ، يمكن استخدام # [track_caller] في السمات الضمنية حتى إذا كان تعريف السمة لا يتضمن التعليق التوضيحي

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

https://github.com/rust-lang/rust/issues/42327#issuecomment -619218371

لست متأكدًا مما إذا كان أي شخص قد ذكر هذا بالفعل ، فهل نحن impl Try for bool ، ربما Try<Ok=(), Error=FalseError> ؟
حتى نتمكن من فعل شيء مثل هذا

fn check_key(v: Vec<A>, key: usize) -> bool {
  let x = v.get_mut(key)?; // Option
  x.is_valid()?; // bool
  x.transform()?; // Result
  true
}

الآن يجب علي استخدام نوع الإرجاع Option<()> في معظم الحالات حيث أعتقد أن ? يمكن أن يجعل الكود أكثر وضوحًا.

لست متأكدًا مما إذا كان أي شخص قد ذكر هذا بالفعل ، فهل نحن impl Try for bool ، ربما Try<Ok=(), Error=FalseError> ؟

هذا سيجعل Try يتصرف كمعامل && على bool s. كما أشار آخرون أعلاه ، هناك أيضًا حالات استخدام للتقصير عند النجاح ، وفي هذه الحالة يجب أن يتصرف Try كـ || . نظرًا لأن المشغلين && و || لم يعدوا أطول بكثير للكتابة ، فأنا أيضًا لا أرى فائدة كبيرة في الحصول على هذا التنفيذ.

calebsander شكرا على الرد التكرم.
هذا صحيح في بعض الحالات البسيطة ، لكنني لا أعتقد أن هذا هو الحال غالبًا ، خاصةً أنه لا يمكننا أبدًا الحصول على بيان مهمة مثل let x = v.get_mut(key)? في التعبير.
إذا كان && و || يفي بالغرض دائمًا ، فيمكننا أيضًا اللعب بـ .unwrap_or_else() ، .and_then() مقابل Option و Error حالات.
هل يمكنك التعبير عن الشفرة المتدفقة بـ && و || ؟

fn check_key(v: Vec<A>, key: usize) -> bool {
  let x = v.get_mut(key)?; // Option
  x.not_valid().not()?; // bool
  for i in x.iter() {
    if i == 1 { return true }
    if i == 2 { return false }
    debug!("get {}" i);
  }
  let y = x.transform()?; // Result
  y == 1
}

بالنسبة لبعض الحالات التي يعني فشل true بينما يعني false النجاح ، قد يكون !expr? مربكًا ، لكن يمكننا استخدام expr.not()? لتنفيذ الحيلة (ملاحظة: من أجل ops::Try ، لدينا دائمًا false مقابل Error )

هذا صحيح في بعض الحالات البسيطة ، لكنني لا أعتقد أن هذا هو الحال غالبًا ، خاصةً أنه لا يمكننا أبدًا الحصول على بيان مهمة مثل let x = v.get_mut(key)? في التعبير.

حسنًا ، ما لم أسيء فهم اقتراحك ، فإن مجرد تنفيذ Try<Ok = (), Error = FalseError> مقابل bool لن يسمح بذلك. ستحتاج أيضًا إلى impl From<NoneError> for FalseError حتى يتمكن عامل التشغيل ? تحويل None إلى false . (وبالمثل ، إذا كنت تريد تطبيق ? على Result<T, E> داخل دالة تُرجع bool ، فستحتاج إلى impl From<E> for FalseError . أعتقد أن مثل هذا قد يكون تطبيق شامل مشكلة.) يمكنك أيضًا استخدام some_option().ok_or(false)? و some_result().map_err(|_| false)? إذا كنت موافقًا على القيمة المرجعة Result<bool, bool> (والتي يمكنك طيها بـ .unwrap_or_else(|err| err) ).

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

fn using_try() -> bool {
    some_bool()?;
    some_bool()?;
    some_bool()
}

و

fn using_and() -> bool {
    some_bool() &&
    some_bool() &&
    some_bool()
}

(من المسلم به أن تدفقات التحكم مثل if و loop التي تُرجع بخلاف () أقل وضوحًا في الترجمة.)

بالنسبة لبعض الحالات التي تعني فشل true بينما يعني false النجاح ، قد يكون !expr? مربكًا ، لكن يمكننا استخدام expr.not()? لتنفيذ الحيلة (ملاحظة: من أجل ops::Try ، لدينا دائمًا false مقابل Error )

ليس من الواضح بالنسبة لي أن false يجب أن يمثل حالة Error . (على سبيل المثال ، قد يحتاج Iterator.all() false إلى دائرة قصر ، لكن Iterator::any() قد يريد true بدلاً من ذلك.) كما أشرت ، يمكنك الحصول على عكس سلوك الدائرة القصيرة عن طريق عكس القيمة التي تم تمريرها إلى ? (وعكس قيمة إرجاع الوظيفة أيضًا). لكنني لا أعتقد أن هذا ينتج رمزًا قابلاً للقراءة للغاية. قد يكون من المنطقي أن يكون لديك struct s And(bool) و Or(bool) منفصلين لتنفيذ السلوكين المختلفين.

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

لا ، لا أريد أن impl From<T> for FalseError ، ربما يمكننا فعل result.ok()?

ليس من الواضح بالنسبة لي أن الخطأ يجب أن يمثل حالة الخطأ.

أعتقد أنه من الطبيعي إلى حد ما عندما يكون لدينا bool::then تلك الخريطة true إلى Some .

سامحني إذا فاتني - لماذا لا يعني NoneError std::error::Error ؟ هذا يجعلها عديمة الفائدة لأية وظائف تعيد Box<dyn Error> أو ما شابه.

سامحني إذا فاتني - لماذا لا يعني NoneError std::error::Error ؟ هذا يجعلها عديمة الفائدة لأية وظائف تعيد Box<dyn Error> أو ما شابه.

لست خبيرًا هنا ، ولكن يمكنني رؤية مشكلات كبيرة في محاولة الخروج برسالة خطأ مفيدة لـ "شيء ما كان None وتوقعت أن يكون Some " (وهو في الأساس ما سنكتسب هنا - بعض الرسائل التشخيصية). من واقع خبرتي ، كان من المنطقي دائمًا استخدام المُجمع Option::ok_or_else لاستخدام نوع خطأ آخر بدلاً من ذلك ، لأنني بصفتي رمز الاتصال ، لدي عمومًا سياق أفضل بكثير لتقديمه على أي حال.

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

المرة الوحيدة التي أسمح فيها بتفسير None كخطأ هي في النماذج الأولية التقريبية. أنا أحسب، إذا أضفنا ما يشبه Option::todo_err() المكالمة، التي من شأنها أن تعود Ok من قيمة الداخلية، ولكن الذعر على None . إنه أمر غير بديهي للغاية حتى تقوم بتحليل احتياجات وضع "النماذج الأولية التقريبية" لتأليف التعليمات البرمجية. إنه مشابه جدًا لـ Ok(my_option.unwrap()) ، لكنه لا يحتاج إلى غلاف Ok(...) . بالإضافة إلى ذلك ، فإنه يحدد صراحة طبيعة "المهام المطلوبة" ، وبالتالي ينقل نية إزالة هذا الرمز من منطق الإنتاج ، واستبداله بربط خطأ مناسب.

ولكن سوف الذعر على لا شيء

أشعر بقوة أننا يجب أن نترك هذا إلى unwrap . إذا أضفنا todo_err ، فيجب أن يُرجع نوع الخطأ الفعلي ، والذي يطرح السؤال عن نوع الخطأ الذي سيتم إرجاعه.

كما أظن أن fn todo_err(self) -> Result<Self, !> ستواجه مشكلة واضحة تتمثل في طلب ! لتحقيق الاستقرار ، وهو ما أه ، حسنًا ، يومًا ما.

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

نظرًا للنوع الذي يفتقر إلى هذا الضمني ، فقد لجأت فقط إلى صفع .ok_or("error msg") عليه ، والذي يعمل أيضًا ولكنه أقل ملاءمة.

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