Rust'ta kontrol akışı iki temel yapıya dayanır: koşula göre dallanan if ifadesi ve kodu tekrarlayan üç döngü türü, yani loop, while ve for. Diğer dillerden farklı olarak if bir ifadedir ve değer üretir, koşul mutlaka bool olmalıdır, loop ise break ile değer döndürebilir. İç içe döngülerde hangi döngünün kırılacağını etiketlerle ('dis:) belirtirsiniz. Bir koleksiyonu dolaşmanın en güvenli ve en yaygın yolu for döngüsüdür.

Bu yazıda her yapıyı Rust 1.99.1 ile çalıştırılmış örneklerle, derleyicinin verdiği gerçek hata mesajlarıyla ve sık yapılan hatalarla birlikte anlatıyoruz. Ayrıca az bilinen etiketli blok ifadesini, while let kalıbını ve 2024 edition ile gelen let zincirlerini kısaca tanıtıyoruz. Fonksiyon ve ifade kavramlarını henüz görmediyseniz fonksiyonlar yazısı iyi bir başlangıçtır; serinin bütünü için Rust öğrenme rehberi sayfasına bakın.

if ve else if

if ve else if

if ifadesi bir koşulla başlar ve koşul doğruysa hemen ardından gelen süslü parantez içindeki bloğu çalıştırır. Bu bloklara, match ifadelerinde olduğu gibi kol (arm) da denir. İsteğe bağlı else bloğu koşul yanlış olduğunda çalışır; else yoksa program if bloğunu atlayıp devam eder. Birden çok koşulu else if ile sıralayabilirsiniz:

rust
fn main() {
    let sayi = 6;
    if sayi % 4 == 0 {
        println!("4'e bölünür");
    } else if sayi % 3 == 0 {
        println!("3'e bölünür");
    } else if sayi % 2 == 0 {
        println!("2'ye bölünür");
    } else {
        println!("4, 3 veya 2'ye bölünmez");
    }

    let kosul = true;
    let deger = if kosul { 5 } else { 6 };
    println!("değer: {deger}");
}
// çıktı: 3'e bölünür
// çıktı: değer: 5

6 sayısı 2'ye de bölündüğü hâlde çıktıda yalnızca ilk doğru koşulun mesajı görünür. Rust koşulları sırayla dener, doğru olan ilk koşulun bloğunu çalıştırır ve geri kalanları hiç kontrol etmez. The Book, çok sayıda else if kodu dağıttığında bunun yerine match ifadesini kullanmanızı önerir.

Rust'ta koşul mutlaka bool tipinde olmalıdır. Ruby ya da JavaScript gibi dillerin aksine Rust, sayıları veya başka tipleri otomatik olarak boolean'a çevirmez:

rust
fn main() {
    let sayi = 3;
    if sayi {
        println!("sayı üç");
    }
}
// error[E0308]: mismatched types
// expected `bool`, found integer

Sayının sıfırdan farklı olup olmadığını kontrol etmek istiyorsanız bunu açıkça yazmanız gerekir: if sayi != 0 { ... }. Bu katılık ilk başta fazladan yazı gibi görünse de koşulların ne kontrol ettiğini her zaman görünür kılar.

if bir ifadedir: let ile kullanım

Yukarıdaki örneğin son kısmında let deger = if kosul { 5 } else { 6 }; satırı vardı. if bir ifade olduğu için sonucu doğrudan bir değişkene bağlanabilir; değişkenin değeri hangi kolun çalıştığına bağlıdır. Bloklar son ifadelerinin değerini ürettiğinden kolların sonunda noktalı virgül olmamalıdır. Bunun bir kuralı vardır: kolların ürettiği değerler aynı tipte olmalıdır. Rust'ın bir değişkenin tipini derleme zamanında kesin olarak bilmesi gerekir; tip çalışma zamanında belli olsaydı derleyici o değişkenin her kullanımını doğrulayamazdı:

rust
fn main() {
    let kosul = true;
    let sayi = if kosul { 5 } else { "altı" };
    println!("{sayi}");
}
// error[E0308]: `if` and `else` have incompatible types
// expected integer, found `&str`

Değer üreten bir if yazarken else kolunu da yazmanız gerekir; aksi hâlde koşul yanlış olduğunda üretilecek değer unit olur ve diğer kolun tipiyle uyuşmaz. Bu kalıp, değişkenler yazısında gördüğümüz sonradan ilk değer verme kalıbının daha kısa ve okunaklı hâlidir.

