Kontrol Akışı
Bir kodun doğruluğuna bağlı olarak kodun başka bir bölümünün çalıştırılması veya bazı kodların bir koşul doğru olduğu sürece çalıştırılması çoğu programlama dilinin temel yapı taşlarıdır. Rust'ta kod yürütme akışını kontrol edebilmenizi sağlayan en yaygın kontrol yapıları if
ifadeleri ve loop
döngüleridir.
if
İfadeleri
If
ifadesi kodunuzu koşullara göre bölerek yürütmenize olanak sağlar. Bir koşul belirleyip ardından "Bu şart sağlanırsa şu kod bloğunu çalıştırın, koşul sağlanmıyorsa çalıştırmayın." demeye benzer.
If
ifadesini daha iyi kavrayabilmek için projeler dizininde dallar adında yeni bir proje oluşturup src/main dosyasına aşağıdaki kodları ekleyin:
Dosya adı: src/main.rs
fn main() { let sayı = 3; if sayı < 5 { println!("Koşul doğru."); } else { println!("Koşul yanlış!"); } }
Tüm if
ifadeleri, if
anahtar kelimesiyle başlar ve bunu bir koşul takip eder. Örneğimizdeki koşul sayı
değişkeninin 5'ten küçük olup olmadığını kontrol eder. Koşulun true
(doğru) olması durumunda yürütülecek kod bloğu koşulun hemen ardından eklenen süslü parantezler içine yerleştirilir. If
ifadesinin koşulunu denetleyen kod blokları, kitabın 2. Bölümündeki Tahmin Sayısının Gizli Sayı ile Karşılaştırılması konusunda yer alan eşleme ifadesinde olduğu gibi bazen kol bazen dal olarak adlandırılabilir.
Dilerseniz örnekte yaptığımız gibi, koşulun false
(yanlış) olması halinde alternatif bir kod bloğu olarak işletilecek bir else
ifadesini de kodunuza ekleyebilirsiniz. Eğer koşul yanlış ve bir else
ifadesi bildirilmemişse program if
bloğunu geçerek sonraki kod parçasına atlayacaktır.
Kodu çalıştırdığınızda aşağıdaki çıktıyı görmelisiniz:
$ cargo run ✔
Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
Finished dev [unoptimized + debuginfo] target(s) in 1.82s
Running `target/debug/dallar`
Koşul doğru.
Ne olacağını görmek için sayı
değerini, koşulu false
yapacak bir değerle değiştirelim:
fn main() {
let sayı = 7;
if sayı < 5 {
println!("Koşul doğru.");
} else {
println!("Koşul yanlış!");
}
}
Ve programı yeniden çalıştırıp çıktıyı inceleyin:
$ cargo run ✔
Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/dallar`
Koşul yanlış!
Bu koddaki koşulun bir bool
olması gerektiğini belirtmekte yarar var. Koşulun bool
olmaması halinde hata ile karşılaşırız. Örnekteki kodu çalıştırmayı deneyin:
Dosya adı: src/main.rs
fn main() {
let sayı = 3;
if number {
println!("Sayı üç");
}
}
Artık if
koşulu 3 değerine ayarlanmış olduğundan Rust bir hata döndürecektir:
$ cargo run
Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
error[E0308]: mismatched types
--> src/main.rs:29:8
|
29 | if sayı {
| ^^^^ expected `bool`, found integer
For more information about this error, try `rustc --explain E0308`.
error: could not compile `dallar` due to previous error
Bu hata bize, Rust'ın bool
türünde bir değer beklediğini ancak tam sayı türünde değer aldığını gösterir. Ruby veya Javascript gibi dillerin tersine Rust, boolean olmayan türleri otomatik olarak boolean türüne dönüştürmeye kalkışmaz. Açık olmanız ve if
koşulunun daima boolean olmasını sağlamanız gerekir. Örneğin sayı 0
olmadıkça if
kod bloğunun yürütülmesini istiyorsak, if
ifadesini aşağıdaki gibi değiştirebiliriz:
Dosya adı: src/main.rs
fn main() { let sayı = 3; if sayı != 0 { println!("Bu sayı sıfır değil!"); } }
Bu kod çalıştırıldığında ekrana Bu sayı sıfır değil!
mesajını yazdıracaktır.
else if
ile Koşulları İşlemek
Bir else if
ifadesinde if
ve else
kelimelerini birleştirerek çok sayıda koşulu denetleyebilirsiniz:
Dosya adı: src/main.rs
fn main() { let sayı = 6; if sayı % 4 == 0 { println!("Sayı 4' e kalansız bölünebilir."); } else if sayı % 3 == 0 { println!("Sayı 3' e kalansız bölünebilir."); } else if sayı % 2 == 0 { println!("Sayı 2' ye kalansız bölünebilir."); } else { println!("Sayı 4, 3 veya 2'ye kalansız bölünemez!"); } }
Bu programın gidebileceği dört olası yol vardır. Progamı çalıştırdığınızda aşağıdaki çıktıyı görmelisiniz:
$ cargo run
Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
Finished dev [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/dallar`
Sayı 3' e kalansız bölünebilir.
Program yürütüldüğünde if
ifadelerinin her birini sırayla kontrol edecek ve bulduğu ilk doğru koşulu işletecektir. 6 sayısının 2'ye kalansız bölünüyor olmasına rağmen, çıktıda Sayı 2' ye kalansız bölünebilir.
mesajını veya else
bloğunda yer alan Sayı 4, 3 veya 2'ye kalansız bölünemez!
mesajını görmediğimize dikkat edin. Bunun nedeni Rust'ın kontrol sırasındaki ilk doğru koşulu bularak onu işletmesi ve diğer koşulların doğu olup olmamasıyla ilgilenmemesidir.
Çok sayıda else if
ifadesi kullanmak kodunuzu karıştırabilir. Gereğinden fazla else if
ifadesi kullandığınızı düşünüyorsanız kodunuzu yeniden düzenlemelisiniz. Kitabın 6. bölümünde böyle durumlarda kullanabileceğiniz güçlü bir dallanma yapısına sahip match
(eşleme) adlı bir yapı anlatılır.
Bir let
Deyiminde if
Kullanmak
Örnek 3-2'de olduğu gibi if
'in bir ifade olması, sonucunun herhangi bir değişkene atanmak üzere let
deyiminin sağ tarafında kullanabilmesni sağlar.
Dosya adı: src/main.rs
fn main() { let koşul = true; let sayı = if koşul {5} else {6}; println!("Sayının değeri : {}", sayı); }
Sayı
değişkeni if
ifadesinin sonucuna göre oluşan bir değere bağlanacaktır. Bu kodu çalıştırdığınızda aşağıdaki çıktıyı elde edeceksiniz:
$ cargo run
Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
Finished dev [unoptimized + debuginfo] target(s) in 1.36s
Running `target/debug/dallar`
Sayının değeri: 5
Kod bloklarının, içlerinde bulunan son ifadeyi değerlendirdiğini ve sayıların da birer ifade olduğunu unutmayın. Bizim durumumuzda tüm if
ifadesinin değeri yürütülecek olan kod bloğunun değerine bağlıdır. Bu da, if
ifadesindeki sonuç üretme potansiyeline sahip her dalın aynı türden olması gerektiği anlamına gelmektedir. Örnek 3-2'de bulunan if
ve else
dallarının her biri i32
türünde birer tam sayıdır. Aşağıdaki örnekten de anlaşılacağı gibi, türlerin uyumsuz olması hata alınmasına neden olur:
Dosya adı: src/main.rs
fn main() {
let koşul = true;
let sayı = if koşul {5} else { "Altı" };
println!("Sayının değeri: {}", sayı);
}
Kodu derlemeye çalıştığımızda if
ve else
kollarının uyumsuz türlerden oluştuğu ve bu hatanın hangi satırda bulunduğunu gösteren bir hata raporuyla karşılaşırız:
$ cargo run
Compiling dallar v0.1.0 (/home/rusdili/projeler/dallar)
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:70:36
|
70 | let sayı = if koşul {5} else { "Altı" };
| - ^^^^^^ expected integer, found `&str`
| |
| expected because of this
For more information about this error, try `rustc --explain E0308`.
error: could not compile `dallar` due to previous error
If
bloğundaki ifade tam sayı olarak değerlendirilirken else
bloğundaki ifadeyse dizgi olarak olarak değerlendirilecektir. Değişkenlerin aynı türden olması ve sayı
değişkeni türünün Rust tarafından derleme zamanında kesinlikle biliniyor olması gerektiğinden bu kod işe yaramaz. Sayı
türünün derleme zamanında biliniyor olması, bu değişkenin kullanıldığı her yerde, derleyici tarafından türünün doğru ve geçerli olduğunun garantilenmesini sağlar. Eğer sayı
değişkeninin türü sadece çalışma zamanında belirlenmiş olsaydı; herhangi bir değişken için çok sayıda varsayımsal türün takip edilmesi gerekecek, buna bağlı olarak derleyici karmaşıklaşacak ve kod hakkında daha az garanti verebileceğinden Rust bunu yapamamış olacaktı.
Döngüler ile Tekrarlama
Bazen bir kod bloğunu defalarca çalıştırmak gerekir. Rust bu amaçla döngü gövdesi içinde kalan kodun tamamını çalıştırıp hemen ardından yeniden baştan başlatan çeşitli döngüler sağlar. Döngülerle çalışabilmek için projeler dizininde donguler adında yeni bir proje başlatalım.
Rust'ta loop
, while
ve for
olmak üzere üç çeşit döngü vardır. Bunların her birini birlikte deneyelim:
loop
ile Kod Tekrarı
Bir anahtar sözcük olan loop
Rust'a, ait olduğu kod bloğunu sonsuza dek ya da siz onu açıkça durdurana kadar tekrar tekrar çalıştırmasını söyler. Şimdi donguler dizinindeki src/main.rs dosyasını örnektekine benzer şekilde değiştirin:
Dosya adı: src/main.rs
fn main() {
loop {
println!("Tekrar!");
}
}
Programı çalıştırdığınızda terminalinizi elle kapatana kadar Tekrar!
mesajının yazdırıldığını göreceksiniz. Pekçok terminal sonsuz döngüye kapılan programların sonlandırılmasını sağlayan ctrl+c klavye kısa yolunu destekler. Demeyelim:
$ cargo run
Compiling donguler v0.1.0 (/home/rusdili/projeler/donguler)
Finished dev [unoptimized + debuginfo] target(s) in 0.29s
Running `target/debug/donguler`
Tekrar!
Tekrar!
Tekrar!
Tekrar!
^CTekrar!
^C
işareti ctrl-c tuşuna bastığınız yeri gösterir. ^C
'den sonra Tekrar!
yazısını görmeniz, kodun kesme sinyalini aldığında döngünün neresinde bulunduğuna bağlı olduğundan bu mesajı göremeyebilirsiniz.
Neyse ki Rust, bu tür döngülerden kod kullanarak çıkmanın bir yolunu sağlar. Programa işletilen döngünün durdurulacağı yeri, o noktaya bir break
anahtar sözcüğü yerleştirerek bildirebilirsiniz. Bu yöntemi 2. Bölümdeki “Doğru Tahmin Sonrası Oyundan Çıkmak” bölümünden hatırlayor olmanız gerek.
Yine hatırlayacağınız gibi tahmin oyunu programında, döngünün o anki tekrarını durdurup bir sonraki tekrara atlayan continue
anahtar kelimesini de kullanmıştık.
İçiçe döngüler söz konusu olduğunda break
ve continue
anahtar kelimeleri en içteki döngüye uygulanır. Dilerseniz döngü üzerinde daha sonra break
ya da continue
ile kullanabileceğiniz bir döngü etiketi bildirebilirsiniz. Bu durumda break
ve continue
anahtar kelimeleri en içteki döngüye değil etiketlenen döngüye uygulanırlar. Aşağıda iki adet içiçe geçmiş döngü örneği yer almaktadır:
fn main() { let mut sayaç = 0; 'saydır: loop { println!("sayaç: {}", sayaç); let mut kalan = 10; loop { println!("Kalan: {}", kalan); if kalan == 9 { break; } if sayaç == 2 { break 'saydır; } kalan -= 1; } sayaç += 1; } println!("Sayaç durdu: {}", sayaç); }
Saydır
etiketine sahip olan dış döngü 0'dan 2'ye kadar sayar. Etiketsiz olan iç döngü ise 10'dan 9'a doğru geri sayım yapar. Etiketsiz olan ilk break
yalnızca iç döngüden, break 'saydır;
ifadesiyse dış döngüden çıkar. Bu kod aşağıdaki çıktıyı üretir:
$ cargo run
Compiling donguler v0.1.0 (/home/rusdili/projeler/donguler)
Finished dev [unoptimized + debuginfo] target(s) in 0.48s
Running `target/debug/donguler`
sayaç: 0
Kalan: 10
Kalan: 9
sayaç: 1
Kalan: 10
Kalan: 9
sayaç: 2
Kalan: 10
Sayaç durdu: 2
Döngülerden Değer Döndürmek
Döngü kullanımlarından biri de, iş parçacıklarının işlerini bitirip bitirmediğinin kontrolü gibi başarısız olması muhtemel işlemleri yeniden denemektir. Hem ayrıca işlem sonucunu bu döngünün dışında kalan kod bölümüne de aktarmanız gerekebilir. Bunu yapabilmek için döngüyü döngüyü sonlandıracak olan break
ifadesinden ardından döndürülmesini istediğiniz değeri eklemek yeterlidir. Bu değer örnekte gösterildiği gibi döngüden döndürülecektir:
fn main() { let mut sayaç = 0; let sonuç = loop { sayaç += 1; if sayaç == 10 { break sayaç * 2; } }; println!("Sonuç: {}", sonuç); }
Döngüden önce sayaç
adında bir değişken tanımlıyarak 0
değeriyle başlatıyoruz. Hemen ardından döngüden dönecek olan değeri depolayacağımız sonuç
değişkenini tanımlıyoruz. Döngü tekrarlandıkça sayaç değerine 1
ekleneceğinden sayacın 10
'a eşit olup olmadığını kontrol ediyor, değer 10
olduğunda break
anahtar sözcüğüne ek olarak sayaç * 2
değerini ekliyoruz. Döngü bitiminde değeri sonuç
değişkenine atayacak olan ifadeyi noktalı virgül ile bitirdiğimize dikkat edin. Nihayetinde programı sonuç
değişkenine atanan 20 değerini yazdıracak şekilde tamamlayıp bitiriyoruz.
while
ile Koşullu Döngüler
Programların genellikle döngü içinde bulunan koşulları değerlendirmeleri gerekir. Koşul doğru olduğu sürece çalışan döngü, koşulun yanlış olması durumunda programın break
çağrısı sonucunda durdurulur. Bu tür bir davranışı if
, else
ve break
kombinasyonlarını kullanarak uygulamak mümkündür. Eğer isterseniz bunu bir programla hemen şimdi deneyebilirsiniz. Fakat bu model o kadar yaygın biçimde kullanılmaktadır ki, Rust bunun için while
döngüsü adında yerleşik bir dil yapısı sunar. Örnek 3-3'te geriye doğru 3 tur dönen ve her dönüşünde döngünün bulunduğu turu yazdıran, son olarak bir mesaj yazdırarak döngüden çıkan program için while
döngüsünden yararlanıyoruz.
Dosya adı: src/main.rs
fn main() { let mut sayı = 3; while sayı != 0 { println!("{}!", sayı); sayı -= 1; } println!("Görev Tamamlandı!"); }
Bu yapı, loop
, if
, else
ve break
kullanarak yazacağınız bir programda gerekli olacak çok sayıda içiçe yuvalanmayı ortadan kaldıracağı için oldukça nettir. Ve bu kod, koşul doğru olduğu sürece çalışacak aksi halde döngüden çıkacaktır.
Bir Koleksiyonu for
Döngüsüyle Dolaşmak
Dizi gibi bir koleksiyonun öğeleri üzerinde yineleme yapmak için while
yapısını kullanmak isteyebilirsiniz. Mesela Örnek 3-4'teki döngü a
dizisindeki tüm öğeleri yazdırır:
Dosya adı: src/main.rs
fn main() { let a = [10, 20, 30, 40, 50]; let mut dizin = 0; while dizin < 5 { println!("Değer: {}", a[dizin]); dizin += 1; } }
Bu kod dizideki elemaları sayar. Bunu 0 dizininden başlayarak koleksiyonun sonuncu dizinine yani koşulumuz dizin < 5
'in doğru olmadığı noktaya dek döngü şeklinde tekrarlayarak yapar. Bu kod çalıştırıldığında dizideki tüm öğeler yazdırırılır:
$ cargo run ✔
Compiling donguler v0.1.0 (/home/rusdili/projeler/donguler)
Finished dev [unoptimized + debuginfo] target(s) in 0.29s
Running `/home/rusdili/projeler/donguler/target/debug/donguler`
Değer: 10
Değer: 20
Değer: 30
Değer: 40
Değer: 50
Beklendiği gibi dizideki beş elemanın her biri terminalde görünür. Bir noktada dizin
değeri 5
'e ulaşsa bile, diziden altıncı değeri alınmadan önce döngü yürütmeyi durdurur.
Ancak bu yaklaşım dizin değeri ya da test koşulunun yanlış olduğu hallerde hataya açık olup programın paniklemesine neden olur. Örneğin eğer a dizisini 4 elemandan oluşacak şekilde yeniden düzenler ve döngü koşulunu dizin < 4
şeklinde güncellemeyi unutursanız kodunuz panikleyecektir. Ayrıca bu tasarım derleyicinin, döngü boyunca her tekrarda koşulun dizi sınırlarını aşıp aşmadığını kontrol edecek ek çalışma zamanı kodları eklemesini gerektireceğinden yavaş kalacaktır.
Alternatif olarak bir koleksiyondaki her öğeyi ayrı ayrı işlemek için daha kısa ve özlü olan for
döngüsünü kullanabilirsiniz. Bir for
döngüsü Örnek 3-5'teki koda benzer:
Dosya adı: src/main.rs
fn main() { let a = [10, 20, 30, 40, 50]; for öğe in a { println!("Değer: {}", öğe); } }
Bu kodu çalıştırdığımızda Örnek 3-4'tekiyle aynı mesajları alırız. Daha da önemlisi artık kodun güvenliğini artırmış, dizi eleman sayısının ötesine geçmek ya da gereği kadar tur yapmamaktan kaynaklı bazı öğelerin işlenememesi gibi hata olasılıklarını ortadan kaldırmış olduk.
Hem ayrıca for
döngüsü kullanımında dizi öğe sayısının değişmesi, Örnek 3-4'te olduğu gibi kodun yeniden güncellenmesini gerektirmez.
Kısa ve güvenle kullanılıyor olması for
döngüsünün Rust'ta en yaygın kullanılan döngü yapısı olmasını sağlar. Geri sayım için while
döngüsü kullanan Örnek 3-3'te olduğu gibi pekçok Rust geliştiricisi, belli sayıda tekrarlanacak kodlar için bile for
döngüsünden yararlanır. Geliştiriciler bunu yaparken, belli bir başlangıç ve bitiş sayısı arasında kalan tüm sayıları sırayla üreten ve standart kitaplık tarafından sağlanan bir Range
aralığı kullanırlar.
Aralığı tersine çevirebilmek içinse aşağıda gösterildiği gibi for döngüsü eşliğinde henüz görmediğimiz rev
metodu kullanılır:
Dosya adı: src/main.rs
fn main() { for sayı in (1..4).rev() { println!("{}", sayı); } println!("Görev Tamamlandı!"); }
Bu kod size daha hoş görünmüyor mu?
Özet
Değişkenler, skaler ve bileşik veri türleri, işlevler, yorumlar, if
ifadeleri ve döngüleri içeren oldukça büyük bir bölümü biigi sahibi olup bölümü tamamladınız. Burada tartışılan kavramları pekiştirmek amacıyla sonraki satırda önereceğimiz programları yazmayı deneyin.
- Isı değerlerini Fahrenheit ve Celsius dereceleri arasında dönüştürün.
- Fibonacci serisindeki n. eleman değerini hesaplayın.
- Bir noel şarkısı olan "The Twelve Days of Christmas"ın nakaratlarını kullanarak şarkının sözlerini yazdırın.
Devam etmeye hazır olduğunuzda diğer programlama dillerinde olmayan Rust'ın mülkiyet kavramından bahsedeceğiz.