İçeriğe geç
academia.sh

Ders 10 / 15

Özel Hata Tipleri

Soru işaretinin, çağıran taraf hiçbir dönüşüm çağrısı yazmadan bir hata tipini başka bir hata tipine çevirdiğinin ve bu örtük dönüşümün — elle yazılan bir dönüşümün aksine — çağıran taraftaki bağlam bilgisini taşıyamadığının ölçülmesi.

Yayın Güncelleme

Bu derste

  • Soru işaretinin bir hata dönüşümünü çağıran taraf hiçbir şey yazmadan çalıştırdığını koşturarak gösterir
  • Bir hata zincirinin kaç halkasında dönüşümün gerçekten koştuğunu sayarak doğrular
  • Örtük dönüşümün çağıran taraftaki bağlam bilgisini kaybettiğini elle yazılan dönüşümle karşılaştırarak ayırt eder
İçindekiler

Hata Yayılımının Kısa Yazımı dersi, soru işaretinin farklı hata tiplerini birbirine dönüştürdüğünü gösterdi, ama bu dönüşümün nasıl gerçekleştiğine girmedi — yalnız kullandı. Bu ders o dönüşümü açıyor: bir hata bir tipten başka bir tipe geçerken, bu geçişi kim çağırıyor ve bu çağrı çağıran tarafın kaynağında görünür mü?

Önceki derste kurulan impl From<std::num::ParseIntError> for SayiHatasi bloğu bu sorunun kilidini açan parça. O ders bu bloğu yalnız soru işaretinin çalışabilmesi için gerekli bir ön koşul olarak kullandı; bu ders aynı bloğun kendisini inceliyor — içinde ne kuruluyor, ne atılıyor ve bu kurma işlemi kim tarafından, kaç kez tetikleniyor.

Arayüzler, Hatalar ve Genellikler kursu (M09/K02) hata sarmalamayı (error wrapping) ve hata zincirini ölçmüştü — bir hatanın başka bir hatayı içinde taşıyıp taşımadığını, hata kimliğinin zincirde nasıl korunduğunu. Bu ders sarmalama değil dönüşüm ölçüyor: bir hata tipi, kendi hâlini koruyarak değil, tamamen başka bir tipe çevrilerek ilerliyor. Rust bu iki düzeneği ayrı tutar — sarmalama M09/K02’nin konusuydu, dönüşüm burada.

Sarmalama, bir hatayı bir zarfa koyar; zarfın içinde orijinal hata bozulmadan durur ve gerektiğinde geri çıkarılabilir. Dönüşüm ise orijinal hatayı bozar: kaynak tip elden çıkar, yerine yeni bir tip gelir ve o yeni tip artık eskisine dönemez — meğerki eskisini kendi içinde bir alan olarak sakladı. Bu ders bu ikinci düzeneği, SayiHatasinin ParseIntErrori kendi içinde saklamadığı, yalnız onu bir sinyale indirgediği bir örnekle ölçüyor. From gerçekleştirimi bu indirgemenin nerede yapıldığını gösteriyor: kaynak hatanın verisi, hedef tip kuruluyorken elde tutulur ya da atılır — bu, gerçekleştirimi yazan kişinin kararı.

Bu konunun ilk üç dersi, Option ve Resultın kendi başarı ve başarısızlık kollarının kaç kez çalıştığını ölçtü; kollardaki hata değerlerinin kendisi önemsizdi, SayiHatasi hiçbir bilgi taşımayan boş bir yapıydı. Bu ders o boşluğu dolduruyor: SayiHatasi artık bir alan taşıyor ve bu alanın doğru mu yanlış mı dolduğu, dönüşümün nerede ve nasıl yapıldığına bağlı hâle geliyor.

