O Cairo disponibiliza técnicas de tratamento de erros para resolver problemas e gerir erros, permitindo que o código corra sem problemas durante a execução.
Existem dois tipos de erros em Cairo:
panic - estes erros terminam o programa imediatamente.Result - estes erros podem ser tratados em tempo de execução.panic
Em Cairo, podem surgir problemas inesperados durante a execução do programa, originando erros de execução a que se chama "panics".
Isto interrompe a execução do programa, o que é útil quando não faz sentido continuar.
Um panic pode ocorrer de forma inadvertida, através de ações que fazem o código entrar em panic (por exemplo, aceder a um array para lá dos seus limites), ou de forma deliberada, invocando a função panic.
fn main() {
let data = array![1, 2];
if true {
panic(data);
}
println!("This line will not be reached.");
}
Executar este exemplo imprimiria o seguinte na consola:
Run panicked with [1, 2, ].
Existe também uma macro especial, chamada panic!, que permite usar um ByteArray como mensagem de erro.
panic!("The error for the panic! Error message is not limited to 31 characters anymore");
assert!
A macro assert! é uma ferramenta útil para impor condições específicas no teu código.
Se a condição em assert! for avaliada como false, o programa entra em panic com uma mensagem de erro do tipo ByteArray.
Isto é usado frequentemente para verificar suposições durante o desenvolvimento e garantir que os valores cumprem determinados critérios.
Por exemplo:
fn main() {
let x = 5;
assert!(x > 0, "x must be greater than zero");
}
Se x não for maior do que zero, o programa entra em panic com a mensagem "x must be greater than zero".
O assert! é útil para verificar invariantes e pré-condições sem escreveres manualmente código de tratamento de erros.
nopanic
A notação nopanic de Cairo indica que uma função nunca entra em panic.
fn function_never_panic() -> felt252 nopanic {
108
}
Uma função nopanic só pode ser chamada numa função também anotada como nopanic.
panic_with
O atributo panic_with de Cairo serve para marcar uma função que devolve um Option ou um Result e cria um wrapper para a função anotada, que entra em panic se a função devolver Option::None ou 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
O enum Result trata os erros de forma controlada, fornecendo feedback em vez de provocar uma falha ou um comportamento indefinido.
enum Result<T, E> {
Ok: T,
Err: E,
}
Vamos ver um exemplo de como podemos usar este enum para tratar uma divisão por zero:
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),
}
}
?
O operador ? serve para um tratamento de erros conciso e implícito, deixando que a função que chama lide com os erros que possam ocorrer.
Quando usas o operador ? num tipo Result ou Option, acontece o seguinte:
Result::Ok(x) ou Option::Some(x), devolve diretamente o valor interno x.Result::Err(e) ou Option::None, propaga o erro ou o None, regressando imediatamente da função.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),
}
}