loop: sonsuz döngü, break ve continue

loop: sonsuz döngü, break ve continue

loop anahtar kelimesi bir bloğu siz açıkça durdurana kadar tekrar tekrar çalıştırır. Yanlışlıkla sonsuz bir döngüye girdiyseniz çoğu terminalde ctrl-c ile programı durdurabilirsiniz. Döngüyü koddan durdurmak için break, o turun geri kalanını atlayıp sonraki tura geçmek için continue kullanılır. loop'un en kullanışlı özelliği break'ten sonra bir değer yazarak döngüden değer döndürebilmesidir. Bu, başarısız olabilecek bir işlemi tekrar denerken sonucu döngü dışına taşımak için idealdir:

rust
fn main() {
    let mut sayac = 0;
    let sonuc = loop {
        sayac += 1;
        if sayac == 10 {
            break sayac * 2;
        }
    };
    println!("sonuç: {sonuc}");
}
// çıktı: sonuç: 20

Döngüden sonra gelen noktalı virgüle dikkat edin: let sonuc = loop { ... }; bir let deyimidir ve noktalı virgülle biter. break yalnızca içinde bulunduğu döngüden çıkar; return ise her zaman bulunduğu fonksiyondan çıkar. Reference'a göre hiç break içermeyen bir loop ifadesi hiç tamamlanmaz ve tipi ! (never) olur; break içeren bir loop'un tipi ise break ifadelerinin değerleriyle uyumlu olmalıdır. Değersiz bir break, break () ile aynı anlama gelir.

Döngü etiketleri ile iç içe döngüler

İç içe döngülerde break ve continue varsayılan olarak en içteki döngüye uygulanır. Dıştaki bir döngüyü kırmak için o döngüye bir etiket verip break ya da continue'yu bu etiketle yazarsınız. Etiketler tek bir kesme işaretiyle başlamalıdır:

rust
fn main() {
    let mut sayim = 0;
    'dis: loop {
        println!("sayım = {sayim}");
        let mut kalan = 10;
        loop {
            println!("kalan = {kalan}");
            if kalan == 9 {
                break;
            }
            if sayim == 2 {
                break 'dis;
            }
            kalan -= 1;
        }
        sayim += 1;
    }
    println!("son sayım = {sayim}");
}
// çıktı: sayım = 0
// çıktı: kalan = 10
// çıktı: kalan = 9
// çıktı: sayım = 1
// çıktı: kalan = 10
// çıktı: kalan = 9
// çıktı: sayım = 2
// çıktı: kalan = 10
// çıktı: son sayım = 2

Etiketsiz break yalnızca içteki döngüden çıkar ve dış döngü bir sonraki tura geçer. sayim 2 olduğunda çalışan break 'dis; ise iki döngüden birden çıkar; bu yüzden son turda kalan = 9 satırı yazılmaz. Etiketler while ve for döngülerinde de aynı şekilde çalışır.

while: koşullu döngü

Bir koşul doğru olduğu sürece dönen döngüler o kadar yaygındır ki Rust'ta bunun için ayrı bir yapı vardır. loop, if, else ve break ile de aynı şeyi kurabilirsiniz, ama while gereksiz iç içeliği ortadan kaldırır ve daha okunaklıdır. Aşağıdaki örneğin ilk kısmı 3'ten geriye sayar. Önemli bir kısıt da şudur: Reference'a göre değer döndürebilen yalnızca loop ve etiketli bloklardır; while içinde break ile değer döndürmeye çalışırsanız derleyici reddeder:

rust
fn main() {
    let mut i = 0;
    let x = while i < 3 {
        i += 1;
        break 5;
    };
    println!("{x:?}");
}
// error[E0571]: `break` with value from a `while` loop

Derleyici burada break'i değersiz kullanmanızı önerir. Bunun mantığı açıktır: koşul ilk turda yanlış çıkarsa döngü hiç break'e ulaşmadan biter ve döndürülecek bir değer olmaz. Değer döndürmeniz gerekiyorsa döngüyü loop olarak yazın.

for: koleksiyonlar ve aralıklar üzerinde dolaşmak