Ders başlığındaki “özel” sözcüğü, bir hata tipinin kaynağa özgü olarak tasarlandığını işaret ediyor: SayiHatasi standart kütüphanenin bir parçası değil, bu programın kendi başarısızlık biçimini taşımak için kurulmuş bir yapı. Gerçek bir programda böyle bir tip tek bir alan yerine bir numaralandırma da olabilir — her varyant farklı bir başarısızlık türünü taşıyabilir (bir alanın hiç bulunmaması, bir sayının aralık dışı olması, bir biçimin uyuşmaması gibi). Bu ders bu çeşitliliği kurmuyor; tek bir yapı ve tek bir alanla, dönüşümün kim tarafından çağrıldığı sorusuna odaklanıyor. Çeşitlenmiş bir hata tipinin her varyantı da aynı From mekaniğini kullanabilir; ölçülen eksen değişmez.

Kim Çağırıyor: Örtük Dönüşüm

Bir hata tipi, başka bir hata tipinden From gerçekleştirimiyle üretilebiliyorsa, soru işareti bu üretimi otomatik olarak çağırıyor. Çağıran tarafın kaynağında bu çağrıyı gösteren hiçbir satır yok — ? yalnız oradaki tek karakter.

Aşağıdaki koşum, üç alanlı bir kaydı ayrıştıran küçük bir zinciri temsil ediyor: yas, puan, deneme alanlarının her biri halka işleviyle sırayla denetleniyor. Zincirin kendisi doğrusal — bir alan diğerini beklemiyor, hepsi bağımsız çağrılıyor — ama “zincir” sözü burada üç ayrı halkanın toplamını anlatıyor: kaç halkada dönüşümün gerçekten çalıştığı, üçünün toplamından okunuyor.

// donusum.rs — kere.rs'in SayiHatasi/From kesiti: kac halkada donusum kostu
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Defter = Rc<RefCell<BTreeMap<String, u32>>>;

fn say(defter: &Defter, ad: &str) {
    *defter.borrow_mut().entry(ad.to_string()).or_insert(0) += 1;
}
fn kere(defter: &Defter, ad: &str) -> u32 {
    *defter.borrow().get(ad).unwrap_or(&0)
}

struct SayiHatasi {
    alan: String,
}

impl From<std::num::ParseIntError> for SayiHatasi {
    fn from(_: std::num::ParseIntError) -> SayiHatasi {
        SayiHatasi { alan: String::from("bilinmiyor") }
    }
}

fn halka(_alan: &str, metin: &str) -> Result<i32, SayiHatasi> {
    let sayi: i32 = metin.parse()?;
    Ok(sayi)
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));
    let kayit = [("yas", "otuz"), ("puan", "78"), ("deneme", "uc")];

    for (alan, deger) in kayit {
        match halka(alan, deger) {
            Ok(sayi) => println!("{:<8} basarili: {}", alan, sayi),
            Err(hata) => {
                say(&defter, "hata-donusumu");
                println!("{:<8} basarisiz, kayitli alan: {}", alan, hata.alan);
            }
        }
    }
    println!();
    println!("zincirdeki halka sayisi: {}", kayit.len());
    println!("donusum kosan halka: {} kez", kere(&defter, "hata-donusumu"));
}
yas      basarisiz, kayitli alan: bilinmiyor
puan     basarili: 78
deneme   basarisiz, kayitli alan: bilinmiyor

zincirdeki halka sayisi: 3
donusum kosan halka: 2 kez

HT10. halkanın gövdesinde metin.parse()? dışında hiçbir satır SayiHatasiden söz etmiyor — parsein ürettiği ParseIntError, halkanın dönüş tipi olan SayiHatasie otomatik dönüşüyor, çünkü SayiHatasi için bir impl From<ParseIntError> yazılı. kayitteki üç halkadan ikisi ("yas" ve "deneme") ayrıştırılamıyor ve hata-donusumu iki kez artıyor; "puan" ayrıştırılabildiği için o halkada dönüşüm hiç çalışmıyor. Zincirdeki halka sayısı sabit (üç) kalırken, dönüşümün kaç halkada gerçekten koştuğu girdiye bağlı — burada üçte iki.

