السمة 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
fold
من حيث try_fold
، بحيث لا يلزم تجاوز كليهما.)قطعتان من الدرّاجات:
هل لدينا دافع خاص لاستدعاء النوع المرتبط 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 في استخدام الأسماء التي تقترح كسر / متابعة بدلاً من الخطأ / حسنًا.
لوضع الكود المحدد هنا ، أحب كيف يقرأ هذا:
إنها بالطبع ليست جميلة تمامًا مثل الحلقة المستقيمة ، ولكن مع "طرق التجربة" ستكون قريبة:
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
:
إذا كانت الدالة ستعيد نوع الناقل ، فمن المهم أن تكون هناك طريقة لوضع قيمة الفائدة في هذا النوع من الناقل. والأهم من ذلك ، لا أعتقد أن تقديم هذا يمثل صعوبة. له تعريف طبيعي للغاية للنتيجة ، للخيار ، للنتيجة ، إلخ.
كما أشار 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.
لذلك حاولت فهمها وشرحها بشكل ملموس.
يبدو أن هذا الخيط هو مفترق طرق على الأرجح حيث آمل أن أساعد أي شخص مثلي يتعثر في هذا الأمر ، فلا تتردد في التصحيح:
From<T: StdError> for Error
ومتخصص From<NoneError> for Error
تعارضات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>
أو ما شابه غير ممكن. و لكنها. وبالتالي غير ممكن.
لا أفهم سبب تسبب نتيجة ذات طبقات في حدوث مشكلات. هل يمكنك أن تشرح بالتفصيل من فضلك؟
لأغراض الإسناد الترافقي ، تم اقتراح صياغة بديلة على العناصر الداخلية بواسطة
تضمين التغريدة
حسنًا ، فكرت في الأمر بعمق أكبر وأعتقد أنني ربما وجدت تفسيرًا جيدًا إلى حد ما.
تكمن المشكلة في تفسير نوع الإرجاع أو أن التعليقات التوضيحية الغريبة قد تؤدي إلى فوضى في الشفرة. قد يكون ذلك ممكنًا ، لكنه سيجعل قراءة التعليمات البرمجية أكثر صعوبة. (الشرط المسبق: يطبق #[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
، ليس من الخطأ العثور على ما تبحث عنه ، لكننا نريد إيقاف التكرار هناك.
يمكننا مناقشة اختيار التسمية هذا ، لكن الفكرة كانت أن العودة المبكرة لا تتعلق دائمًا بالأخطاء في حد ذاتها.
سبب عدم إعجابي بالاسم "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")
عليه ، والذي يعمل أيضًا ولكنه أقل ملاءمة.
التعليق الأكثر فائدة
ما هو الوضع الحالي لهذه الميزة؟