Menu
flag Ar iconالعربيةdown icon

Raises و Check في Zero: فشل صريح دون استثناءات

تُعلن دوال Zero أوضاع فشلها بـ raises ويُقرّ المستدعون بها بـ check. إليك كيف يعمل النظام، ولماذا لا يوجد رمي صامت، وكيف يتفاعل مع قدرة World.

تحتوي هذه الصفحة على محررات قابلة للتشغيل — حرّر، شغّل، وشاهد النتيجة فوراً.

التمهيد

الفشل في Zero ليس تدفّق تحكّم منفصلًا متوازيًا. هو جزء من تدفّق التحكّم العادي، مُعلن في توقيع الدالة ومُقرٌّ به في كل موقع استدعاء. قطعتان تجعلان هذا يعمل:

  • raises في توقيع الدالة — "هذه الدالة يمكن أن تفشل."
  • check في موقع الاستدعاء — "إن فشل هذا، فأخفِق دالتي بنفس الخطأ."

التركيب كافٍ للتعبير عمّا تلجأ إليه معظم اللغات عبر try/catch أو أنواع Result.

إعلان دالة قابلة للفشل

أضف raises بعد نوع الإرجاع:

fun validate(ok: Bool) -> i32 raises { InvalidInput } {
    if ok == false {
        raise InvalidInput
    }
    return 42
}

طريقتان لقراءة التوقيع:

  • "validate تُعيد i32 أو ترفع InvalidInput."
  • "مجموعة النتائج الممكنة هي { i32، InvalidInput }."

كلتا القراءتَين صحيحتان. يتتبّع المترجم كلا الاحتمالَين ويطلب من المستدعين فعل شيء صريح بشأن كلٍّ منهما.

تستطيع أيضًا كتابة عبارة raises مجرّدة:

pub fun main(world: World) -> Void raises {
    check world.out.write("hello\n")
}

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

للدوال الأعمق في مكدّس الاستدعاءات، فضّل الشكل الصريح raises { ErrorA, ErrorB } كي تكون أنماط الفشل موثَّقة على كل مستوى.

رفع خطأ

داخل دالة قابلة للفشل، تُنهي raise الدالة بالخطأ المُعطى:

fun validate(ok: Bool) -> i32 raises { InvalidInput } {
    if ok == false {
        raise InvalidInput
    }
    return 42
}

raise InvalidInput يُنتج خطأ InvalidInput في ذلك السطر. الدالة لا تستمرّ بعد تلك النقطة — يعود التحكّم إلى المستدعي، ويرى المستدعي الخطأ بدلًا من i32. عبارة raises تذكر أنواع الأخطاء الوحيدة التي يُسمح لهذه الدالة برفعها؛ رفع شيء ليس في القائمة خطأ ترجمة.

نشر خطأ بـ check

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

fun run() -> Void raises { InvalidInput } {
    check validate(true)
}

check validate(true) يفعل شيئَين:

  1. يستدعي validate(true).
  2. إن رفعت validate خطأً، ينشره إلى الأعلى — تُخفق run بنفس الخطأ تجاه مستدعيها.

لكي يُسمح بالنشر، يجب أن تُعلن run أنّها تستطيع رفع InvalidInput (أو شيء متوافق) في عبارة raises الخاصّة بها. يتحقّق المترجم من ذلك. لو قال توقيع run raises { OtherError }، لفشل النشر في الترجمة لأن InvalidInput ليست في المجموعة.

مثال متكامل من العيّنات الرسمية للغة — اضغط Run لرؤية نشر الخطأ ينجح:

ينتقل نوع الخطأ مع توقيع الدالة على طول مكدّس الاستدعاءات حتى الأعلى. raises المجرّدة في main تقبل أي شيء قد ترفعه run، فيصل النشر بأمان.

لماذا ليس try/catch؟

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

هذا مريح لكاتب الشيفرة الرامية. مكلف على غيرها:

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

تدفع Zero التكلفة مسبقًا — تعليقات على كل دالة قابلة للفشل وcheck على كل استدعاء قابل للفشل — للحصول على الخاصية "أنماط الفشل التي تشارك فيها الدالة مرئية من توقيعها". تلك خاصية يستطيع البشر والوكلاء الاعتماد عليها.

لماذا ليس Result<T, E> فقط؟

تستطيع التعبير عن الشيء نفسه بـ choice — نوع Result<T, E> بمتغيِّرَي ok وerr. تُعطيك Zero هذا النمط أيضًا؛ هو أداة مفيدة عندما يكون الفشل بياناتٍ تريد فحصها أو تخزينها أو تمريرها.

ما يُضيفه raises/check هو اتّفاق على مستوى الصياغة للحالة الشائعة: "إن فشل هذا، فأخفِق دالتي بالطريقة نفسها." بدون ذلك، كل استدعاء كان سيُلفّ في match يُعيد تقريبًا دائمًا تغليف الخطأ في Result الخاص بالمستدعي. check اختصار لذلك، مع تأكّد المترجم من أن النشر مُلائم للأنواع.