HT11. Yazdırılan alan değerlerinin ikisi de "bilinmiyor" — gerçekte başarısız olan alanlar "yas" ve "deneme" olsa da, hata değerinin kendisi bunu bilmiyor. From gerçekleştiriminin imzası sabit: yalnız ParseIntErrori alıyor, halkanın hangi alan argümanıyla çağrıldığını bilmiyor. Dönüşüm gerçekten çalışıyor — hata-donusumu sayacı bunu kanıtlıyor — ama çağıran taraf (halka içindeki ?) bu dönüşüme hiçbir bağlam geçiremiyor, çünkü dönüşümü çağıran, halkanın kendisi değil, derleyicinin ? için ürettiği koddur.

halka işlevinin gövdesinde SayiHatasiden söz eden tek yer dönüş tipi (Result<i32, SayiHatasi>); gövdenin kendisi yalnız metin.parse()? yazıyor. Bu, kısa yazımın önceki derste ölçülen faydasının aynısı — kaynak kısa kalıyor — ama burada bedeli de görünüyor: kaynak kısaldıkça, o kaynağın çağırabileceği bilgi de kısıtlanıyor. halkanın çağıranı alanı biliyor (kayitteki ilk eleman), ama halkanın kendi gövdesi bu bilgiyi SayiHatasie aktaracak bir yol taşımıyor, çünkü aktarım ?nin arkasında, halkanın kontrolü dışında gerçekleşiyor.

hata-donusumu sayacının ikisi de “her seferinde” sınıfına giriyor: girdi geçersiz her alan için, dönüşüm kaçınılmaz olarak koşuyor — koşullu değil, ihtimale bağlı değil, girdi geçersizse dönüşüm de kesinlikle çalışıyor. Bu, önceki dersteki tembel-varsayilan-gerekli ya da soru-sonrasi-basarili gibi “yalnız gerektiğinde” sınıfından farklı: oradaki sayı girdiye bağlı olarak sıfır ya da bir olabilirdi, burada her başarısız halka için dönüşüm kesin olarak bir kez koşuyor, koşulsuz.

kayit dizisindeki üç eleman, koşumun her tekrarında aynı sırayla işleniyor; bu sırayı değiştirmek hata-donusumu sayısını değiştirmiyor, çünkü her halka bağımsız değerlendiriliyor — önceki bir halkanın başarılı ya da başarısız olması, sonraki halkanın sonucunu etkilemiyor. Bu, önceki derste bir doğrulama işlem hattı olarak tanıtılan aynı düzenin devamı: burada her halkanın ek olarak bir dönüşüm çalıştırıp çalıştırmadığı da sayılıyor. Elde edilen “2 kez” sayısı, üç halkadan tam olarak kaçının başarısız olduğunu ve o başarısızlığın her birinde dönüşümün gerçekten tetiklendiğini birlikte söylüyor.

Sınırlayıcı Ölçüm

Her hata dönüşümü bilgi kaybedebilir. Dönüşen tip, kaynağı taşımıyorsa, kaybedilen bilgi elle taşınmadıkça geri getirilemez. Bu, SayiHatasinin özel bir eksikliği değil, From imzasının doğal bir sonucu: fn from(deger: Kaynak) -> Hedef yalnız Kaynak tipindeki değeri alıyor, çağrıldığı yerin çevresindeki hiçbir değişkeni görmüyor. Bu sınır her From gerçekleştirimi için aynı — yalnız SayiHatasi gibi kaynak hatanın dışında bir bilgiye (alan adı gibi) ihtiyaç duyan tipler için görünür hâle geliyor. Aşağıdaki koşum aynı senaryoyu, dönüşümü elle yazarak tekrarlıyor.

// elle_donusum.rs — donusumu cagiran taraf acikca yaziyor, baglami kaybetmiyor
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Defter = Rc<RefCell<BTreeMap<String, u32>>>;

fn say(defter: &Defter, ad: &str) {
    *defter.borrow_mut().entry(ad.to_string()).or_insert(0) += 1;
}
fn kere(defter: &Defter, ad: &str) -> u32 {
    *defter.borrow().get(ad).unwrap_or(&0)
}

struct SayiHatasi {
    alan: String,
}

