if let ve let...else ile Özlü Kontrol Akışı
if let sözdizimi, if ve let anahtar kelimelerini birleştirerek yalnızca tek bir desenle eşleşen değerleri işleyip geri kalanları yok saymanın daha özlü bir yolunu sunar. Liste 6-6’da, max_ayari değişkenindeki Option<u8> değerini eşleştiren ancak yalnızca değer Some varyantı olduğunda kod çalıştırmak istediğimiz programı inceleyelim.
fn main() {
let max_ayari = Some(3u8);
match max_ayari {
Some(max) => println!("Maksimum değer {max} olarak ayarlandı"),
_ => (),
}
}
Some olduğunda kod çalıştıran bir match ifadesiDeğer Some ise, varyantın içindeki veriyi desendeki max değişkenine bağlayarak ekrana yazdırırız. None değeriyle ise hiçbir işlem yapmak istemiyoruz. Ancak match ifadesinin tüm durumları kapsama zorunluluğunu karşılamak için, tek bir varyantı işledikten sonra bile _ => () kolunu eklemek zorunda kalırız; bu da her seferinde yazılması can sıkıcı bir kalıp koddur.
Bunun yerine aynı mantığı if let kullanarak çok daha kısa bir biçimde yazabiliriz. Aşağıdaki kod, Liste 6-6’daki match ifadesiyle birebir aynı şekilde çalışır:
fn main() {
let max_ayari = Some(3u8);
if let Some(max) = max_ayari {
println!("Maksimum değer {max} olarak ayarlandı");
}
}
if let sözdizimi, aralarında eşittir (=) işareti bulunan bir desen ve bir ifade alır. Çalışma mantığı match ile aynıdır: Eşittir işaretinin sağındaki ifade match yapısına verilen değere, solundaki desen ise ilk match koluna karşılık gelir. Bu örnekte desen Some(max) şeklindedir ve max değişkeni Some varyantının içindeki değere bağlanır. Ardından max değişkenini, tıpkı eşdeğer match kolunda yaptığımız gibi if let bloğunun gövdesinde kullanabiliriz. if let bloğu içindeki kod, yalnızca değer belirtilen desenle eşleştiğinde çalışır.
if let kullanmak; daha az kod yazmak, daha az girinti kullanmak ve kalıp kodlardan kurtulmak demektir. Buna karşılık, match yapısının zorunlu kıldığı ve olası hiçbir durumu gözden kaçırmamanızı güvence altına alan kapsamlılık denetiminden vazgeçmiş olursunuz. match ile if let arasında seçim yapmak, içinde bulunduğunuz senaryoda ne amaçladığınıza ve kodda özlü bir anlatım kazanmanın kapsamlılık denetiminden vazgeçmeye değip değmeyeceğine bağlıdır.
Başka bir deyişle if let yapısını, değer belirli bir desenle eşleştiğinde kod çalıştıran ve diğer tüm değerleri yok sayıp geçen bir match ifadesi için sözdizimsel şeker (syntax sugar) olarak düşünebilirsiniz.
Bir if let ifadesine else bloğu da ekleyebiliriz. else bloğunun içindeki kod, if let ve else yapısına eşdeğer bir match ifadesindeki _ koluna yazacağımız kodla birebir aynı işlevi görür. Liste 6-4’te tanımladığımız ve YirmiBesKurus varyantının içinde bir Eyalet değeri barındırdığı MadeniPara enum’ını hatırlayın. Gördüğümüz çeyreklik (25 kuruşluk) dışındaki tüm madeni paraları saymak, aynı zamanda çeyrekliklerin hangi eyalete ait olduğunu duyurmak isteseydik bunu şöyle bir match ifadesiyle yazabilirdik:
#[derive(Debug)]
enum Eyalet {
Alabama,
Alaska,
// --snip--
}
enum MadeniPara {
Kurus,
BesKurus,
OnKurus,
YirmiBesKurus(Eyalet),
}
fn main() {
let para = MadeniPara::Kurus;
let mut sayac = 0;
match para {
MadeniPara::YirmiBesKurus(eyalet) => {
println!("{eyalet:?} eyaletinden çeyreklik!")
}
_ => sayac += 1,
}
}
Ya da aynı işlemi aşağıdaki gibi bir if let ve else ifadesiyle gerçekleştirebilirdik:
#[derive(Debug)]
enum Eyalet {
Alabama,
Alaska,
// --snip--
}
enum MadeniPara {
Kurus,
BesKurus,
OnKurus,
YirmiBesKurus(Eyalet),
}
fn main() {
let para = MadeniPara::Kurus;
let mut sayac = 0;
if let MadeniPara::YirmiBesKurus(eyalet) = para {
println!("{eyalet:?} eyaletinden çeyreklik!");
} else {
sayac += 1;
}
}
let...else ile “Mutlu Yolda” Kalmak
Programlamada sıkça karşılaşılan bir akış biçimi, beklenen bir değer mevcut olduğunda bazı hesaplamalar yapmak, aksi takdirde ise doğrudan varsayılan bir değer döndürmektir. Eyalet verisi taşıyan madeni paralar örneğimizden devam edelim: Çeyrekliğin üzerindeki eyaletin ne kadar eski olduğuna bakarak esprili bir mesaj üretmek isteseydik, Eyalet üzerinde eyaletin yaşını (belirli bir tarihte var olup olmadığını) kontrol eden şöyle bir metot tanımlayabilirdik:
#[derive(Debug)] // birazdan eyaleti inceleyebilmek için
enum Eyalet {
Alabama,
Alaska,
// --snip--
}
impl Eyalet {
fn su_tarihte_var_miydi(&self, yil: u16) -> bool {
match self {
Eyalet::Alabama => yil >= 1819,
Eyalet::Alaska => yil >= 1959,
// -- snip --
}
}
}
enum MadeniPara {
Kurus,
BesKurus,
OnKurus,
YirmiBesKurus(Eyalet),
}
fn eyalet_ceyrekligini_tanimla(para: MadeniPara) -> Option<String> {
if let MadeniPara::YirmiBesKurus(eyalet) = para {
if eyalet.su_tarihte_var_miydi(1900) {
Some(format!("{eyalet:?} Amerika'ya göre oldukça eski!"))
} else {
Some(format!("{eyalet:?} nispeten yeni."))
}
} else {
None
}
}
fn main() {
if let Some(tanim) =
eyalet_ceyrekligini_tanimla(MadeniPara::YirmiBesKurus(Eyalet::Alaska))
{
println!("{tanim}");
}
}
Ardından Liste 6-7’de olduğu gibi, madeni paranın türünü eşleştirmek için if let kullanabilir ve koşul gövdesinin kapsamında geçerli bir eyalet değişkeni bağlayabiliriz.
#[derive(Debug)] // birazdan eyaleti inceleyebilmek için
enum Eyalet {
Alabama,
Alaska,
// --snip--
}
impl Eyalet {
fn su_tarihte_var_miydi(&self, yil: u16) -> bool {
match self {
Eyalet::Alabama => yil >= 1819,
Eyalet::Alaska => yil >= 1959,
// -- snip --
}
}
}
enum MadeniPara {
Kurus,
BesKurus,
OnKurus,
YirmiBesKurus(Eyalet),
}
fn eyalet_ceyrekligini_tanimla(para: MadeniPara) -> Option<String> {
if let MadeniPara::YirmiBesKurus(eyalet) = para {
if eyalet.su_tarihte_var_miydi(1900) {
Some(format!("{eyalet:?} Amerika'ya göre oldukça eski!"))
} else {
Some(format!("{eyalet:?} nispeten yeni."))
}
} else {
None
}
}
fn main() {
if let Some(tanim) =
eyalet_ceyrekligini_tanimla(MadeniPara::YirmiBesKurus(Eyalet::Alaska))
{
println!("{tanim}");
}
}
if let içine yerleştirilmiş koşullarla bir eyaletin 1900 yılında var olup olmadığını kontrol etmeBu yaklaşım işimizi görür, ancak asıl mantığı if let bloğunun içine doğru iter; yapılacak işlemler karmaşıklaştıkça en üst düzeydeki dalların birbirleriyle nasıl ilişkili olduğunu takip etmek zorlaşabilir. Rust’ta ifadelerin değer üretme özelliğinden yararlanarak, Liste 6-8’de olduğu gibi if let ifadesinden ya eyalet değerini üretebilir ya da fonksiyondan erken dönüş yapabiliriz. (Benzer bir yapıyı match ile de kurabilirsiniz.)
#[derive(Debug)] // birazdan eyaleti inceleyebilmek için
enum Eyalet {
Alabama,
Alaska,
// --snip--
}
impl Eyalet {
fn su_tarihte_var_miydi(&self, yil: u16) -> bool {
match self {
Eyalet::Alabama => yil >= 1819,
Eyalet::Alaska => yil >= 1959,
// -- snip --
}
}
}
enum MadeniPara {
Kurus,
BesKurus,
OnKurus,
YirmiBesKurus(Eyalet),
}
fn eyalet_ceyrekligini_tanimla(para: MadeniPara) -> Option<String> {
let eyalet = if let MadeniPara::YirmiBesKurus(eyalet) = para {
eyalet
} else {
return None;
};
if eyalet.su_tarihte_var_miydi(1900) {
Some(format!("{eyalet:?} Amerika'ya göre oldukça eski!"))
} else {
Some(format!("{eyalet:?} nispeten yeni."))
}
}
fn main() {
if let Some(tanim) =
eyalet_ceyrekligini_tanimla(MadeniPara::YirmiBesKurus(Eyalet::Alaska))
{
println!("{tanim}");
}
}
if let kullanmaNe var ki bu yapıyı okumak da kendi içinde biraz yorucudur! if let ifadesinin bir dalı değişkene atanacak bir değer üretirken, diğer dalı fonksiyondan tamamen çıkış yapar.
Bu yaygın akış kalıbını çok daha temiz ifade edebilmek için Rust’ta let...else sözdizimi bulunur. let...else yapısı, if let sözdizimine çok benzer biçimde sol tarafta bir desen ve sağ tarafta bir ifade alır; ancak bir if bloğu yoktur, yalnızca else bloğu vardır. Desen eşleşirse, eşleşen değer desendeki değişkene doğrudan dış kapsamda bağlanır. Desen eşleşmezse program akışı else koluna girer ve bu kolun fonksiyondan dönerek (veya akışı keserek) mevcut kapsamdan çıkması gerekir.
Not: let...else yapısında eşleşen değişkenin (eyalet) yeni bir iç blok yerine doğrudan dış kapsama bağlanabilmesi, derleyicinin else bloğuna uyguladığı kesin bir kurala dayanır: else bloğu mevcut kontrol akışını mutlaka kesmek, yani ıraksamak (diverge) zorundadır. Bunun için else bloğunun return ile fonksiyondan dönmesi, bir döngü içindeyse break veya continue ile akışı yönlendirmesi ya da panic! / todo!() gibi normal biçimde sonlanmayan (! — asla türü üreten) bir ifadeyle bitmesi gerekir. Eğer else bloğunun normal şekilde tamamlanıp alt satıra geçmesine izin verilseydi eyalet değişkeni ilk değerini almamış olurdu; Rust derleyicisi bunu error[E0308]: `else` clause of `let...else` does not diverge (let...else yapısının else bloğu ıraksamıyor) hatasıyla derleme zamanında engeller.
Liste 6-9’da, if let yerine let...else kullanıldığında Liste 6-8’deki kodun nasıl göründüğünü inceleyebilirsiniz.
#[derive(Debug)] // birazdan eyaleti inceleyebilmek için
enum Eyalet {
Alabama,
Alaska,
// --snip--
}
impl Eyalet {
fn su_tarihte_var_miydi(&self, yil: u16) -> bool {
match self {
Eyalet::Alabama => yil >= 1819,
Eyalet::Alaska => yil >= 1959,
// -- snip --
}
}
}
enum MadeniPara {
Kurus,
BesKurus,
OnKurus,
YirmiBesKurus(Eyalet),
}
fn eyalet_ceyrekligini_tanimla(para: MadeniPara) -> Option<String> {
let MadeniPara::YirmiBesKurus(eyalet) = para else {
return None;
};
if eyalet.su_tarihte_var_miydi(1900) {
Some(format!("{eyalet:?} Amerika'ya göre oldukça eski!"))
} else {
Some(format!("{eyalet:?} nispeten yeni."))
}
}
fn main() {
if let Some(tanim) =
eyalet_ceyrekligini_tanimla(MadeniPara::YirmiBesKurus(Eyalet::Alaska))
{
println!("{tanim}");
}
}
let...else kullanmaBu sayede fonksiyonun ana gövdesinin, if let örneğindeki gibi iki dal arasında bambaşka kontrol akışlarına bölünmeden, girintisiz biçimde doğrudan “mutlu yolda” (happy path) — yani beklenen ana işleyiş akışında — kaldığına dikkat edin.
Programınızda match kullanarak ifade etmenin fazla ayrıntılı kaldığı bir mantıkla karşılaştığınızda, if let ve let...else yapılarının da Rust araç çantanızda bulunduğunu unutmayın.
Not: Bir kontrol akışı yapısı seçerken şu pratik ölçütleri kullanabilirsiniz:
match: Birden fazla varyantı ayrı ayrı ele almanız gerektiğinde veya ileride enum’a yeni bir varyant eklendiğinde derleyicinin sizi derleme hatasıyla uyarmasını (kapsamlılık denetimi) istediğinizdematchkullanın (ve_joker deseninden kaçının).if let: Yalnızca tek bir desenle ilgilenip orada kısa bir işlem yapacak, diğer tüm durumları yok sayacak (veya tek birelsebloğunda toplayacak) isenizif lettercih edin.let...else: Fonksiyonun devam edebilmesi için bir değerin belirli bir desenle eşleşmesi ön koşulsa (erken doğrulama), iç içe girintilerden kaçınıp çıkarılan veriyi doğrudan dış kapsamda kullanarak ana akışta (“mutlu yolda”) kalmak içinlet...elsekullanın.
Özet
Bu bölümde, numaralandırılmış bir değerler kümesinden yalnızca biri olabilen özel türler tanımlamak için enum’ların nasıl kullanılacağını ele aldık. Standart kütüphanedeki Option<T> türünün, hataları derleme zamanında önlemek üzere tür sisteminden yararlanmanıza nasıl yardımcı olduğunu gördük. Enum değerleri kendi içlerinde veri barındırdığında, ele almanız gereken durum sayısına bağlı olarak bu verileri çıkarmak ve kullanmak için match, if let veya let...else yapılarından yararlanabilirsiniz.
Rust programlarınız artık çalıştığınız problem alanındaki kavramları struct’lar ve enum’lar aracılığıyla ifade edebilir. API’nizde kullanmak üzere özel türler oluşturmak tür güvenliği sağlar: Derleyici, her fonksiyonun yalnızca beklediği türdeki değerleri almasını güvence altına alır.
Kullanıcılarınıza kullanımı anlaşılır ve yalnızca gerçekten ihtiyaç duyacakları öğeleri dışarıya açan iyi düzenlenmiş bir API sunabilmek için şimdi Rust’ın modül sistemine geçelim.