---
title: 'Özel Hata Tipleri'
source: 'https://academia.sh/tr/kurslar/rust-soyutlama/ozel-hata-tipleri'
course: "Trait'ler, Genellikler ve Hata Yönetimi"
language: tr
updated: '2026-10-21T05:03:45+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - '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'
---

# Ö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.

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, `SayiHatasi`nin
`ParseIntError`i 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.

```rust
// 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.** `halka`nın gövdesinde `metin.parse()?` dışında hiçbir satır `SayiHatasi`den söz
etmiyor — `parse`in ürettiği `ParseIntError`, `halka`nın dönüş tipi olan `SayiHatasi`e
otomatik dönüşüyor, çünkü `SayiHatasi` için bir `impl From<ParseIntError>` yazılı.
`kayit`teki üç 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 `ParseIntError`i alıyor, `halka`nı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, `halka`nın kendisi değil, derleyicinin `?` için
ürettiği koddur.

`halka` işlevinin gövdesinde `SayiHatasi`den 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. `halka`nın çağıranı
`alan`ı biliyor (`kayit`teki ilk eleman), ama `halka`nın kendi gövdesi bu bilgiyi
`SayiHatasi`e aktaracak bir yol taşımıyor, çünkü aktarım `?`nin arkasında, `halka`nı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, `SayiHatasi`nin ö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.

```rust
// 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_err`e verilen kapanışın `alan`
parametresini *yakalayabilmesi*: `halka_elle`nin 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. `SayiHatasi`nin `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_err`in 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.