fn halka_elle(alan: &str, metin: &str) -> Result<i32, SayiHatasi> {
    metin
        .parse::<i32>()
        .map_err(|_| SayiHatasi { alan: alan.to_string() })
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));
    let kayit = [("yas", "otuz"), ("puan", "78"), ("deneme", "uc")];

    for (alan, deger) in kayit {
        match halka_elle(alan, deger) {
            Ok(sayi) => println!("{:<8} basarili: {}", alan, sayi),
            Err(hata) => {
                say(&defter, "hata-donusumu");
                println!("{:<8} basarisiz, kayitli alan: {}", alan, hata.alan);
            }
        }
    }
    println!();
    println!("zincirdeki halka sayisi: {}", kayit.len());
    println!("donusum kosan halka: {} kez", kere(&defter, "hata-donusumu"));
}
yas      basarisiz, kayitli alan: yas
puan     basarili: 78
deneme   basarisiz, kayitli alan: deneme

zincirdeki halka sayisi: 3
donusum kosan halka: 2 kez

HT12. Sayılar birebir aynı: üç halkadan iki tanesinde dönüşüm koşuyor. Ama alan değerleri artık doğru — "yas" başarısız olduğunda hata "yas" yazıyor, "deneme" başarısız olduğunda hata "deneme" yazıyor. Fark, map_erre verilen kapanışın alan parametresini yakalayabilmesi: halka_ellenin kendi kapsamındaki alan değişkeni, kapanışın içinden erişilebilir durumda. From::from böyle bir yakalamaya izin vermiyor — imzası yalnız kaynak hatayı alıyor, çağıran bağlamdan hiçbir şey almıyor. Bu, kısa yazımın (?) neden bazen yetersiz kaldığının somut kanıtı: kısa yazım hızlı ve kısa, ama yalnız hedef tipin kaynak hatadan üretebileceği kadar bilgi taşıyabiliyor; kaynak hatanın dışındaki bir bağlamı (burada alan adı) taşıması gerekiyorsa, elle yazılan bir dönüşüm gerekiyor.

Bu sınır genel bir kurala işaret ediyor: bir dönüşüm, hedef tip kaynağı ya da onun bağlamını tutmuyorsa geri dönüşsüzdür. SayiHatasinin alan alanı olması bu kaybı görünür kılıyor — alan boş bir yapı olsaydı (önceki iki derste olduğu gibi), kaybedilecek bir şey de olmazdı, çünkü taşınacak bilgi zaten yoktu. Bilgi taşıyan bir hata tipi tasarlandığında, bu tasarım kararı da beraberinde geliyor: örtük dönüşüm mü, yoksa elle yazılan ve bağlamı koruyan bir dönüşüm mü?

halka ile halka_elle arasındaki fark, önceki iki derste görülen “aynı sayı, farklı kaynak uzunluğu” örneğinin bir varyasyonu. Orada kısa yazım ile elle yazım aynı sayıyı veriyordu ve fark yalnız satır sayısındaydı; burada da sayı aynı kalıyor, ama fark artık satır sayısında değil, hatanın taşıdığı bilgide. Kısa yazımın kazandırdığı kısalık, bu kez bir bedelle geliyor — ve o bedel, koşum sayacıyla değil, yazdırılan alan değeriyle görünür oluyor.

Bu iki tasarım arasında seçim yaparken sayı tek başına yeterli bir ölçüt değil — iki koşum da aynı “donusum kosan halka: 2 kez” satırını verdi. Karar, kaynağın ne kadar kısa kalacağı ile hatanın ne kadar bilgi taşıyacağı arasında. halka bir satırlık gövdesiyle en kısa yazımı temsil ediyor, ama hatası her zaman "bilinmiyor" diyor. halka_elle üç satırlık bir map_err çağrısı taşıyor, ama hatası doğru alanı söylüyor. Bir kod tabanında her iki yazım da bulunabilir: hatanın hangi alandan geldiği önemli değilse (yalnız “bir şey başarısız oldu” bilgisi yeterliyse) kısa yazım tercih edilebilir; hatanın kaynağı kullanıcıya ya da bir günlük kaydına yansıyacaksa, elle yazılan dönüşüm gerekir.

