Unrecoverable Errors with panic

En Cairo, pueden surgir problemas inesperados durante la ejecución del programa, dando lugar a errores en tiempo de ejecución. Mientras que la función de pánico de la librería principal no proporciona una resolución para estos errores, sí reconoce su ocurrencia y termina el programa. Hay dos formas principales en las que se puede desencadenar un pánico en Cairo: inadvertidamente, a través de acciones que causan que el código entre en pánico (por ejemplo, acceder a un array más allá de sus límites), o deliberadamente, invocando la función de pánico.

When a panic occurs, it leads to an abrupt termination of the program. The panic function takes an array as an argument, which can be used to provide an error message and performs an unwind process where all variables are dropped and dictionaries squashed to ensure the soundness of the program to safely terminate the execution.

Así es como podemos panic desde dentro de un programa y devolver el código de error 2:

Filename: 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();
}

La ejecución del programa producirá el siguiente resultado:

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

Como se puede observar en la salida, la sentencia print nunca se alcanza, ya que el programa termina después de encontrar la sentencia panic.

An alternative and more idiomatic approach to panic in Cairo would be to use the panic_with_felt252 function. This function serves as an abstraction of the array-defining process and is often preferred due to its clearer and more concise expression of intent. By using panic_with_felt252, developers can panic in a one-liner by providing a felt252 error message as an argument, making the code more readable and maintainable.

Veamos un ejemplo:

fn main() {
    panic_with_felt252(2);
}

Ejecutando este programa se obtendrá el mismo mensaje de error que antes. En ese caso, si no hay necesidad de una matriz y múltiples valores a devolver dentro del error, por lo que panic_with_felt252 es una alternativa más sucinta.

nopanic notation

Puede utilizar la anotación nopanic para indicar que una función nunca entrará en pánico. Sólo las funciones nopanic pueden ser llamadas en una función anotada como nopanic.

Ejemplo:

fn function_never_panic() -> felt252 nopanic {
    42
}

Ejemplo incorrecto:

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

Si escribes la siguiente función que incluye una función que puede entrar en pánico obtendrás el siguiente error:

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');
    ^********************^

Tenga en cuenta que hay dos funciones que pueden entrar en pánico aquí, assert e equality.

panic_with attribute

You can use the panic_with attribute to mark a function that returns an Option or Result. This attribute takes two arguments, which are the data that is passed as the panic reason as well as the name for a wrapping function. It will create a wrapper for your annotated function which will panic if the function returns None or Err, the panic function will be called with the given data.

Ejemplo:

#[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'
}

Using assert

La función assert de la librería del núcleo de Cairo es en realidad una función de utilidad basada en panics. Afirma que una expresión booleana es verdadera en tiempo de ejecución, y si no lo es, llama a la función panic con un valor de error. La función assert toma dos argumentos: la expresión booleana a verificar y el valor de error. El valor de error se especifica como un felt252, por lo que cualquier cadena que se pase debe caber dentro de un felt252.

He aquí un ejemplo de su uso:

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

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

    100 / my_number;
}

We are asserting in main that my_number is not zero to ensure that we're not performing a division by 0. In this example, my_number is zero so the assertion will fail, and the program will panic with the string 'number is zero' (as a felt252) and the division will not be reached.

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