Ders 09 / 15
Hata Yayılımının Kısa Yazımı
Bir hatayı denetleyip erken dönmeyi tek bir işarete indirgeyen kısa yazımın, aynı işi elle yazılan bir dallanmayla birebir aynı sayıları verdiğinin — kaynağı kısalttığının, koşumu değiştirmediğinin — ölçülmesi.
Yayın Güncelleme
Bu derste
- Soru işaretinden sonraki gövdenin başarılı çözümlemede bir kez, başarısızda hiç çalışmadığını koşturarak gösterir
- Kısa yazımın elle yazılan dallanmayla aynı sayıları verdiğini karşılaştırarak doğrular
- Erken dönüşün kalan gövdeyi sessizce atladığını ve atlanan adımın yan etkisi olmadığını örnekle ayırt eder
İçindekiler
Başarısızlığın Dönüş Değeri dersi, aynı işlevin art arda çağrıldığında başarı ve
başarısızlık kollarının birbirinden bağımsız çalıştığını, her ikisinin de aynı match
gövdesinde üç satırlık bir düzeni tekrarladığını gösterdi: değeri ayrıştır, hata varsa
bildir, yoksa devam et. Bir işlev birden çok ayrıştırma yapıyorsa bu düzen aynı biçimde art
arda tekrarlanır — her ayrıştırmadan sonra aynı iki kollu match. Bu ders, bu tekrarı tek
bir işarete indirgeyen kısa yazımı ölçüyor: soru işareti (?).
Bu konunun üçüncü dersi olarak, bu ders artık başarı ve başarısızlığın ayrı gövdeler
olduğunu varsayıyor — o ayrım önceki derste kuruldu. Burada sorulan soru bir adım öteye
geçiyor: bu iki gövdeyi ayıran match iskeleti, kaynakta ne kadar kısalabilir ve bu
kısalma koşumdaki sayıları değiştirir mi? Yanıt, aşağıdaki iki koşumun karşılaştırılmasıyla
ortaya çıkıyor.
Arayüzler, Hatalar ve Genellikler kursu (M09/K02) bir hatanın bir dönüş değeri olarak taşındığını, kontrol akışının dışına çıkmadığını kurmuştu. Soru işareti de bu kararı bozmuyor — hâlâ bir dönüş değeri üretiyor, hâlâ normal fonksiyon dönüşü kullanıyor — yalnız o dönüşü yazmanın kaynaktaki uzunluğunu kısaltıyor. Bu ders M09/K02’nin ölçtüğü nerede sorusuna dönmüyor; ölçtüğü, kısa yazımın koşumu değiştirip değiştirmediği.
Fonksiyonlar ve İfade Odaklılık dersi (M10/K01), bir işlevin gövdesi bitmeden erken
dönmenin ne zaman gerektiğini ölçmüştü: gövdenin geri kalanı hesaplanmayacaksa, açık bir
return bu durumu kaynakta görünür kılıyordu. Soru işareti tam olarak bu erken dönüşü
kullanıyor — yalnız return yazımını elle yazmak yerine, bir Result çağrısının hemen
ardına tek bir işaret koyarak. Bu ders o dersin kurduğu erken dönüş kavramını yeniden
kurmuyor; yalnız bu belirli kısa yazımın, elle yazılan bir erken dönüşle aynı sayıları
verip vermediğini ölçüyor.
Aynı Sayılar, Daha Kısa Kaynak
Soru işareti, bir Result döndüren bir işlevin içinde, başka bir Result döndüren bir
çağrının hemen ardına yazılır. Çağrı Ok dönerse, içindeki değer açılır ve gövde kaldığı
yerden devam eder; Err dönerse, işlev o anda kendi Erriyle döner ve gövdenin geri kalanı
hiç çalışmaz.
Bu kısayolun bir koşulu var: soru işareti yalnız, içinde bulunduğu işlevin dönüş tipi de
bir Result (ya da Option) olduğunda yazılabilir. main işlevinin dönüş tipi () ise,
mainin gövdesinde bir Result üreten bir çağrının ardına doğrudan ? konamaz — erken
dönüşün döneceği bir Result yuvası yok. Bu ders bu koşulu ihlal etmiyor: cozumle kendi
Result dönüş tipini taşıyor, ? yalnız onun gövdesinde kullanılıyor; main içindeki
çağrılar ise dönen Resultı bir değişkene bağlayıp {:?} ile yazdırıyor, kendisi ?
kullanmıyor.
Aşağıdaki cozumle işlevinde metin.parse() bir Result<i32, ParseIntError> döndürür,
ama işlevin kendi dönüş tipi Result<i32, SayiHatasi>dir — iki farklı hata tipi. Soru
işareti bu farkı kendi başına çözer: SayiHatasi için yazılan impl From<ParseIntError>
gerçekleştirimi sayesinde, parsein ürettiği hata otomatik olarak SayiHatasie
dönüştürülür. Bu dönüşümün kendisi Özel Hata Tipleri dersinin konusu; bu derste yalnız
?nin bu dönüşümü kullandığı, ama bu derste ölçülen sayıyı etkilemediği görülüyor.
// sorulu.rs — kere.rs'in cozumle kesiti: basarili columdan sonrasi bir kez, basarisizda hic kosuyor
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)
}
#[derive(Debug)]
struct SayiHatasi;
impl From<std::num::ParseIntError> for SayiHatasi {
fn from(_: std::num::ParseIntError) -> SayiHatasi {
SayiHatasi
}
}
fn cozumle(defter: &Defter, metin: &str, etiket: &str) -> Result<i32, SayiHatasi> {
let sayi: i32 = metin.parse()?;
say(defter, etiket);
Ok(sayi * 2)
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));
let basarili = cozumle(&defter, "21", "soru-sonrasi-basarili");
let basarisiz = cozumle(&defter, "yirmibir", "soru-sonrasi-basarisiz");
println!("basarili cagri: {:?}", basarili);
println!("basarisiz cagri: {:?}", basarisiz);
println!();
for ad in ["soru-sonrasi-basarili", "soru-sonrasi-basarisiz"] {
println!("{:<24}{} kez", ad, kere(&defter, ad));
}
}
basarili cagri: Ok(42) basarisiz cagri: Err(SayiHatasi) soru-sonrasi-basarili 1 kez soru-sonrasi-basarisiz 0 kez
HT7. cozumle(&defter, "21", ...) çağrısında metin.parse() başarıyla 21i üretiyor;
soru işareti bu değeri açıyor, sayiya bağlıyor ve gövde kaldığı yerden devam ediyor —
say çağrısı çalışıyor, satır soru-sonrasi-basarili bir kez artıyor. cozumle(&defter, "yirmibir", ...) çağrısında ise parse bir hata üretiyor; soru işareti bu hatayı
SayiHatasie çeviriyor ve işlevden hemen dönüyor. say çağrısına hiç sıra gelmiyor —
soru-sonrasi-basarisiz sıfırda kalıyor. Bu, önceki dersteki Ok/Err kollarının aynı
davranışı: biri çalışıyor, öbürü çalışmıyor. Fark, bu iki kolun artık ayrı yazılmış bir
match içinde değil, tek bir işaretin arkasında saklı olması.
cozumle işlevinin dönüş tipine bakıldığında, gövdesinde yalnız bir satır (let sayi: i32 = metin.parse()?;) Result ile ilgili görünüyor; geri kalan iki satır (say çağrısı ve
Ok(sayi * 2)) sıradan kod gibi okunuyor. Bu, soru işaretinin kaynakta bıraktığı izin ne
kadar küçük olduğunu gösteriyor — bir işlev, kaç ayrı ayrıştırma yapıyorsa yapsın, her
ayrıştırmanın ardına tek bir karakter ekleyerek hata denetimini tamamlıyor. etiket
parametresinin saye geçirilen adı taşıması, bu derste iki farklı çağrıyı (başarılı ve
başarısız) aynı işlevin üzerinden, ama ayrı sayaç anahtarlarıyla izlemeyi sağlıyor; gerçek
bir kullanımda bu parametre bulunmaz, yalnız ölçüm amacıyla eklendi.
Aynı işlev elle yazılan bir dallanmayla da kurulabilir. Aşağıdaki koşum, soru işaretini
match ve erken return ile değiştiriyor ve aynı iki girdiyle çağrılıyor.
// elle_dallanma.rs — ayni is elle dallanmayla yazilinca da ayni sayilari veriyor
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)
}
#[derive(Debug)]
struct SayiHatasi;
fn cozumle_elle(defter: &Defter, metin: &str, etiket: &str) -> Result<i32, SayiHatasi> {
let ayristirma = metin.parse::<i32>();
let sayi = match ayristirma {
Ok(deger) => deger,
Err(_) => return Err(SayiHatasi),
};
say(defter, etiket);
Ok(sayi * 2)
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));
let basarili = cozumle_elle(&defter, "21", "soru-sonrasi-basarili");
let basarisiz = cozumle_elle(&defter, "yirmibir", "soru-sonrasi-basarisiz");
println!("basarili cagri: {:?}", basarili);
println!("basarisiz cagri: {:?}", basarisiz);
println!();
for ad in ["soru-sonrasi-basarili", "soru-sonrasi-basarisiz"] {
println!("{:<24}{} kez", ad, kere(&defter, ad));
}
}
basarili cagri: Ok(42) basarisiz cagri: Err(SayiHatasi) soru-sonrasi-basarili 1 kez soru-sonrasi-basarisiz 0 kez
HT8. İki koşum birebir aynı sayıları veriyor: soru-sonrasi-basarili bir, soru-sonrasi-basarisiz
sıfır. Kaynak farklı — ilkinde tek satır (metin.parse()?), ikincisinde beş satırlık bir
match — ama koşum aynı. Soru işareti yeni bir davranış eklemiyor; elle yazılan match ve
erken returnün derleyicinin ürettiği koduyla aynı işi, daha az yazarak yapıyor. Bu, kısa
yazımın koşumu değil kaynağı kısalttığının doğrudan kanıtı: iki program farklı uzunlukta
kaynağa sahip, ama aynı ikili davranışı üretiyor.
cozumle_elle işlevindeki match, önceki dersteki matchten bir farkla ayrılıyor: Err
kolu bir değer üretip gövdenin geri kalanına devretmiyor, doğrudan return Err(SayiHatasi)
ile işlevden çıkıyor. Bu, Denetim Akışı dersinde (M10/K01) kurulan matchin bir ifade
olması ilkesiyle çelişmiyor — return bir ifade değil bir deyim ve bir match kolunun
içinde bir deyim yazılabilir; o kolun ürettiği “değer” gövdenin geri kalanına hiç ulaşmıyor,
çünkü işlevin kendisinden çıkılıyor. Ok kolu ise sıradan bir değer (deger) üretiyor ve
bu değer sayiya bağlanıyor, gövde oradan devam ediyor. İki kolun farklı doğası —
biri işlevden çıkıyor, öbürü bir değer veriyor — soru işaretinin arkasında da aynen
duruyor, yalnız görünmez.
Kaynaktaki kısalmanın gerçek bedeli, bir işlev birden çok ayrıştırma yaptığında görülür.
Üç ayrı alanı sırayla ayrıştıran bir işlev, elle yazılan dallanmayla üç kez tekrarlanan
beş satırlık bir match (toplam on beş satır) gerektirirdi; aynı işlev soru işaretiyle üç
satıra iner — her alanın ardına tek bir işaret. Bu derste ölçülen iki koşum (bir satırlık
kısa yazım, beş satırlık elle yazım) bu farkın en küçük örneği; alan sayısı arttıkça
kaynaktaki fark da büyür, ama her iki yazımın da ürettiği soru-sonrasi-basarili /
soru-sonrasi-basarisiz çifti aynı kalır. Kısa yazımın büyümesi doğrusal değil — kaynak
satır sayısı azalırken, koşumun kendisi hiç değişmez.
Soru işareti Option üzerinde de aynı biçimde çalışır: içinde bulunduğu işlev Option
dönüyorsa, Some içindeki değeri açar, None durumunda işlevden hemen None döndürür. Bu
ders bu ikinci kullanımı koşturmuyor — Yokluğun Tiple İfadesi dersinde ölçülen Option
yöntemleri bir değer üretme (unwrap_or, unwrap_or_else) üzerine kuruluydu, burada
ölçülen ise erken dönüş. İki kullanım da aynı işareti taşısa da, biri bir değer üretir
(unwrap_or_elsein yaptığı gibi), öbürü işlevden çıkar; bu derste ölçülen yalnız
Result üzerindeki, işlevden çıkan biçimi.
Sınırlayıcı Ölçüm
Yukarıdaki koşumda soru-sonrasi-basarili ve soru-sonrasi-basarisiz her zaman birlikte
hareket ediyor: biri bir olduğunda öbürü sıfır, biri sıfır olduğunda öbürü bir. Bu, tek bir
gözlemin değil, aynı satırın iki farklı çağrıdaki durumunun sonucu — ikisi de aslında aynı
say(defter, etiket) çağrısını temsil ediyor, yalnız etiket argümanı çağırana göre
değişiyor.
HT9. Erken dönüş, kendisinden sonraki gövdeyi sessizce atlıyor — ne bir uyarı yazdırıyor
ne bir işaret bırakıyor. "yirmibir" girdisiyle yapılan çağrıda say çağrısı hiç
çalışmadığı için sayaç defterine hiçbir kayıt düşmüyor; bu, “adım atlandı” diye bir iz
bırakmıyor, yalnız beklenen kaydın yokluğuyla görünüyor. Atlanan adımın kendisi bir yan
etki üretmiş olsaydı — bir dosyaya yazma, bir sayacı artırma, bir günlük satırı basma — o
yan etki de gerçekleşmezdi. Bu ders bunu tam olarak say çağrısının kendisiyle gösteriyor:
say, atlanan adımın tek yan etkisi ve o yan etki gerçekleşmediği için sayı sıfırda
kalıyor.
Bu, önceki dersteki Err kolunun kaynakta yazılı ama koşmamış olmasından farklı bir
durum. Orada iki kol da aynı matchin içinde açıkça duruyordu, ikisi de görünürdü. Burada
“atlanan gövde” ayrı bir kol olarak yazılmıyor — soru işaretinden sonraki her satır, hata
durumunda örtük olarak atlanıyor. Kaynağı okuyan biri, ?den sonraki satırların hangi
koşulda çalışacağını görebilir, ama derleyicinin bunun için ayrı bir kol ürettiğini
kaynakta görmez; bu kol yalnız koşumda, sayaçla ölçüldüğünde ortaya çıkıyor.
Bu sessizlik bir tasarım kararının sonucu, bir kusur değil. Soru işareti bir uyarı
yazdırmıyor, çünkü hatanın kendisi zaten dönüş değeri olarak taşınıyor — çağıran taraf bu
değeri denetlediği sürece, “neden atlandığı” bilgisi kayıp değil, yalnız gövdenin geri
kalanında değil, dönüş değerinde duruyor. Bu ders bu bilgiyi izlemiyor; yalnız gövdenin geri
kalanının çalışıp çalışmadığını sayıyor. Bir işlevin cozumleyi çağıran tarafı, dönen
Resultı denetlemezse, hem bu bilgiyi hem de neyin atlandığını görme fırsatını kaybeder —
ama bu, soru işaretinin değil, çağıran tarafın denetlememe kararının sonucu.
Aynı sessiz atlama, işlev sınırlarını art arda geçen bir soru işareti zincirinde de aynı biçimde tekrarlanır. Bir işlev kendi içinde soru işaretiyle başka bir işlevi çağırıyorsa ve o işlev de kendi içinde bir üçüncü işlevi soru işaretiyle çağırıyorsa, en alttaki başarısızlık üç ayrı gövdeyi de sessizce atlayarak en üste kadar yükselir — hiçbiri bir iz bırakmadan. Bu ders bu çok katmanlı zinciri koşturmuyor; yalnız tek bir sınırda, atlamanın kendisinin sessiz olduğunu gösteriyor.
Bu üç katmanlı zinciri düşünmek sınırı daha da netleştiriyor. En alttaki işlev bir
ayrıştırma yapıyor ve başarısız oluyor diyelim; onu çağıran orta katman kendi gövdesinde
üç satır daha çalıştıracaktı, ama hiç çalıştırmıyor; en üstteki çağıran ise beş satır daha
çalıştıracaktı, o da çalıştırmıyor. Toplamda sekiz satırlık gövde, tek bir başarısızlık
yüzünden sessizce atlanmış oluyor — ama kaynakta bu sekiz satırın “atlanabilir” olduğunu
işaretleyen hiçbir yorum, hiçbir özel söz dizimi yok. Yalnız her üç katmanın da kendi
dönüş tipinin Result olması ve her katmanın çağrısının ardında bir soru işareti
bulunması, bu sessiz atlamanın mümkün olduğunu gösteriyor.
Bu konunun ilk iki dersinde ölçülen ayrım burada da geçerliliğini koruyor: Optionda hazır
ve tembel gövdeler arasındaki fark, Resultda başarı ve başarısızlık kolları arasındaki
fark, burada da aynı biçimde duruyor — yalnız artık match kolları elle yazılmıyor, tek
bir işaretin ardında saklanıyor. Kısa yazım, ölçülen üç sınıfın (her seferinde, yalnız
gerektiğinde, hiç) hiçbirini ortadan kaldırmıyor; yalnız o sınıfları taşıyan kaynağı
küçültüyor.
Özet
- Soru işareti, bir
Resultçağrısının ardından yazılan erken dönüşü tek bir işarete indirger;Okdönerse değeri açar ve gövde devam eder,Errdönerse işlev hemen döner. - Soru işaretinden sonraki gövde, çağrı başarılı olduğunda yalnız gerektiğinde çalışır — çağrı başarısız olduğunda hiç çalışmaz.
- Aynı işlev elle yazılan bir
matchve erkenreturnile kurulduğunda aynı sayıları verir: kısa yazım koşumu değil, kaynağı kısaltır — bu fark, bir işlev birden çok ayrıştırma yaptıkça büyür. - Erken dönüş kalan gövdeyi sessizce atlar; atlanan adımın hiçbir yan etkisi de gerçekleşmez — ne bir uyarı, ne bir kısmi kayıt.
- Atlanan gövde kaynakta ayrı bir kol olarak görünmez; yalnız sayaçla ölçüldüğünde, eksik kalan kayıtla fark edilir.
- Soru işareti farklı hata tiplerini
Fromile otomatik dönüştürür; bu dönüşümün kim tarafından çağrıldığı sıradaki dersin konusudur.
Sonraki Adım
Yukarıdaki SayiHatasi, std::num::ParseIntErrori kendi tipine dönüştüren bir From
gerçekleştirimi taşıyordu — soru işaretinin bu dönüşümü nasıl çağırdığı bu derste
gösterilmedi, yalnız kullanıldı. cozumle işlevinin gövdesinde bu dönüşümü çağıran açık
bir satır yok; metin.parse()? yazımının kendisi hem hatayı denetliyor hem de gerekirse
dönüştürüyor. Özel Hata Tipleri dersi bu dönüşümü kimin çağırdığını ölçüyor: kısa yazım bu
çağrıyı çağıran taraf hiçbir şey yazmadan mı yapıyor, yoksa elle yazılan bir dönüşüm mü
gerekiyor — ve iki yaklaşım aynı bilgiyi taşıyabiliyor mu, yoksa örtük dönüşüm bir şeyi
kaybediyor mu?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.