Aynı örtük/elle ayrımı, dönüşümün maliyeti açısından da bir soru doğuruyor — ama bu ders o maliyeti ölçmüyor. From::fromün gövdesi burada tek bir yapı kurmaktan ibaret; map_errin kapanışı da aynı işi yapıyor. İkisinin de kaç kez çalıştığı yukarıda ölçüldü (iki), ikisinin de gövdesi aynı büyüklükte. Fark yalnız neyin kurulduğunda: biri sabit bir dizge kuruyor, öbürü çağıran bağlamdan gelen bir dizgeyi.

Trait Tanımı ve Gerçekleştirimi dersi, bir trait’in varsayılan gövdesinin bir tipte hiç çalışmayabileceğini, başka bir tipte çalışabileceğini ölçmüştü — aynı sözleşmenin iki farklı gerçekleştirimi, iki farklı koşum sayısı verebiliyordu. From<ParseIntError> for SayiHatasi da bir trait gerçekleştirimi ve bu ders aynı ilkeyi taşıyor: gerçekleştirim kaynakta tek bir kez yazılır, ama koşumda girdiye bağlı olarak sıfır ya da daha çok kez çağrılabilir. Farkı, bu gerçekleştirimin çağrısının kaynakta hiçbir zaman açıkça görünmemesi — ?nin arkasında kalması.

Özet

  • Bir hata tipi başka bir hata tipinden From ile üretilebiliyorsa, soru işareti bu üretimi otomatik çağırır; çağıran tarafın kaynağında bu çağrıyı gösteren bir satır bulunmaz.
  • Bir zincirdeki halkaların hangilerinde dönüşümün gerçekten koştuğu sayılabilir; burada üç halkadan ikisinde dönüşüm her seferinde koşuyor — her başarısız halka için kaçınılmaz, koşullu değil.
  • From::fromün imzası yalnız kaynak hatayı alır; çağıran bağlamdan (örneğin hangi alanın başarısız olduğu) hiçbir bilgi alamaz.
  • Elle yazılan bir dönüşüm (map_err ile bir kapanış), çağıran kapsamdaki değişkenleri yakalayabildiği için bu bilgiyi koruyabilir — aynı sayıyı verirken daha fazla bilgi taşır.
  • Her hata dönüşümü bilgi kaybedebilir; hedef tip kaynağı ya da bağlamı taşımıyorsa, bu kayıp geri döndürülemez.
  • Seçim, kaynağın kısalığı ile hatanın taşıdığı bilginin zenginliği arasındadır; hangisi seçilirse seçilsin, dönüşümün kaç kez koştuğu sayı olarak aynı kalır.

Sonraki Adım

Bu konunun ilk dört dersi, bir işlemin başarısız olabileceği durumları ölçtü — Optionın yokluğu, Resultın kolları, kısa yazımın sessiz atlaması, dönüşümün bilgi kaybı. Hepsinde ortak olan bir şey var: çağıran taraf başarısızlığı bir değer olarak aldı ve ne yapacağına kendisi karar verdi — devam etti, bir yedek kullandı, erken döndü, bilgiyi taşıdı ya da kaybetti. Ama karar her zaman çağıranındı.

Panik ve Kurtarılamaz Hatalar dersi, bunun tam tersini ölçüyor: bir başarısızlığın hiçbir değer üretmeden, çağırana hiçbir seçim bırakmadan, kalan gövdeyi olduğu gibi durdurduğu bir tasarım. Result dönen bir işlev, başarısızlığını bir veri olarak sunar ve çağıranın onu görmezden gelmesine bile izin verir; bu konunun son dersinde ölçülen tasarım, tam tersine, görmezden gelinemeyen bir durdurma modelliyor — ve bu iki tasarımın aynı başarısızlık için verdiği kalan-gövde sayılarının nasıl ayrıştığını karşılaştırıyor.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat