İçeriğe geç
academia.sh

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, match her zaman (bir dal seçilerek), loop yalnız breake bir değer verildiğinde.
  • while ve for hiçbir zaman değer üretmiyor; ikisi de her koşulda () veriyor.
  • if ve matchin 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 bir if de 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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat