Ders 06 / 12
Denetim Akışı
if, match, loop, while, for — beş denetim akışı biçiminden yalnız üçü bir değer üretebilir: if ve match birer ifadedir ve bütün dallarının aynı tipi vermesi zorunludur, loop yalnız break ile değer verir, while ve for hiçbir zaman vermez; etiketli bir break, iç içe döngülerden bir değeri doğrudan dışarıya taşıyabilir.
Yayın Güncelleme
Bu derste
- if, match, loop, while, for biçimlerinden hangilerinin bir ifade olarak değer ürettiğini koşturarak sınıflandırır
- if ve match'in bütün dallarının aynı tipi vermesi gerektiğini ve etiketli bir break'in iç içe döngüden değer taşıyabildiğini gösterir
- Else'siz bir if'in de bir ifade olduğunu ve değerinin () olduğunu kanıtlar
İçindekiler
Önceki ders if/else if/else zincirini zaten bir ifade olarak kullandı, ama bu beş
denetim akışı biçiminden (if, match, loop, while, for) yalnız birinin durumunu
gösterdi. Bu ders beşini birden yan yana koşturuyor ve tek bir soruyu her birine soruyor:
bu biçim, kullanıldığı yerde bir değer üretiyor mu, yoksa yalnız bir iş mi yapıyor?
Bu beş biçim, çoğu dilde tek bir kategori altında toplanır — “denetim akışı yapıları” — çünkü hepsi programın hangi satırların ne sırayla çalışacağını belirler. Rust’ta bu ortak işlev korunuyor, ama beşi ayrıca ikiye bölünüyor: kimi biçim akışı yönlendirirken bir değer de üretiyor, kimi biçim yalnız akışı yönlendiriyor. Bu ayrım rastgele değil — kursun baştan beri sorduğu soruyla aynı eksende duruyor ve bu derste beş biçim üzerinden tek seferde yanıtlanıyor.
Beş Biçim, Üç Yanıt
// denetim_akisi.rs — bes bicimden kaci deger uretiyor
fn main() {
let sayi = 7;
let if_sonucu = if sayi % 2 == 0 { "cift" } else { "tek" };
println!("if: {}", if_sonucu);
let match_sonucu = match sayi {
0 => "sifir",
n if n % 2 == 0 => "cift",
_ => "tek",
};
println!("match: {}", match_sonucu);
let mut sayac = 0;
let loop_sonucu = loop {
sayac += 1;
if sayac == 5 {
break sayac * 10;
}
};
println!("loop: {}", loop_sonucu);
let mut toplam = 0;
let while_sonucu = while toplam < 3 {
toplam += 1;
};
println!("while: {:?}", while_sonucu);
let mut liste_toplami = 0;
let for_sonucu = for eleman in [1, 2, 3] {
liste_toplami += eleman;
};
println!("for: {:?}", for_sonucu);
println!("liste_toplami: {}", liste_toplami);
}
if: tek match: tek loop: 50 while: () for: () liste_toplami: 6
Beş biçim üç ayrı yanıt veriyor. if ve match birer değer üretiyor — hangi dal seçilirse
o dalın değerini veriyorlar. loop da bir değer üretiyor, ama yalnız breake bir değer
verildiğinde; burada sayac beşe ulaştığında break sayac * 10 çalışıyor ve loopun
kendisi 50 değerine dönüşüyor. while ve for ise hiçbir zaman bir değer üretmiyor —
ikisi de her koşulda () veriyor, döngü gövdesi ne yaparsa yapsın. for döngüsünün
içindeki liste_toplami değişkeni bunu netleştiriyor: döngünün kendi değeri ()dir, ama
gövdesinin içindeki bir yan etki (bir dış değişkeni güncellemek) döngünün asıl işini
görüyor.
Bu üç yanıt rastgele dağılmıyor; her birinin kaynağı biçimin kendi kurgusunda. if ve
match seçicidir — birden çok dal sunar, tam olarak birini seçer ve dilin doğal
tercihi seçilen dalın değerini bütün ifadenin değeri saymaktır. loop sınırsızdır —
kendi başına ne zaman duracağını bilmez, bu yüzden bir değeri ancak açıkça breake
verildiğinde üretebilir; break hiç çağrılmazsa loop da hiç bitmez ve “değeri” sorusu
gündeme bile gelmez. while ve for ise koşulludur — döngünün her turu bir koşula
(whileda açık bir mantıksal ifade, forda bir yineleyicinin tükenip tükenmediği) bağlı
ve döngü bu koşul yanlış olduğunda kendiliğinden duruyor. Bu doğal duruşun hangi turda
gerçekleşeceği kaynaktan görünmüyor, bu yüzden dilin bu iki biçime bir değer vermesi
anlamsız olurdu — hangi durumun “sonuç” sayılacağını kaynak kendisi söylemiyor.
Bu üçlü ayrımın bir sonucu daha var: loop, sınırsız olduğu için, aslında while true { }
ile aynı akışı kurabilir — ikisi de kendiliğinden durmaz. Ama yalnız loop bir değer
üretebiliyor, while true { } üretemiyor; çünkü whileın kendisi, koşulu ne olursa olsun,
hiçbir zaman breake bir değer taşıma imkânı sunmuyor. Aynı sınırsız akış, iki farklı
biçimle yazıldığında iki farklı yeteneğe sahip oluyor.
RD15. if ve matchin bir değer üretebilmesinin bir koşulu var: bütün dalları
aynı tipte bir değer vermek zorunda. Yukarıdaki if_sonucu örneğinde her iki dal da
&str veriyor; match_sonucuda üç dalın üçü de &str veriyor. Bir dal başka bir tip
verseydi (örneğin bir dal &str, öbürü i32 verseydi) derleyici reddederdi — çünkü
if_sonucu ya da match_sonucunun tek bir tipi olmak zorunda ve o tip hangi dalın
seçildiğine bakılmadan, derleme zamanında bilinmek zorunda.
match_sonucudaki ikinci dal (n if n % 2 == 0 => "cift") bir koruma koşulu (match
guard) taşıyor: n deseni sayinin değerini yakalıyor, if n % 2 == 0 ise bu dalın
yalnız yakalanan değer çift olduğunda seçilmesini sağlıyor. Bu, desen eşlemenin ileride
ayrıca ele alınacak daha geniş bir örneğidir; burada yalnız bir dalın seçilip
seçilmeyeceğine ek bir koşulun karar verebildiğini görmek yeterli — sonucu, dalın hâlâ tek
bir değer ("cift") üretmesi.
Bu kısıt, matchin koruma koşulu taşıyan dalında da görülen bir başka özelliği açıklıyor:
matchin kaç dal taşıdığı ve dalların hangi koşullarla seçildiği çalışma zamanına kadar
belli olmayabilir, ama dalların tipi hep derleme zamanında bellidir. Derleyici hangi
dalın koşacağını bilmeden de bütün dalların tipini denetleyebiliyor, çünkü tip denetimi
kaynağın tamamına bakıyor, yalnız çalışma zamanında seçilecek tek bir dala değil. Bu, tip
güvenliğinin hangi dalın seçildiğinden bağımsız olarak sağlanmasının nasıl mümkün
olduğunu gösteriyor: her dal ayrı ayrı denetleniyor, sonra hepsinin aynı tipte olduğu
doğrulanıyor — hiçbiri koşmadan önce.
Etiketli Döngüden Değer Taşımak
loopun break ile değer üretmesi, iç içe geçmiş döngülerde bir sorun doğuruyor:
break en yakın çevreleyen döngüyü kırıyor, ama bazen bir değeri daha dıştaki bir
döngüden çıkarmak gerekiyor. Rust bunu döngü etiketleriyle çözüyor.
// etiketli_donguler.rs — ic ice donguden bir deger disariya tasinir
fn main() {
let hedef = 6;
let mut i = 1;
let sonuc = 'disarisi: loop {
let mut j = 1;
loop {
if j > 5 {
break;
}
if i * j == hedef {
break 'disarisi (i, j);
}
j += 1;
}
i += 1;
if i > 5 {
break (0, 0);
}
};
println!("bulunan cift: {:?}", sonuc);
}
bulunan cift: (2, 3)
Burada iki loop iç içe. İçteki döngünün etiketi yok ve kendi breaki yalnız kendisini
kırıyor — j beşi aştığında bu iç döngü sona eriyor ve dıştaki döngü iyi artırarak devam
ediyor. Dıştaki döngü 'disarisi etiketini taşıyor; break 'disarisi (i, j) satırı, içteki
döngünün tam ortasından, doğrudan dıştaki döngüyü kırıyor ve (i, j) demedini
sonuca taşıyor. i=2, j=3 çarpımı hedefi (6) verdiğinde bu satır çalışıyor ve döngü
zinciri anında sona eriyor.
Bu kalıp, iki boyutlu bir alanda arama yapan her koda tanıdık gelir: bir tabloda, bir
ızgarada ya da iki değişkenli bir arama uzayında belirli bir koşulu sağlayan ilk çifti
bulmak. Etiketsiz break kullanılsaydı, koşul sağlandığında yalnız içteki döngü
kırılabilirdi; dıştaki döngü devam eder, iyi artırıp taramayı sürdürürdü — sonucu
saklamak için ayrı bir bayrak değişkeni ve dıştaki döngüde ek bir kontrol gerekirdi.
Etiketli break, bu ek denetimi ortadan kaldırıp arama sonucunu doğrudan döngü zincirinin
kendi değeri yapıyor.
RD16. Etiketli break, yalnız hangi döngünün kırılacağını seçmiyor, aynı zamanda
değerin hangi döngüye ait olduğunu da belirliyor. sonucun değeri, en dıştaki
'disarisi loopun değeridir — içteki döngünün kendisi hiçbir zaman bir değer üretmedi,
üretemez de, çünkü içteki break; hiçbir değer taşımıyor. Bir demet döndürme yeteneği
yalnız etiketlenen döngüye ait; iç döngü yalnız kendi akışını kontrol ediyor, dış döngünün
sonucuna doğrudan karışamıyor — o yalnız etiketli break ile mümkün.
Kaynaktaki iki break satırı bu yüzden aynı anahtar sözcüğü taşısa da farklı işler
yapıyor. Etiketsiz break; en yakın çevreleyen döngüyü kırıyor — burada içteki döngüyü —
ve hiçbir değer taşımıyor, çünkü içteki döngü hiç bir sonuca bağlanmadı. Etiketli
break 'disarisi (i, j) ise arada kaç döngü olursa olsun doğrudan adı geçen döngüye
atlıyor ve o döngünün değerini belirliyor. Bu iki kullanım aynı derste, aynı dosyada yan
yana durabiliyor, çünkü etiket yalnız hedefi değiştiriyor; söz dizimi ve anahtar sözcük
aynı kalıyor.
Etiketleme yalnız break için değil, continue için de geçerlidir — bir sonraki tura
etiketli bir döngüden geçmek de aynı 'ad: söz dizimini kullanır. Bu ders yalnız değer
taşıyan breaki ölçtü, çünkü kursun ekseni değer üretimidir; continueün etiketlenmesi de
aynı hedefleme mantığını izliyor, yalnız bir değer taşımıyor.
Sınırlayıcı Ölçüm: Elsesiz if de Bir İfadedir
Buraya kadar görülen her if bir else dalı taşıyordu. else dalı olmayan bir if de bir
ifadedir — yalnız değeri her zaman ()dir.
// elsesiz_if.rs — elsesiz bir if de bir ifadedir, degeri ()dir
fn main() {
let sayi = 4;
let sonuc = if sayi % 2 == 0 {
println!("cift bulundu: {}", sayi);
};
println!("elsesiz if in degeri: {:?}", sonuc);
}
cift bulundu: 4 elsesiz if in degeri: ()
sonuc gerçekten bir bağlamaya bağlandı — bu, ifin bir ifade olarak değerlendirildiğini
kanıtlıyor, çünkü Rust bir deyimi bir bağlamaya bağlamana izin vermez. Ama sonucun değeri
(), çünkü koşul sağlanmadığında ne olacağı hiç yazılmadı ve Rust bu boşluğu () ile
dolduruyor — sanki görünmeyen bir else { } varmış gibi.
Bu, ifin dallarının “aynı tipi vermesi” kuralının elsesiz durumdaki karşılığıdır: bir
elsesiz ifin gövdesi, sayi gibi bir i32 üretmeye kalksaydı (if sayi % 2 == 0 { sayi }, hiç else yazmadan), derleyici reddederdi. Çünkü yazılmayan else dalı örtük
olarak () üretiyor sayılıyor ve gövde i32 üretirse iki “dal” (yazılan gövde, örtük
else) farklı tipte olurdu — tıpkı önceki bölümdeki if_sonucu örneğinde iki dalın aynı
tipte olması gerektiği gibi. Bir elsesiz ifin gövdesi bir değer üretecekse, o değer
()ten başka bir şey olamaz; aksi hâlde gövdenin sonuna bir noktalı virgül eklenip
değerin sessizce atılması gerekir.
Bu sınır, kaynak yazarken bir kestirme sağlıyor: bir if yalnız bir yan etki için
yazılıyorsa (bir yazdırma, bir güncelleme) ve karşıt durumda yapılacak hiçbir şey yoksa,
else dalı yazmaya gerek kalmıyor — derleyici zaten () bekliyor ve gövde de ()
üretiyor. else yalnız iki dalın da farklı bir değer üretmesi gerektiğinde zorunlu
oluyor.
Bu, önceki dersteki noktalı virgül kuralıyla aynı aileden bir gözlem. Orada bir fonksiyon
gövdesinin son satırına noktalı virgül eklemek, gövdenin ürettiği değeri ()e çeviriyordu;
burada bir ifin else dalını hiç yazmamak, aynı sonuca farklı bir yoldan ulaşıyor —
yazılmayan dal, örtük olarak () üretiyor sayılıyor. İkisi de aynı ilkeyi taşıyor: Rust’ta
bir “eksik” değer, kaynağın hiçbir yerinde tanımsız bırakılmıyor; eksik olan yer hep ()e
karşılık geliyor.
Beş biçimin üçe ayrılması, kaynağı yazarken de bir seçim sunuyor: bir hesaplamayı belirli
sayıda dal arasından seçen bir yapıya mı ihtiyacın var (if, match), yoksa durma koşulu
önceden bilinmeyen, açık uçlu bir tekrara mı (loop), yoksa sonu koşuldan önceden belli bir
tekrara mı (while, for)? Bu üç soru birbirinden bağımsızdır ve her birinin yanıtı, o
biçimin değer üretip üretmeyeceğini de beraberinde getiriyor. Bir programcı “bu döngü bana
bir sonuç verecek mi” sorusunu sormadan önce, hangi döngü biçimini seçtiğine bakması
yeterli — yanıt biçimin kendisinde saklı.
Bu ders, altı derslik bu bölümün topladığı bütün parçaları tek bir yerde birleştiriyor.
İlk ders üç sınıfı (ifade, deyim, öğe) tanımladı ve bir ölçüm çekirdeğiyle sayıya döktü;
ikinci ders bu sınıfların proje düzeyinde de geçerli olduğunu gösterdi; üçüncü ders bağlamayı
kurdu; dördüncü ders bağlanan değerlerin tiplerini kurdu; beşinci ders bir fonksiyon
gövdesinin bu üç sınıfı nasıl tek bir dönüş değerinde topladığını gösterdi. Bu ders, son
olarak, denetim akışının kendisinin — programın hangi satırın ne zaman çalışacağını
belirleyen beş biçimin — aynı üç sınıfa nasıl uyduğunu ölçtü. Kaynakta yazılan hiçbir şey bu
sınıflandırmanın dışında kalmadı: en küçük bir işleçten en büyük bir denetim yapısına kadar,
her satır ya bir değer üretiyor ya () üretiyor ya da yalnız bir ad bildiriyor. Bu, altı
dersin başında sorulan tek sorunun — kaynakta yazdığın bu şey bir değer mi üretiyor — neden
tek bir dersle sınırlı kalmayıp bir bölümün tamamını taşıyabildiğini de açıklıyor: soru
büyümüyor, yalnız uygulandığı yazım genişliyor.
Özet
- Beş denetim akışı biçiminden üçü değer üretebiliyor:
if,matchher zaman (bir dal seçilerek),loopyalnızbreake bir değer verildiğinde. whileveforhiçbir zaman değer üretmiyor; ikisi de her koşulda()veriyor.ifvematchin bütün dalları aynı tipte bir değer vermek zorunda; aksi hâlde derleyici reddeder.- Etiketli bir
break, iç içe döngülerden bir değeri doğrudan en dıştaki etiketli döngüye taşıyabiliyor; etiketlenmemiş döngüler yalnız kendi akışlarını kontrol edebiliyor. elsesiz birifde bir ifadedir ve değeri her zaman()dir; gövdesi başka bir tip üretmeye kalkarsa derlenmez.- Kaynakta yazılan hiçbir şey ifade / deyim / öğe sınıflandırmasının dışında kalmıyor; bu ders, denetim akışının en karmaşık biçimlerinin de aynı üç yanıttan birine girdiğini gösterdi.
Sonraki Adım
Bu altı derste denetim akışının değer üretip üretmediği, tek tek biçimlere bakılarak ölçüldü. Sıradaki konu aynı soruyu veriye taşıyor: bir tip nasıl kuruluyor, kaç durum taşıyor ve o durumlar nasıl ayrıştırılıyor — yapılar ve numaralandırmalarla başlayan yeni bir bölüm bu soruları tek tek ele alacak. Şimdiye kadar görülen tipler dilin kendi sunduğu hazır tiplerdi (tam sayı, kayan noktalı, demet); sıradaki bölümde bu tiplerden yenilerinin nasıl tanımlandığı — alanların bir arada toplanması, durumların bir arada sayılması — ele alınacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.