Rust'ta match, bir değeri sırayla desenlerle karşılaştırıp ilk eşleşen kolun kodunu çalıştıran bir ifadedir; tüm olası durumları kapsamak zorundadır ve bir değer üretir. Yalnızca tek bir desenle ilgilendiğinizde if let, eşleşmeyen durumda fonksiyondan erken çıkmak istediğinizde let...else, sadece doğru veya yanlış sonucu gerektiğinde matches! makrosu daha kısa bir yazım sunar.

Bu yazıda match kollarının nasıl değerlendirildiğini, desenlerin içteki veriyi nasıl bağladığını, kapsayıcılık (exhaustiveness) denetimini, _ ve isimli yakalama kollarını, ardından if let, let...else, while let, matches! ve 2024 edition'daki let zincirlerini çalışan örneklerle anlatıyoruz. Desenlerin kendi sözdizimi, yani aralıklar, @ bağlamaları, guard'lar ve parçalama ayrıntıları, Rust pattern sözdizimi yazısının konusu.

match nasıl çalışır?

match nasıl çalışır?

The Book match'i bozuk para ayıran bir makineye benzetir: para yoldaki ilk uygun deliğe düşer. match anahtar kelimesinden sonra bir ifade gelir; ardından her biri desen => kod biçiminde kollar sıralanır. Rust değeri yukarıdan aşağıya her kolun deseniyle karşılaştırır ve eşleşen ilk kolu çalıştırır; sonraki kollara bakılmaz. Kolun kodu bir ifadedir ve sonucu tüm match ifadesinin değeri olur. Kod birden fazla satırsa süslü parantez kullanılır ve virgül isteğe bağlı hâle gelir.

rust
#[derive(Debug)]
enum Seri {
    Cumhuriyet,
    Ataturk,
}

enum Para {
    Kurus1,
    Kurus5,
    Kurus10,
    Kurus25(Seri),
}

fn kurus(para: &Para) -> u8 {
    match para {
        Para::Kurus1 => 1,
        Para::Kurus5 => 5,
        Para::Kurus10 => 10,
        Para::Kurus25(seri) => {
            println!("{seri:?} serisinden 25 kuruş");
            25
        }
    }
}

fn main() {
    let cuzdan = [Para::Kurus1, Para::Kurus25(Seri::Ataturk), Para::Kurus10,
                  Para::Kurus5, Para::Kurus25(Seri::Cumhuriyet)];
    let toplam: u32 = cuzdan.iter().map(|p| kurus(p) as u32).sum();
    println!("toplam: {toplam}");
}
// çıktı: Ataturk serisinden 25 kuruş
// çıktı: Cumhuriyet serisinden 25 kuruş
// çıktı: toplam: 66

Para::Kurus25(seri) kolu, eşleşme sırasında varyantın içindeki değeri seri adlı değişkene bağlar ve kolun kodu bu değişkeni kullanabilir. Enum'lardan veri çıkarmanın temel yolu budur. if ifadesinin koşulu bool olmak zorundayken match her tipte değerle çalışır; bu örnekte bir enum referansıyla çalıştı.

Option üzerinde match: adım adım

Option<T> ile çalışmanın en temel yolu da match'tir. The Book'taki plus_one örneğinin uyarlamasında fonksiyon, içinde değer varsa bir ekler, yoksa None döndürür. bir_ekle(Some(5)) çağrısında Some(5) önce None deseniyle karşılaştırılır ve eşleşmez; ikinci kolda Some(i) ile aynı varyant olduğu için eşleşir, i değeri 5'e bağlanır ve kol Some(6) üretir. bir_ekle(None) çağrısında ise ilk kol hemen eşleşir ve diğer kollara hiç bakılmaz.