Bir dizinin elemanlarını while ve bir indeks değişkeniyle dolaşmak mümkündür ama hataya açıktır. The Book'un örneğinde olduğu gibi dizinin eleman sayısını değiştirip koşulu güncellemeyi unutursanız program sınır dışı erişim yüzünden panic eder:

rust
fn main() {
    let a = [10, 20, 30, 40];
    let mut indeks = 0;
    while indeks < 5 {
        println!("değer: {}", a[indeks]);
        indeks += 1;
    }
}
// çıktı: değer: 10
// çıktı: değer: 20
// çıktı: değer: 30
// çıktı: değer: 40
// thread 'main' (41080) panicked at c8.rs:5:31:
// index out of bounds: the len is 4 but the index is 4

The Book bu yaklaşımın aynı zamanda yavaş olduğunu da belirtir, çünkü derleyici her turda indeksin dizi sınırları içinde olup olmadığını kontrol eden kod ekler. for döngüsü ise koleksiyondaki her eleman için bir kez çalışır; sınırı aşma ya da eleman atlama ihtimali yoktur ve üretilen makine kodu indeksi her turda dizinin uzunluğuyla karşılaştırmak zorunda kalmadığı için daha verimli olabilir. Güvenliği ve kısalığı nedeniyle for, Rust'ta en çok kullanılan döngü yapısıdır. Belirli sayıda tekrar için de standart kütüphanenin Range tipi kullanılır:

rust
fn main() {
    let mut n = 3;
    while n != 0 {
        println!("{n}!");
        n -= 1;
    }
    println!("KALKIŞ!");

    for n in (1..4).rev() {
        print!("{n} ");
    }
    println!();

    let a = [10, 20, 30];
    for eleman in a {
        print!("{eleman} ");
    }
    println!();

    let mut toplam = 0;
    for i in 1..=10 {
        toplam += i;
    }
    println!("toplam: {toplam}");

    for (i, isim) in ["Ali", "Ayşe"].iter().enumerate() {
        println!("{i}: {isim}");
    }

    for i in 1..=10 {
        if i % 3 != 0 {
            continue;
        }
        print!("{i} ");
    }
    println!();
}
// çıktı: 3!
// çıktı: 2!
// çıktı: 1!
// çıktı: KALKIŞ!
// çıktı: 3 2 1
// çıktı: 10 20 30
// çıktı: toplam: 55
// çıktı: 0: Ali
// çıktı: 1: Ayşe
// çıktı: 3 6 9

1..4 aralığı 1'den başlar ve 4'ten önce biter, yani 1, 2, 3 üretir; rev() sırayı tersine çevirir. Bitiş değerini dahil etmek için 1..=10 yazılır. enumerate() her elemanı indeksiyle birlikte bir tuple olarak verir ve for deseni bu tuple'ı (i, isim) olarak parçalar. print! satır sonu eklemediği için satırlar boşlukla bitiyor; çıktı yorumlarında sondaki boşlukları göstermedik.

Reference'a göre for döngüsü, IntoIterator trait'ini uygulayan her şey üzerinde çalışan bir söz dizimi kolaylığıdır ve aslında içinde match bulunan bir loop'a dönüştürülür: her turda iterator'ın next metodu çağrılır, Some(deger) gelirse gövde çalışır, None gelince döngü biter. Bir diziyi for eleman in a ile değerle ya da for eleman in &a ile referansla dolaşabilirsiniz; bu ayrım sahiplik açısından önemlidir ve iterator yazısında ayrıntılı anlatılıyor.

Etiketli bloklar ve while let

Etiketli bloklar ve while let: loop, while, while let, Koleksiyon ya da aralık üzerinde dolaşma

Reference dört döngü benzeri yapı tanımlar: loop, while, for ve etiketli blok ifadesi. Etiketli blok tam olarak bir kez çalışan, ama içinden break ile erken çıkılabilen bir bloktur. Döngülerden farklı olarak burada break mutlaka etiketle yazılmalıdır ve continue kullanılamaz. Bir değeri birkaç aşamalı kontrolle hesaplarken iç içe if'lerden kurtulmanın şık bir yoludur:

rust
fn harf_notu(not: u32) -> &'static str {
    'blok: {
        if not >= 90 {
            break 'blok "AA";
        }
        if not >= 80 {
            break 'blok "BA";
        }
        "CC"
    }
}