إذن:

  • Result<T, E> (choice) — عندما تريد فحص الفشل أو حمله كقيمة.
  • raises + check — عندما تريد فقط نشر الفشل صعودًا في مكدّس الاستدعاءات.

كلاهما متاح؛ يُغطّيان احتياجات بيئة عمل (ergonomic) مختلفة.

أنواع أخطاء متعدّدة

تستطيع الدالة رفع أكثر من نوع خطأ واحد:

fun parse(input: String) -> i32 raises { Empty, Malformed } {
    if std.mem.len(input) == 0 {
        raise Empty
    }
    // ... منطق التحليل ...
    raise Malformed
}

يستطيع المستدعي:

  • النشر بـ check parse(input) إن كان توقيعه يذكر كلًا من Empty وMalformed (أو مجموعة موسَّعة).
  • معالجة أحدها أو كليهما صراحة بـ match أو بنى بنمط try تكشفها اللغة لهذا الغرض.

الصياغة الدقيقة للمعالجة الدقيقة (المطابقة على أنواع أخطاء محدّدة مقابل نشر الأخرى) من الواجهات التي قد تتغيّر في Zero قبل الإصدار 1.0. العقد — كل نوع خطأ تستطيع الدالة رفعه يكون في توقيعها — هو الجزء المستقرّ.

نموذج ذهني

نظام الفشل في Zero نسخة صارمة-صادقة ممّا لدى كل لغة إجرائية:

المفهومTry/CatchZero
تعليم دالة كقابلة للفشللا شيء (ضمني)raises { ... }
رفع خطأthrow eraise E
نشره إلى المستدعييفقاع بصمتcheck call(...)
معالجته محليًاtry { ... } catch(e) { ... }match على Result معاد، أو شكل معالجة معلَّم بالنوع

الفرق السلوكي صغير. الفرق في التعليقات كبير — وعن قصد. التأثيرات صريحة. الإخفاقات تأثيرات.

ملاحظات أسلوبية

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

التالي: تشخيصات JSON

raises/check يعمل جنبًا إلى جنب مع تشخيصات مترجم Zero — عندما تكتب check ضدّ دالة لا تُعلن الأخطاء الصحيحة، يُخبرك المترجم بالضبط ما هو الخطأ في شكل مهيكل. التوثيق التالي يُغطّي تشخيصات JSON — التغذية القابلة للقراءة آليًا التي يقرأها الوكيل لإصلاح الشيفرة.

الأسئلة الشائعة

ماذا تعني raises في Zero؟

raises على توقيع دالة تُعلن أنها قابلة للفشل. raises المجرّدة تسمح بأي نوع خطأ. الشكل المحدّد مثل raises { InvalidInput } يُقيّد الدالة لتفشل فقط بأنواع الأخطاء المُدرَجة. يجب على المستدعين الإقرار بإمكانية الفشل — إمّا بـ check، أو بشكل معالجة صريح آخر.

ماذا يفعل مُعامل check؟

check expr يُقيّم expr، وإن أنتج خطأ، ينشره إلى مستدعي الدالة الحالية. فكّر فيه على أنه 'نفّذ هذا، وإن فشل، فأخفِق دالة المستدعي بنفس الخطأ.' لكي يُسمح بالنشر، يجب أن تُعلن دالة المستدعي بأنّها تستطيع رفع خطأ متوافق في عبارة raises الخاصّة بها.

كيف ترفع خطأ في Zero؟

استخدم raise ErrorName داخل دالة عبارة raises فيها تتضمّن ذلك الخطأ. مثال: if ok == false { raise InvalidInput }. تخرج الدالة عند تلك النقطة؛ ويصبح الخطأ ناتج الدالة، الذي يستطيع المستدعي check ضدّه.

لماذا لا تستخدم Zero try/catch؟

‏try/catch تسمح للاستثناءات بالتحرّك بصمت عبر دوال لا تعلم بها. تصميم Zero أن يُقرّ كل توقيع دالة بأنماط الفشل التي تشارك فيها. لا يوجد تدفّق تحكّم خفي — إن كانت الدالة تستطيع الفشل، فتوقيعها يقول ذلك، وكل مستدعٍ يجب أن يُقرّ بذلك صراحة بـ check.

هل يمكن لدالة رفع أنواع أخطاء متعدّدة في Zero؟

نعم — اذكرها في عبارة raises { ... }، مفصولة بفواصل (أو بحسب صياغة Zero الحالية لمجموعات الأخطاء). مجموعة الأخطاء الممكنة جزء من عقد الدالة، تمامًا مثل أنواع معاملاتها وإرجاعها. يستطيع المستدعون مطابقة النمط على الخطأ الذي رُفع أو ببساطة نشره بـ check.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن