Kurtarılabilir Hatalar ile Result

Çoğu hata, programın tamamen durmasını gerektirecek kadar ciddi değildir. Bazen, bir fonksiyon başarısız olduğunda, kolayca yorumlayıp yanıt verebileceğiniz bir nedenle başarısız olur. Örneğin, iki büyük tamsayı eklemeye çalıştığınızda ve işlem, toplam maksimum temsil edilebilir değeri aştığı için taşarsa, tanımsız davranışa neden olmak veya işlemi sonlandırmak yerine bir hata veya sarılı bir sonuç döndürmek isteyebilirsiniz.

The Result Enum

Recall from Generic data types section in Chapter 8 that the Result enum is defined as having two variants, Ok and Err, as follows:

enum Result<T, E> {
    Ok: T,
    Err: E,
}

Result<T, E> enumu iki genel tür, T ve E içerir ve iki varyanta sahiptir: Ok, T türünde bir değer tutar ve Err, E türünde bir değer tutar. Bu tanım, bir işlemin başarılı olabileceği (bir T türünde değer döndürerek) veya başarısız olabileceği (bir E türünde değer döndürerek) herhangi bir yerde Result enumunu kullanmayı uygun hale getirir.

ResultTrait

ResultTrait özelliği, Result<T, E> enumu ile çalışmak için yöntemler sağlar, örneğin değerleri açma, Result'un Ok veya Err olup olmadığını kontrol etme ve özel bir mesajla panik yapma. ResultTraitImpl uygulaması, bu yöntemlerin mantığını tanımlar.

trait ResultTrait<T, E> {
    fn expect<+Drop<E>>(self: Result<T, E>, err: felt252) -> T;

    fn unwrap<+Drop<E>>(self: Result<T, E>) -> T;

    fn expect_err<+Drop<T>>(self: Result<T, E>, err: felt252) -> E;

    fn unwrap_err<+Drop<T>>(self: Result<T, E>) -> E;

    fn is_ok(self: @Result<T, E>) -> bool;

    fn is_err(self: @Result<T, E>) -> bool;
}

expect ve unwrap yöntemleri, her ikisi de Result<T, E>'yi Ok varyantında olduğunda T tipindeki değeri çıkarmaya çalıştıkları için benzerdir. Result Ok(x) ise, her iki yöntem de değer x'i döndürür. Ancak, iki yöntem arasındaki temel fark, Result Err varyantında olduğunda gösterilen davranıştadır. expect yöntemi, panik yapılırken kullanılacak özel bir hata mesajı (felt252 değeri olarak) sağlamanıza izin verir, bu da panik üzerinde daha fazla kontrol ve bağlam sağlar. Öte yandan, unwrap yöntemi, varsayılan bir hata mesajı ile panik yapar ve panik nedeni hakkında daha az bilgi sağlar.

The expect_err and unwrap_err methods have the exact opposite behavior. If the Result is Err(x), both methods return the value x. However, the key difference between the two methods is in case of Result::Ok(). The expect_err method allows you to provide a custom error message (as a felt252 value) that will be used when panicking, giving you more control and context over the panic. On the other hand, the unwrap_err method panics with a default error message, providing less information about the cause of the panic.

A careful reader may have noticed the <+Drop<T>> and <+Drop<E>> in the first four methods signatures. This syntax represents generic type constraints in the Cairo language, as seen in the previous chapter. These constraints indicate that the associated functions require an implementation of the Drop trait for the generic types T and E, respectively.

Son olarak, is_ok ve is_err metodları, bir Result enum değerinin varyantını kontrol etmek için ResultTrait özelliği tarafından sağlanan yardımcı fonksiyonlardır.

  • is_ok, bir Result<T, E> değerinin anlık görüntüsünü alır ve Result Ok varyantıysa, yani işlem başarılı olduysa true döner. Result Err varyantıysa, false döner.
  • is_err takes a snapshot of a Result<T, E> value and returns true if the Result is the Err variant, meaning the operation encountered an error. If the Result is the Ok variant, it returns false.

These methods are helpful when you want to check the success or failure of an operation without consuming the Result value, allowing you to perform additional operations or make decisions based on the variant without unwrapping it.

ResultTrait uygulamasını burada bulabilirsiniz.

It is always easier to understand with examples. Have a look at this function signature:

fn u128_overflowing_add(a: u128, b: u128) -> Result<u128, u128>;

It takes two u128 integers, a and b, and returns a Result<u128, u128> where the Ok variant holds the sum if the addition does not overflow, and the Err variant holds the overflowed value if the addition does overflow.

Şimdi, bu fonksiyonu başka bir yerde kullanabiliriz. Örneğin:

fn u128_checked_add(a: u128, b: u128) -> Option<u128> {
    match u128_overflowing_add(a, b) {
        Result::Ok(r) => Option::Some(r),
        Result::Err(r) => Option::None,
    }
}

Here, it accepts two u128 integers, a and b, and returns an Option<u128>. It uses the Result returned by u128_overflowing_add to determine the success or failure of the addition operation. The match expression checks the Result from u128_overflowing_add. If the result is Ok(r), it returns Option::Some(r) containing the sum. If the result is Err(r), it returns Option::None to indicate that the operation has failed due to overflow. The function does not panic in case of an overflow.

Let's take another example:

fn parse_u8(s: felt252) -> Result<u8, felt252> {
    match s.try_into() {
        Option::Some(value) => Result::Ok(value),
        Option::None => Result::Err('Invalid integer'),
    }
}

In this example, the parse_u8 function takes a felt252 and tries to convert it into a u8 integer using the try_into method. If successful, it returns Result::Ok(value), otherwise it returns Result::Err('Invalid integer').

İki test vakamız şunlardır:

fn parse_u8(s: felt252) -> Result<u8, felt252> {
    match s.try_into() {
        Option::Some(value) => Result::Ok(value),
        Option::None => Result::Err('Invalid integer'),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_felt252_to_u8() {
        let number: felt252 = 5;
        // should not panic
        let res = parse_u8(number).unwrap();
    }

    #[test]
    #[should_panic]
    fn test_felt252_to_u8_panic() {
        let number: felt252 = 256;
        // should panic
        let res = parse_u8(number).unwrap();
    }
}


Don't worry about the #[cfg(test)] attribute for now. We'll explain in more detail its meaning in the next Testing Cairo Programs chapter.

#[test] attribute means the function is a test function, and #[should_panic] attribute means this test will pass if the test execution panics.

The first one tests a valid conversion from felt252 to u8, expecting the unwrap method not to panic. The second test function attempts to convert a value that is out of the u8 range, expecting the unwrap method to panic with the error message Invalid integer.

The ? Operator

Son olarak bahsedeceğimiz operatör ? operatörüdür. ? operatörü, daha özdeş ve özlü hata işleme için kullanılır. Bir Result veya Option türü üzerinde ? operatörünü kullandığınızda, aşağıdakileri yapacaktır:

  • Değer Result::Ok(x) veya Option::Some(x) ise, iç değeri x'i doğrudan döndürür.
  • Değer Result::Err(e) veya Option::None ise, hatayı veya None'ı hemen fonksiyondan dönerek yayılır.

? operatörü, hataları dolaylı olarak ele almak ve çağıran fonksiyonun bunlarla ilgilenmesine izin vermek istediğinizde kullanışlıdır.

İşte bir örnek

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)
}

We can see that do_something_with_parse_u8 function takes a felt252 value as input and calls parse_u8 function. The ? operator is used to propagate the error, if any, or unwrap the successful value.

Ve küçük bir test vakası ile:

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)
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_function_2() {
        let number: felt252 = 258;
        match do_something_with_parse_u8(number) {
            Result::Ok(value) => println!("Result: {}", value),
            Result::Err(e) => println!("Error: {}", e),
        }
    }
}

The console will print the error Invalid Integer.

Özet

We saw that recoverable errors can be handled in Cairo using the Result enum, which has two variants: Ok and Err. The Result<T, E> enum is generic, with types T and E representing the successful and error values, respectively. The ResultTrait provides methods for working with Result<T, E>, such as unwrapping values, checking if the result is Ok or Err, and panicking with custom messages.

Kurtarılabilir hataları ele almak için, bir fonksiyon Result türünde bir değer döndürebilir ve bir işlemin başarısı veya başarısızlığını ele almak için desen eşleştirme kullanabilir. ? operatörü, hatayı dolaylı olarak ele almak veya başarılı değeri açmak için kullanılabilir. Bu, daha özlü ve net hata işleme sağlar, burada çağıran, çağrılan fonksiyon tarafından yükseltilen hataları yönetmekten sorumludur.