Cairoには、問題に対処してエラーを管理し、コードをスムーズに実行するためのエラーハンドリングの仕組みが用意されています。
Cairoのエラーには、2つの種類があります。
panicによる回復不可能なエラー:このエラーが発生すると、プログラムは即座に終了します。Resultによる回復可能なエラー:このエラーは実行中に処理できます。panicによる回復不可能なエラーCairoでは、プログラムの実行中に予期しない問題が発生し、実行時エラーである「パニック」が起きることがあります。
パニックが起きるとプログラムの実行は停止します。これは、そのまま続けても意味がない場合に役立ちます。
パニックは、コードをパニックさせる操作(たとえば配列の範囲外へのアクセスなど)によって意図せず起きることもあれば、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と評価されると、プログラムはByteArrayのエラーメッセージとともにパニックします。
これは、開発中に前提を確認したり、値が特定の条件を満たしていることを確かめたりするのによく使われます。
たとえば次のようになります。
fn main() {
let x = 5;
assert!(x > 0, "x must be greater than zero");
}
xが0より大きくない場合、プログラムは"x must be greater than zero"というメッセージとともにパニックします。
assert!を使うと、エラー処理のコードを手書きすることなく、不変条件や事前条件を確認できます。
nopanic記法Cairoのnopanic記法は、その関数が決してパニックしないことを示します。
fn function_never_panic() -> felt252 nopanic {
108
}
nopanic関数は、同じくnopanicが付いた関数の中からしか呼び出せません。
panic_with属性Cairoのpanic_with属性は、OptionまたはResultを返す関数に付けます。これにより、注釈を付けた関数のラッパーが作られ、その関数が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,
}
この列挙型を使って0による除算を処理する例を見てみましょう。
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),
}
}