rust
fn bir_ekle(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

fn main() {
    println!("{:?}", bir_ekle(Some(5)));
    println!("{:?}", bir_ekle(None));
}
// çıktı: Some(6)
// çıktı: None

Bu kalıp Rust kodunda sürekli karşınıza çıkar: bir enum üzerinde eşleştir, içteki veriyi bir değişkene bağla ve ona göre kod çalıştır. Basit dönüşümler için Option metotları (map, and_then gibi) aynı işi daha kısa yapar.

Kapsayıcılık: tüm durumlar ele alınmalı

match kollarının desenleri bütün olasılıkları kapsamalıdır. Bir durumu unutursanız program derlenmez ve derleyici hangi desenin eksik olduğunu tam olarak söyler. Özellikle Option için bu, "değer kesin var" varsayımıyla null hatası yapmayı imkânsız hâle getirir:

rust
fn bir_ekle(x: Option<i32>) -> Option<i32> {
    match x {
        Some(i) => Some(i + 1),
    }
}

fn main() {
    println!("{:?}", bir_ekle(Some(5)));
}
// error[E0004]: non-exhaustive patterns: `None` not covered

Derleyicinin önerisi, eksik desen için None => todo!() gibi bir kol eklemektir. Doğru düzeltme None => None kolunu eklemektir. Bu denetim enum'lara yeni varyant eklediğinizde de çalışır: varyantı ele almayan her match derleme hatasına dönüşür ve böylece kodun güncellenmesi gereken yerler kendiliğinden listelenir.

Yakalama kolları: isim, _ ve ()

Bazı değerler için özel işlem yapıp geri kalanının hepsine aynı davranışı uygulamak istediğinizde son kola herhangi bir değerle eşleşen bir desen yazarsınız. Bu bir değişken adı olabilir; o zaman değer bu ada bağlanır ve kolda kullanılabilir. Değeri kullanmayacaksanız _ yazarsınız; _ her şeyle eşleşir ama hiçbir şeyi bağlamaz, bu yüzden kullanılmayan değişken uyarısı da oluşmaz. Hiçbir şey yapmamak için kolun kodu birim değeri () olabilir.

rust
fn hamle(zar: u8) -> String {
    match zar {
        3 => String::from("şapka kazandın"),
        7 => String::from("şapka kaybettin"),
        diger => format!("{diger} kare ilerle"),
    }
}

fn main() {
    for zar in [3, 9, 7] {
        println!("{}", hamle(zar));
    }
    let zar = 5;
    match zar {
        6 => println!("bir daha at"),
        _ => (), // başka bir şey yapma
    }
}
// çıktı: şapka kazandın
// çıktı: 9 kare ilerle
// çıktı: şapka kaybettin

Kollar sırayla değerlendirildiği için yakalama kolu en sonda olmalıdır. Onu daha yukarı koyarsanız altındaki kollar hiçbir zaman çalışmaz; program derlenir ama Rust bir uyarı verir ve kod beklediğinizden farklı davranır:

rust
fn main() {
    let zar = 3;
    match zar {
        diger => println!("{diger} kare ilerle"),
        3 => println!("şapka kazandın"),
    }
}
// warning: unreachable pattern
// çıktı: 3 kare ilerle

match bir ifadedir: kolların tipi aynı olmalı

match bir ifadedir: kolların tipi aynı olmalı

match bir değer ürettiği için sonucunu doğrudan bir değişkene atayabilir, fonksiyondan döndürebilir ya da başka bir ifadenin içinde kullanabilirsiniz. Bunun bir sonucu, bütün kolların aynı tipte değer üretmesi gerekmesidir. Bir kol &str, diğeri tamsayı döndürürse derleyici kabul etmez:

rust
fn main() {
    let n = 1;
    let ad = match n {
        1 => "bir",
        _ => 0,
    };
}
// error[E0308]: `match` arms have incompatible types

Birden fazla değeri aynı anda incelemek için onları bir demette toplayıp demet üzerinde eşleştirme yapmak yaygın bir kalıptır. Bir String üzerinde metin literal'leriyle eşleştirme yapmak içinse önce as_str() ile &str elde edilir:

rust
fn etiket(n: u32) -> String {
    match (n % 3, n % 5) {
        (0, 0) => String::from("FizzBuzz"),
        (0, _) => String::from("Fizz"),
        (_, 0) => String::from("Buzz"),
        _ => n.to_string(),
    }
}

fn main() {
    let liste: Vec<String> = (1..=15).filter(|n| n % 5 == 0 || n % 3 == 0).map(etiket).collect();
    println!("{liste:?}");

    let komut = String::from("durdur");
    let cevap = match komut.as_str() {
        "başlat" => "çalışıyor",
        "durdur" => "durdu",
        _ => "bilinmeyen komut",
    };
    println!("{cevap}");
}
// çıktı: ["Fizz", "Buzz", "Fizz", "Fizz", "Buzz", "Fizz", "FizzBuzz"]
// çıktı: durdu

Kolda return, continue ve break

Kolların aynı tipte olması kuralının önemli bir istisnası vardır: continue, break, return veya panic! ile biten bir kol hiçbir zaman değer üretmez. The Book, continue'nun tipinin hiçbir değeri olmayan never tipi ! olduğunu ve derleyicinin bu yüzden sonucun tipini diğer koldan aldığını açıklar. Bu sayede girdiyi ayrıştırıp hatalıları atlamak tek bir ifadeyle yazılabilir:

rust
fn main() {
    let girdiler = ["12", "abc", "30"];
    let mut toplam = 0;
    for g in girdiler {
        let n: u32 = match g.parse() {
            Ok(n) => n,
            Err(_) => continue, // tipi !, bu yüzden n yine u32
        };
        toplam += n;
    }
    println!("{toplam}");
}
// çıktı: 42

Referans üzerinde match

Bir koleksiyonu referansla dolaşırken elinizdeki değer &Option<i32> gibi bir referanstır. Some(n) desenini doğrudan yazabilirsiniz; Rust Reference'ın binding mode kuralına göre referans olmayan bir desen bir referansla eşleştirildiğinde bağlamalar otomatik olarak ref olur. Böylece n değeri taşımaz, &i32 tipinde bir referans olur ve koleksiyon sonradan da kullanılabilir:

rust
fn main() {
    let degerler = vec![Some(3), None, Some(10)];
    let mut toplam = 0;
    for d in &degerler {
        match d {
            Some(n) => toplam += n, // n: &i32
            None => println!("boş değer atlandı"),
        }
    }
    println!("toplam {toplam}, uzunluk {}", degerler.len());
}
// çıktı: boş değer atlandı
// çıktı: toplam 13, uzunluk 3

if let: tek bir desen için kısa yol

Yalnızca bir desenle ilgilenip geri kalan her şeyi yok saymak istediğinizde match yazmak gereksiz törendir: kapsayıcılık için sona _ => () eklemeniz gerekir. if let bir desen ile bir ifadeyi eşittir işaretiyle ayırır ve yalnızca desen eşleşirse bloğu çalıştırır. İsterseniz else ekleyebilirsiniz; else bloğu, eşdeğer match'teki _ kolunun yerini tutar. else if ve else if let ile birbirinden bağımsız koşulları da zincirleyebilirsiniz.

rust
fn main() {
    let ayar: Option<u8> = Some(3);
    if let Some(azami) = ayar {
        println!("azami değer {azami} olarak ayarlı");
    }

    let renk: Option<&str> = None;
    let sali_mi = false;
    let yas: Result<u8, _> = "34".parse();

    if let Some(r) = renk {
        println!("arka plan: {r}");
    } else if sali_mi {
        println!("salı günü yeşil");
    } else if let Ok(yas) = yas {
        if yas > 30 { println!("mor"); } else { println!("turuncu"); }
    } else {
        println!("mavi");
    }
}
// çıktı: azami değer 3 olarak ayarlı
// çıktı: mor

Bedeli kapsayıcılık denetimini kaybetmektir. The Book bu seçimi bir takas olarak tanımlar: kısalık mı yoksa unutulan durumların derleyici tarafından yakalanması mı daha önemli? Birden çok varyantı ele almanız gerekiyorsa match daha güvenlidir; son else'i atlarsanız derleyici sizi uyarmaz.

let...else: mutlu yolda kalmak

Sık görülen bir kalıp, bir değer eşleşirse onunla devam etmek, eşleşmezse fonksiyondan çıkmaktır. Bunu if let ile yazınca asıl iş iç içe bloklara gömülür. let...else, desen eşleşirse bağlamaları dış kapsamda kullanılabilir yapar; eşleşmezse else bloğuna girer ve bu blok fonksiyondan çıkmak, döngüden kırılmak ya da panik gibi bir yolla akışı mutlaka ayırmak zorundadır:

rust
fn ilk_sayi(metin: &str) -> Option<u32> {
    let Some(ilk) = metin.split_whitespace().next() else {
        return None;
    };
    let Ok(sayi) = ilk.parse::<u32>() else {
        println!("'{ilk}' sayı değil");
        return None;
    };
    Some(sayi * 2)
}

fn main() {
    println!("{:?}", ilk_sayi("21 elma"));
    println!("{:?}", ilk_sayi("elma 21"));
    println!("{:?}", ilk_sayi("   "));
}
// çıktı: Some(42)
// çıktı: 'elma' sayı değil
// çıktı: None
// çıktı: None

Bu yazımda her adımın başarısızlık yolu tek satırda görülür ve ana mantık girinti olmadan ilerler. sayi ve ilk değişkenleri let...else satırından sonra normal değişkenler gibi kullanılır.

while let ve let zincirleri

while let, desen eşleştiği sürece döngüyü sürdürür. Bir yığından pop ile eleman almak tipik örnektir: pop None döndürünce döngü biter. Rust 2024 edition ile if ve while koşullarında birden fazla let ifadesini ve sıradan bool koşullarını && ile zincirleyebilirsiniz. Edition Guide bu özelliğin 2024 edition'a bağlı olduğunu belirtir.

rust
fn main() {
    let mut yigin = vec![1, 2, 3];
    let mut alinan = Vec::new();
    while let Some(ust) = yigin.pop() {
        alinan.push(ust);
    }
    println!("{alinan:?}");

    let yas: Result<u8, _> = "34".parse();
    if let Ok(yas) = yas && yas > 30 {
        println!("30'dan büyük: {yas}");
    }

    let sayilar = [5u8, 250];
    let mut it = sayilar.iter();
    if let Some(a) = it.next() && let Some(b) = it.next() {
        println!("{:?}", a.checked_add(*b));
    }
}
// çıktı: [3, 2, 1]
// çıktı: 30'dan büyük: 34
// çıktı: Some(255)

matches! makrosu

Bir değerin belirli bir desene uyup uymadığını yalnızca bool olarak öğrenmek istiyorsanız standart kütüphanedeki matches! makrosu tek satırlık bir çözümdür. Desen sözdizimi match kolundakiyle aynıdır ve isteğe bağlı bir if guard'ı da alabilir:

rust
fn main() {
    let harf = 'ğ';
    println!("{}", matches!(harf, 'a'..='z'));
    println!("{}", matches!(harf, 'a'..='z' | 'ç' | 'ğ' | 'ı' | 'ö' | 'ş' | 'ü'));

    let deger = Some(4);
    println!("{}", matches!(deger, Some(x) if x > 2));
}
// çıktı: false
// çıktı: true
// çıktı: true

Hangisini ne zaman kullanmalı?

Hangisini ne zaman kullanmalı? match, if let / else, let...else, while let
YapıKapsayıcılık denetimiUygun olduğu durum
matchVarBirden çok durumu ayrı ayrı ele almak, değer üretmek
if let / elseYokTek bir desenle ilgilenmek
let...elseYok, else akışı ayırmalıEşleşmezse erken çıkmak
while letYokDesen eşleştikçe döngü
matches!YokYalnızca true veya false sonucu

Pratik bir kural olarak, ele almanız gereken durum sayısı ikiden fazlaysa ya da enum ileride yeni varyant kazanabilecekse match yazın; derleyici gelecekteki eksikleri sizin yerinize bulur. Tek bir varyantı işleyip gerisini bilinçli olarak yok saydığınız yerlerde if let, ön koşulları kontrol edip başarısızlıkta hemen çıktığınız fonksiyon başlarında let...else, filtreleme ve doğrulama ifadelerinde ise matches! kodu en okunaklı hâle getirir.

Sık yapılan hatalar

  • Yakalama kolunu başa koymak: Sonraki kollar ulaşılamaz olur; "unreachable pattern" uyarısını hata gibi ciddiye alın.
  • Kollarda farklı tip döndürmek: E0308 alırsınız. Bütün kolların ortak bir tipe, örneğin String'e dönüştüğünden emin olun.
  • String ile literal eşleştirmeye çalışmak: match s yerine match s.as_str() yazın.
  • Değeri match içinde taşımak: match deger ifadesinde Some(s) deseni String'i taşır; sonrasında değeri kullanmak istiyorsanız match &deger yazın.
  • Dış değişkenle karşılaştırdığını sanmak: Kolda Some(y) yazmak dıştaki y ile karşılaştırma yapmaz, yeni bir y tanımlar. Karşılaştırma için guard kullanılır; bkz. pattern sözdizimi.

Sık Sorulan Sorular

match ile switch arasındaki fark nedir?

match yalnızca sabit değerlerle değil enum varyantları, demetler, aralıklar ve iç içe yapılarla eşleşir; içteki veriyi değişkenlere bağlar, bir değer üretir ve tüm durumların kapsanmasını zorunlu kılar. Kollar arasında düşme (fall-through) yoktur; ilk eşleşen kol çalışır.

if let içinde guard kullanabilir miyim?

The Book'a göre match guard'ları yalnızca match kollarında vardır. 2024 edition'da ise if let Some(x) = deger && x > 2 gibi bir let zinciriyle benzer bir koşulu yazabilirsiniz.

Bir kolda birden çok değeri nasıl eşleştiririm?

Desendeki veya işleci | ile 1 | 2 => ... yazabilir, sayı ve karakterler için 1..=5 gibi kapsayıcı aralıklar kullanabilirsiniz. Bu desen biçimlerinin tamamı pattern sözdizimi yazısında anlatılıyor.

let...else bloğunda ne yazabilirim?

else bloğu normal akışa geri dönemez; return, döngü içinde break veya continue, ya da panic! gibi bir çıkış içermelidir. Eşleşme başarısız olduğunda bağlanacak bir değer olmadığı için kod devam edemez.

match performansı if zincirinden kötü mü?

Belgeler iki yapı arasında bir performans karşılaştırması vermez. Seçimi okunabilirliğe ve kapsayıcılık denetimine göre yapın; birden çok varyantlı enum'larda match hem daha okunaklı hem daha güvenlidir.

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 · Enum ve Option · Pattern sözdizimi · Kontrol akışı · Result ve hata yönetimi · Rust 2024 edition · Rust nedir?