O Cairo oferece técnicas de tratamento de erros para resolver problemas e gerenciar erros, permitindo que o código rode sem problemas durante a execução.
Existem dois tipos de erros em Cairo:
panic: esses erros encerram o programa imediatamente.Result: esses erros podem ser tratados em tempo de execução.panic
Em Cairo, problemas inesperados podem surgir durante a execução do programa, gerando erros de tempo de execução chamados de "panics".
Isso interrompe a execução do programa, o que é útil quando continuar não faria sentido.
Um panic pode ocorrer de forma inadvertida, por meio de ações que fazem o código entrar em panic (por exemplo, acessar um array além dos seus limites), ou de forma deliberada, ao chamar a função panic.
fn main() {
let data = array![1, 2];
if true {
panic(data);
}
println!("This line will not be reached.");
}
Rodar este exemplo imprimiria o seguinte no console:
Run panicked with [1, 2, ].
Há 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 seu código.
Se a condição em assert! for avaliada como false, o programa vai entrar em panic com uma mensagem de erro do tipo ByteArray.
Isso costuma ser usado para verificar suposições durante o desenvolvimento e garantir que os valores atendam a certos critérios.
Por exemplo:
fn main() {
let x = 5;
assert!(x > 0, "x must be greater than zero");
}
Se x não for maior que zero, o programa vai entrar em panic com a mensagem "x must be greater than zero".
O assert! é útil para verificar invariantes e pré-condições sem escrever manualmente o código de tratamento de erros.
nopanic
A notação nopanic do Cairo indica que uma função nunca vai entrar em panic.
fn function_never_panic() -> felt252 nopanic {
108
}
Uma função nopanic só pode ser chamada em uma função também anotada como nopanic.
panic_with
O atributo panic_with do Cairo marca uma função que retorna um Option ou um Result e cria uma função wrapper para a sua função anotada, que vai entrar em panic se a função retornar 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 erros de forma elegante, fornecendo feedback em vez de travar ou produzir 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 ? é usado para um tratamento de erros conciso e implícito, deixando a função chamadora lidar com quaisquer erros que possam ocorrer.
Quando você usa o operador ? em um tipo Result ou Option, ele faz o seguinte:
Result::Ok(x) ou Option::Some(x), ele retorna o valor interno x diretamente.Result::Err(e) ou Option::None, ele propaga o erro ou o None, retornando 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),
}
}