fn main() {
    println!("{} {} {}", harf_notu(95), harf_notu(85), harf_notu(40));

    let mut yigin = vec![1, 2, 3];
    while let Some(ust) = yigin.pop() {
        print!("{ust} ");
    }
    println!();
}
// çıktı: AA BA CC
// çıktı: 3 2 1

Örneğin ikinci kısmındaki while let, bir desen eşleştiği sürece dönen döngüdür. pop() vektörde eleman kaldıkça Some(deger), vektör boşalınca None döndürür; desen eşleşmediği anda döngü biter. Rust 2024 edition'dan itibaren Edition Guide'a göre if ve while koşullarında birden çok let ifadesi && ile zincirlenebilir ve bool ifadelerle karıştırılabilir (örneğin if let Some(n) = x && n > 5). if let, let else ve desenlerin ayrıntıları match ve if let yazısında, edition farkları Rust 2024 edition yazısında anlatılıyor.

YapıNe zamanbreak ile değercontinue
loopBelirsiz sayıda tekrar, yeniden denemeEvetEvet
whileKoşul doğru oldukça tekrarHayırEvet
while letDesen eşleştikçe tekrarHayırEvet
forKoleksiyon ya da aralık üzerinde dolaşmaHayırEvet
Etiketli blok ('b: { }')Bir kez çalışan, erken çıkılabilen blokEvet, etiketleHayır

Hangi döngüyü seçeceğinize karar verirken The Book'un gözlemi yol gösterir: Rust geliştiricilerinin çoğu, bir işi belirli sayıda tekrarlamaları gerektiğinde bile while ve sayaç yerine bir aralıkla for kullanır. Böylece sayacı elle güncelleme ve sınır koşulunu yanlış yazma riski ortadan kalkar. loop, bitiş koşulunun döngünün ortasında belli olduğu ya da sonucun dışarı taşınması gereken durumlar için; while, koşulun her turun başında kontrol edildiği durumlar için; while let ise bir kaynaktan değer geldikçe çalışan işler için doğal seçimdir. Etiketli bloklar döngü değildir ama aynı break mekanizmasını kullanır; birden çok erken çıkış noktası olan bir hesaplamayı ayrı bir fonksiyona taşımadan tek bir ifade içinde tutmak istediğinizde işe yarar.

Sık yapılan hatalar

  • Koşul olarak sayı kullanmak: if sayi yazmak E0308 verir; if sayi != 0 yazın.
  • if kollarında farklı tipler: Değer üreten bir if'in bütün kolları aynı tipte olmalıdır.
  • Değer üreten if'te else'i unutmak: else olmayan bir if unit üretir; sonucu bir değişkene bağlıyorsanız else şarttır.
  • while ile indeks dolaşmak: Sınır koşulunu yanlış yazmak panic üretir; koleksiyonlar için for kullanın.
  • while'dan değer döndürmeye çalışmak: E0571 alırsınız; değer gerekiyorsa loop kullanın.
  • Etiketsiz break ile dış döngüden çıkmayı beklemek: Etiketsiz break yalnızca en içteki döngüyü kırar.
  • Aralığın bitişini dahil sanmak: 1..10 10'u içermez; dahil etmek için 1..=10 yazın.

Sık Sorulan Sorular

Rust'ta üçlü operatör (koşul ? a : b) var mı?

Ayrı bir üçlü operatör yoktur, çünkü if zaten bir ifadedir: let x = if kosul { a } else { b }; aynı işi görür.

C tarzı for (i = 0; i &lt; n; i++) döngüsü var mı?

Rust'ın for döngüsü bir iterator üzerinde çalışır. Sayaçlı bir döngü için for i in 0..n yazarsınız; geriye saymak için (0..n).rev() kullanılır.

Sonsuz döngü için while true mu loop mu?

loop kullanın. Hem amacı açıkça gösterir hem de break ile değer döndürebilir; while bunu yapamaz.

Döngüden fonksiyonun dışına nasıl çıkarım?

return kullanın. break yalnızca döngüden çıkarken return içinde bulunduğu fonksiyonu doğrudan sonlandırır ve isterseniz bir değer de döndürür.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Rust öğrenme rehberi · Rust fonksiyonlar · match ve if let · Iterator · Vec kullanımı · Rust 2024 edition · Go nedir