无法恢复的错误与恐慌(panic)

在Cairo中,程序执行过程中可能会出现意外问题,导致运行时错误。虽然核心库中的panic函数并没有为这些错误提供解决方案,但它确实承认这些错误的发生并终止程序。在Cairo中,有两种主要的方式可以触发panic:无意地通过导致代码panic的行为(例如,访问一个超出其界限的数组),或故意地,通过调用panic函数。

当发生恐慌时,它会导致程序突然终止。panic 函数接受一个数组作为参数,可以用来提供错误消息,并执行一个解除过程,在这个过程中所有变量都会被丢弃,字典被压缩,以确保程序的健全性,安全地终止执行。

下面是我们如何在一个程序中panic并返回错误代码2

文件名: src/lib.cairo

use debug::PrintTrait;

fn main() {
    let mut data = ArrayTrait::new();
    data.append(2);
    if true == true {
        panic(data);
    }
    'This line isn\'t reached'.print();
}

运行该程序将产生以下输出:

$ scarb cairo-run
Run panicked with [2 (''), ].

正如你在输出中所注意到的,打印语句没有被执行,因为程序在遇到panic语句后就终止了。

Cairo 中处理恐慌的另一种更符合习惯的方法是使用 panic_with_felt252 函数。这个函数作为定义数组过程的抽象,通常更受欢迎,因为它表达意图更清晰、更简洁。通过使用 panic_with_felt252,开发者可以通过提供一个 felt252 类型的错误消息作为参数,在一行代码中实现恐慌,使代码更易读和可维护。

让我们来考察一个例子:

fn main() {
    panic_with_felt252(2);
}

执行这个程序会产生和之前一样的错误信息。在这种情况下,如果在返回错误是不需要一个数组和多个值,那么panic_with_felt252是一个更简洁的选择。

nopanic记号

你可以使用nopanic记号来表示一个函数永远不会恐慌。只有 nopanic函数可以在标注为 nopanic的函数中被调用。

例子:

fn function_never_panic() -> felt252 nopanic {
    42
}

错误的例子:

fn function_never_panic() nopanic {
    assert(1 == 1, 'what');
}

如果你写了以下函数,其中包括一个可能会panic的函数,你会得到以下错误:

error: Function is declared as nopanic but calls a function that may panic.
 --> test.cairo:2:12
    assert(1 == 1, 'what');
           ^****^
Function is declared as nopanic but calls a function that may panic.
 --> test.cairo:2:5
    assert(1 == 1, 'what');
    ^********************^

请注意,有两个函数可能会在这里发生panic,即断言和相等比较。

panic_with 属性

您可以使用 panic_with 属性来标记返回 OptionResult 的函数。该属性需要两个参数,即作为 panic 原因传递的数据以及包装函数的名称。它将为您标注的函数创建一个封装函数,如果函数返回 NoneErr,该封装函数将被调用,并使用给定的数据。

例子:

#[panic_with('value is 0', wrap_not_zero)]
fn wrap_if_not_zero(value: u128) -> Option<u128> {
    if value == 0 {
        Option::None
    } else {
        Option::Some(value)
    }
}

fn main() {
    wrap_if_not_zero(0); // this returns None
    wrap_not_zero(0); // this panic with 'value is 0'
}

使用断言(assert)

Cairo核心库中的assert函数实际上是一个基于panic的实用函数。它断言一个布尔表达式在运行时是真的,如果不是,它就会调用带有错误值的panic函数。assert函数需要两个参数:要验证的布尔表达式,以及错误值。错误值被指定为felt252,所以任何传递的字符串都必须能够容纳在felt252中。

下面是它的一个使用例子:

fn main() {
    let my_number: u8 = 0;

    assert(my_number != 0, 'number is zero');

    100 / my_number;
}

我们在main中断言my_number不是0,以确保我们没有进行除以0的操作。 在这个例子中,my_number是零,所以断言会失败,程序会panic, 并给出 'number is zero'的字符串结果(以felt252的形式),除法将不会被执行。

Last change: 2023-11-19, commit: a15432b