توفّر Cairo تقنيات لمعالجة الأخطاء والتعامل معها، مما يتيح تشغيل الكود بسلاسة أثناء التنفيذ.
يوجد نوعان من الأخطاء في Cairo:
panic - هذه الأخطاء تُنهي البرنامج فورًا.Result - يمكن معالجة هذه الأخطاء أثناء وقت التشغيل.panic
في Cairo، قد تنشأ مشكلات غير متوقعة أثناء تنفيذ البرنامج، مما يؤدي إلى أخطاء في وقت التشغيل تُسمى panics.
وهذا يوقف تنفيذ البرنامج، وهو أمر مفيد حين لا يكون للمتابعة أي معنى.
قد يحدث panic عن غير قصد، عبر إجراءات تؤدي إلى panic في الكود (مثل الوصول إلى مصفوفة خارج حدودها)، أو عن قصد، عبر استدعاء دالة panic.
fn main() {
let data = array![1, 2];
if true {
panic(data);
}
println!("This line will not be reached.");
}
تشغيل هذا المثال يطبع ما يلي في الطرفية:
Run panicked with [1, 2, ].
يوجد أيضًا ماكرو خاص يُسمى panic! يسمح باستخدام ByteArray كرسالة خطأ.
panic!("The error for the panic! Error message is not limited to 31 characters anymore");
assert!
يُعدّ ماكرو assert! أداة مفيدة لفرض شروط محددة في الكود.
إذا قُيّم الشرط في assert! إلى false، فسيحدث panic في البرنامج برسالة خطأ من نوع ByteArray.
ويُستخدم هذا غالبًا للتحقق من الافتراضات أثناء التطوير وللتأكد من أن القيم تستوفي معايير معينة.
على سبيل المثال:
fn main() {
let x = 5;
assert!(x > 0, "x must be greater than zero");
}
إذا لم تكن قيمة x أكبر من صفر، فسيحدث panic في البرنامج برسالة "x must be greater than zero".
ويفيد assert! في التحقق من الثوابت والشروط المسبقة دون كتابة كود معالجة الأخطاء يدويًا.
nopanic
يشير تدوين nopanic في Cairo إلى أن الدالة لن تُسبب panic أبدًا.
fn function_never_panic() -> felt252 nopanic {
108
}
لا يمكن استدعاء دالة nopanic إلا من داخل دالة موسومة أيضًا بـnopanic.
panic_with
تُستخدم سمة panic_with في Cairo لوسم دالة تُرجع Option أو Result، وهي تُنشئ غلافًا للدالة الموسومة لديك يحدث فيه panic إذا أرجعت الدالة Option::None أو Result::Err.
#[panic_with('value is 0', wrap_not_zero)]
fn check_not_zero(value: u128) -> Option<u128> {
if value == 0 {
Option::None
} else {
Option::Some(value)
}
}
fn main() {
check_not_zero(0); // -> returns Option::None
wrap_not_zero(0); // -> panics with 'value is 0'
}
Result
يتعامل تعداد Result مع الأخطاء بلطف، فيقدّم تغذية راجعة بدلًا من الانهيار أو إنتاج سلوك غير محدد.
enum Result<T, E> {
Ok: T,
Err: E,
}
لنلقِ نظرة على مثال لكيفية استخدام هذا التعداد لمعالجة القسمة على صفر:
fn divide(a: u32, b: u32) -> Result<u32, ByteArray> {
if b == 0 {
return Result::Err("Error: Division by zero"); // Return an error if division by zero is attempted
}
Result::Ok(a / b)
}
fn main() {
// Test with division by zero
match divide(10, 0) {
Result::Ok(result) => println!("Division successful: {}", result),
Result::Err(e) => println!("{}", e),
}
}
?
يُستخدم عامل ? لمعالجة الأخطاء بشكل موجز وضمني، إذ يتيح للدالة المستدعية التعامل مع أي أخطاء قد تحدث.
عند استخدام عامل ? على نوع Result أو Option، فإنه يقوم بما يلي:
Result::Ok(x) أو Option::Some(x)، فسيُرجع القيمة الداخلية x مباشرة.Result::Err(e) أو Option::None، فسيمرّر الخطأ أو None بالعودة فورًا من الدالة.fn parse_u8(s: felt252) -> Result<u8, felt252> {
match s.try_into() {
Option::Some(value) => Result::Ok(value),
Option::None => Result::Err('Invalid integer'),
}
}
fn do_something_with_parse_u8(input: felt252) -> Result<u8, felt252> {
let input_to_u8: u8 = parse_u8(input)?;
// DO SOMETHING
let res = input_to_u8 - 1;
Result::Ok(res)
}
fn main() {
let number: felt252 = 258;
match do_something_with_parse_u8(number) {
Result::Ok(value) => println!("Result: {}", value),
Result::Err(e) => println!("Error: {}", e),
}
}