Rust'ta pattern (desen), bir değerin yapısını tarif eden ve eşleşirse parçalarını değişkenlere bağlayan sözdizimidir. Desenler yalnızca match kollarında değil; let, if let, while let, let...else, for döngüleri ve fonksiyon parametrelerinde de kullanılır. Literal'ler, değişken adları, _, |, aralıklar, struct, enum, demet ve slice parçalama, .. ile kalanı atlama, @ bağlamaları ve match guard'ları bu sözdiziminin parçalarıdır.

Bu yazı desen sözdiziminin başvuru niteliğindeki özetidir: her biçimi kısa, çalışan bir örnekle gösterir ve refutability (çürütülebilirlik) kavramını, _ ile _ad arasındaki sahiplik farkını ve 2024 edition'daki binding mode kısıtlarını açıklar. match ve if let'in kontrol akışı olarak kullanımı Rust match ve if let yazısında anlatılıyor.

Desenler nerelerde kullanılır?

Fark etmeden desen yazdığınız pek çok yer vardır. let x = 5; satırındaki x bile bir desendir: her şeyle eşleşir ve değeri bağlar. Aynı şekilde for (i, deger) in ... ifadesindeki demet ve bir fonksiyonun parametre listesi de desendir. Closure parametreleri de fonksiyon parametreleri gibi desen kabul eder.

rust
fn konum_yaz(&(x, y): &(i32, i32)) {
    println!("konum: ({x}, {y})");
}

fn main() {
    let (a, b, c) = (1, 2, 3); // let ile demet parçalama
    println!("{a} {b} {c}");

    for (i, harf) in ['a', 'b'].iter().enumerate() {
        println!("{harf} {i}. sırada");
    }

    konum_yaz(&(3, 5));

    let ciftler = [(1, 2), (3, 4)];
    let toplamlar: Vec<i32> = ciftler.iter().map(|&(x, y)| x + y).collect();
    println!("{toplamlar:?}");
}
// çıktı: 1 2 3
// çıktı: a 0. sırada
// çıktı: b 1. sırada
// çıktı: konum: (3, 5)
// çıktı: [3, 7]

let ile demet parçalarken desendeki eleman sayısı demetinkine eşit olmalıdır; let (x, y) = (1, 2, 3); derlenmez. konum_yaz fonksiyonunda &(x, y) deseni, &(3, 5) referansını açıp içindeki iki değeri ayrı değişkenlere bağlar.

Refutability: eşleşmeyebilecek desenler

Refutability: eşleşmeyebilecek desenler: let, fonksiyon parametresi, for, if let, while let, let...else, match kolları

Desenler iki türdür. Her olası değerle eşleşen desenlere irrefutable (çürütülemez), bazı değerlerde eşleşmeyebilecek desenlere refutable (çürütülebilir) denir. x çürütülemez, Some(x) ise çürütülebilirdir, çünkü değer None olabilir. Fonksiyon parametreleri, let ve for yalnızca çürütülemez desen kabul eder; değer eşleşmezse program anlamlı bir şey yapamaz. let ile çürütülebilir bir desen kullanırsanız derleyici E0005 verir:

rust
fn main() {
    let deger: Option<i32> = None;
    let Some(x) = deger;
    println!("{x}");
}
// error[E0005]: refutable pattern in local binding

Derleyicinin önerdiği çözüm let...else kullanmaktır; eşleşmezse çalışacak bir çıkış yolu verirsiniz. Tersi de geçerlidir: if let, while let ve let...else çürütülemez bir desenle de çalışır ama derleyici uyarır, çünkü koşullu bir yapının tek amacı başarısızlığı ele almaktır:

rust
fn main() {
    let x = 5 else {
        return;
    };
    println!("{x}");
}
// warning: unreachable `else` clause
// çıktı: 5
YerKabul ettiği desenNot
let, fonksiyon parametresi, forYalnızca çürütülemezÇürütülebilir desen E0005 verir
if let, while let, let...elseİkisi deÇürütülemez desen uyarı verir
match kollarıÇürütülebilirSon kol kalan her şeyi kapsamalı

Literal, birden çok desen ve aralıklar

Desen olarak doğrudan literal değer yazabilirsiniz. | işleci desenlerde "veya" anlamına gelir. ..= kapsayıcı, .. ise üst sınırı dışlayan aralık desenidir; Rust Reference'a göre aralık desenleri sayı ve char tipleriyle çalışır ve boş olmamalıdır, örneğin 10..=0 geçersizdir. Tek taraflı aralıklar da tanımlıdır: 1.. 1 ve üzerini, ..0 sıfırdan küçük değerleri kapsar.

rust
fn sinif(n: i32) -> &'static str {
    match n {
        ..0 => "negatif",
        0 => "sıfır",
        1 | 2 => "bir ya da iki",
        3..=9 => "tek basamaklı",
        10..100 => "iki basamaklı",
        _ => "büyük",
    }
}

fn main() {
    for n in [-4, 0, 2, 7, 42, 100] {
        println!("{n}: {}", sinif(n));
    }
    let harf = 'ş';
    let tur = match harf {
        'a'..='z' => "ASCII küçük harf",
        'ç' | 'ğ' | 'ı' | 'ö' | 'ş' | 'ü' => "Türkçe küçük harf",
        _ => "diğer",
    };
    println!("{harf}: {tur}");
}
// çıktı: -4: negatif
// çıktı: 0: sıfır
// çıktı: 2: bir ya da iki
// çıktı: 7: tek basamaklı
// çıktı: 42: iki basamaklı
// çıktı: 100: büyük
// çıktı: ş: Türkçe küçük harf

i32 üzerindeki bu match'te son kolu kaldırırsanız derleyici 100 ve üzerinin kapsanmadığını söyler; aralık desenleri de kapsayıcılık denetimine dahildir.

İsimli değişkenler ve gölgeleme tuzağı

İsimli değişkenler ve gölgeleme tuzağı

match, if let ve while let yeni bir kapsam başlatır. Desende kullanılan bir değişken adı, dışarıda aynı adlı bir değişken olsa bile onunla karşılaştırma yapmaz; yeni bir değişken tanımlar ve dıştakini gölgeler. Dıştaki bir değerle karşılaştırmak için match guard gerekir:

rust
fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("50 geldi"),
        Some(y) => println!("eşleşti, y = {y}"), // yeni y, dıştaki değil
        _ => println!("varsayılan, x = {x:?}"),
    }

    match x {
        Some(50) => println!("50 geldi"),
        Some(n) if n == y => println!("eşleşti, n = {n}"), // guard dıştaki y'yi kullanır
        _ => println!("varsayılan, x = {x:?}"),
    }
    println!("sonda: x = {x:?}, y = {y}");
}
// çıktı: eşleşti, y = 5
// çıktı: varsayılan, x = Some(5)
// çıktı: sonda: x = Some(5), y = 10

Struct, enum ve demet parçalama

Desenler değerleri parçalarına ayırmak (destructuring) için kullanılır. Struct deseninde alan adlarından sonra iki nokta ile değişken adı yazılır; değişken adı alan adıyla aynıysa kısaltma olarak yalnızca alan adı yazılabilir. Bazı alanlar için değişken yerine literal yazarak o alanın belirli bir değere eşit olup olmadığını da test edebilirsiniz. Enum desenleri varyantın tanımlanma biçimini izler: tuple varyantlarda parantez, struct varyantlarda süslü parantez kullanılır. Desenler istenildiği kadar iç içe olabilir.

rust
struct Nokta {
    x: i32,
    y: i32,
}

enum Renk {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

enum Mesaj {
    Tasi { x: i32, y: i32 },
    RenkDegistir(Renk),
}

fn main() {
    let p = Nokta { x: 0, y: 7 };
    let Nokta { x: a, y: b } = p; // farklı adlarla
    let Nokta { x, y } = p;       // kısaltma
    println!("{a} {b} {x} {y}");

    match p {
        Nokta { x, y: 0 } => println!("x ekseninde, x = {x}"),
        Nokta { x: 0, y } => println!("y ekseninde, y = {y}"),
        Nokta { x, y } => println!("eksenlerde değil: ({x}, {y})"),
    }

    let mesajlar = [
        Mesaj::Tasi { x: 1, y: 2 },
        Mesaj::RenkDegistir(Renk::Hsv(0, 160, 255)),
        Mesaj::RenkDegistir(Renk::Rgb(255, 0, 0)),
    ];
    for m in &mesajlar {
        match m {
            Mesaj::Tasi { x, y } => println!("taşı ({x}, {y})"),
            Mesaj::RenkDegistir(Renk::Rgb(r, g, b)) => println!("rgb {r} {g} {b}"),
            Mesaj::RenkDegistir(Renk::Hsv(h, s, v)) => println!("hsv {h} {s} {v}"),
        }
    }

    let ((ayak, inc), Nokta { x: px, y: py }) = ((3, 10), Nokta { x: 3, y: -10 });
    println!("{ayak} {inc} {px} {py}");
}
// çıktı: 0 7 0 7
// çıktı: y ekseninde, y = 7
// çıktı: taşı (1, 2)
// çıktı: hsv 0 160 255
// çıktı: rgb 255 0 0
// çıktı: 3 10 3 -10

Nokta { x: 0, y: 0 } hem birinci hem ikinci kola uyardı, ama match ilk eşleşen kolda durduğu için yalnızca "x ekseninde" yazardı. Nokta'nın alanları i32 olduğu ve i32 Copy olduğu için p'yi iki kez parçalamak sorun yaratmadı; alanlardan biri String olsaydı ilk parçalama onu taşırdı.

Değerleri yok saymak: _, _ad ve ..

Bir değerin tamamını ya da bir kısmını yok saymanın dört yolu vardır. _ her şeyle eşleşir ama hiçbir şeyi bağlamaz; fonksiyon parametresinde, örneğin bir trait'in istediği imzayı karşılarken kullanılmayan parametre için işe yarar. Başka bir desenin içindeki _ yalnızca o parçayı yok sayar. Adı alt çizgiyle başlayan bir değişken kullanılmasa bile uyarı üretmez. .. ise bir struct'ın ya da demetin açıkça yazılmayan geri kalanını atlar.

rust
struct Nokta3 {
    x: i32,
    y: i32,
    z: i32,
}

fn main() {
    let mut ayar = Some(5);
    let yeni_ayar = Some(10);
    match (ayar, yeni_ayar) {
        (Some(_), Some(_)) => println!("var olan ayarın üzerine yazılamaz"),
        _ => ayar = yeni_ayar,
    }
    println!("ayar: {ayar:?}");

    let sayilar = (2, 4, 8, 16, 32);
    let (ilk, _, ucuncu, _, besinci) = sayilar;
    println!("{ilk} {ucuncu} {besinci}");
    let (bas, .., son) = sayilar;
    println!("{bas} {son}");

    let kok = Nokta3 { x: 0, y: 1, z: 2 };
    let Nokta3 { x, .. } = kok;
    println!("x = {x}, y = {}, z = {}", kok.y, kok.z);
}
// çıktı: var olan ayarın üzerine yazılamaz
// çıktı: ayar: Some(5)
// çıktı: 2 8 32
// çıktı: 2 32
// çıktı: x = 0, y = 1, z = 2

.. belirsiz olmamalıdır. Bir demet deseninde iki kez kullanılırsa Rust hangi elemanın hangi değişkene düşeceğini bilemez ve hata verir:

rust
fn main() {
    let sayilar = (2, 4, 8, 16, 32);
    match sayilar {
        (.., ikinci, ..) => println!("{ikinci}"),
    }
}
// error: `..` can only be used once per tuple pattern

_ ile _ad arasındaki sahiplik farkı

Bu ikisi aynı şey değildir. _ad değeri yine de bağlar; bağlanan değer Copy değilse taşınır. _ ise hiç bağlamadığı için hiçbir şeyi taşımaz. The Book bu farkı bir Option<String> ile gösterir:

rust
fn main() {
    let s = Some(String::from("Merhaba!"));
    if let Some(_s) = s {
        println!("metin bulundu");
    }
    println!("{s:?}");
}
// error[E0382]: borrow of partially moved value: `s`

Some(_s) yerine Some(_) yazdığınızda String taşınmaz ve son satır Some("Merhaba!") yazdırır. Değeri kullanmayacaksanız _, kullanıp uyarıyı geçici olarak susturmak istiyorsanız _ad yazın. Taşıma kuralları için Rust ownership yazısına bakabilirsiniz.

Match guard ve öncelik

Match guard, desenin ardından gelen ek bir if koşuludur; kolun seçilmesi için desen eşleşmeli ve koşul da doğru olmalıdır. Koşul desende bağlanan değişkenleri kullanabilir. Bedeli şudur: guard içeren kollarda derleyici kapsayıcılığı koşula bakarak doğrulayamaz, bu yüzden genellikle guard'sız bir yedek kol gerekir. | ile birleştirilmiş desenlerde guard, tek bir alternatife değil desenin tamamına uygulanır:

rust
fn main() {
    let sayi = Some(4);
    match sayi {
        Some(x) if x % 2 == 0 => println!("{x} çift"),
        Some(x) => println!("{x} tek"),
        None => (),
    }

    let x = 4;
    let y = false;
    match x {
        4 | 5 | 6 if y => println!("evet"), // (4 | 5 | 6) if y
        _ => println!("hayır"),
    }
}
// çıktı: 4 çift
// çıktı: hayır

İkinci match "hayır" yazar, çünkü guard yalnızca 6'ya değil 4, 5 ve 6'nın tamamına bağlıdır ve y yanlıştır. Guard yalnızca son alternatife uygulansaydı 4 eşleşir ve "evet" yazılırdı.

@ bağlamaları

@ işleci, bir değeri bir desene karşı test ederken aynı anda bir değişkene bağlamanızı sağlar. Bir aralık deseni değeri kontrol eder ama kola değişken vermez; id @ 3..=7 ise hem aralığı test eder hem de eşleşen değeri id olarak kullanılabilir kılar:

rust
enum Mesaj {
    Selam { id: i32 },
}

fn main() {
    for m in [Mesaj::Selam { id: 5 }, Mesaj::Selam { id: 11 }, Mesaj::Selam { id: 40 }] {
        match m {
            Mesaj::Selam { id: id @ 3..=7 } => println!("aralıkta bir id: {id}"),
            Mesaj::Selam { id: 10..=12 } => println!("diğer aralıkta bir id"),
            Mesaj::Selam { id } => println!("başka bir id: {id}"),
        }
    }
}
// çıktı: aralıkta bir id: 5
// çıktı: diğer aralıkta bir id
// çıktı: başka bir id: 40

Slice desenleri

Rust Reference, diziler ve slice'lar için köşeli parantezli desenler tanımlar. .. burada sıfır veya daha fazla elemanı temsil eder ve kalan @ .. biçiminde bir değişkene bağlanabilir. Slice'ın uzunluğu derleme zamanında bilinmediği için boş, tek elemanlı ve çok elemanlı durumları ayrı kollarda ele almak, uzunluk kontrolü ve indeksleme yazmaktan çok daha güvenlidir:

rust
fn tarif(s: &[&str]) -> String {
    match s {
        [] => String::from("boş"),
        [tek] => format!("tek eleman: {tek}"),
        [ilk, .., "z"] => format!("{ilk} ile başlıyor, z ile bitiyor"),
        [bas, kalan @ ..] => format!("baş = {bas}, kalan = {kalan:?}"),
    }
}

fn main() {
    println!("{}", tarif(&[]));
    println!("{}", tarif(&["a"]));
    println!("{}", tarif(&["a", "b", "z"]));
    println!("{}", tarif(&["a", "b", "c"]));
}
// çıktı: boş
// çıktı: tek eleman: a
// çıktı: a ile başlıyor, z ile bitiyor
// çıktı: baş = a, kalan = ["b", "c"]

Slice'ın kendisi hakkında daha fazla bilgi için Rust slice ve &str yazısına bakabilirsiniz.

Binding mode ve 2024 edition kısıtı

Bir referansı, referans olmayan bir desenle eşleştirdiğinizde Rust referansı kendiliğinden açar ve bağlamaları ref yapar; buna varsayılan binding mode denir ve let [a, b] = &[1, 2]; yazdığınızda a ile b'nin &i32 olmasını sağlar. Edition Guide'a göre Rust 2024'te bu otomatik mod devredeyken bir bağlamaya mut, ref veya ref mut yazmak hatadır. Eski edition'larda mut yazmak modu sessizce "move"a çeviriyor ve tipi değiştiriyordu; bu şaşırtıcı davranış 2024'te kaldırıldı:

rust
fn main() {
    let dizi = &[1u8, 2];
    let [mut a, b] = dizi;
    a += 1;
    println!("{a} {b}");
}
// error: cannot mutably bind by value within an implicitly-borrowing pattern

Çözüm, deseni tamamen açık yazmaktır: dıştaki referansı & deseniyle siz açarsanız varsayılan mod "move" olarak kalır ve mut yazmak serbesttir.

rust
fn main() {
    let dizi = &[1u8, 2];
    let &[mut a, b] = dizi; // referansı desende açıkça açıyoruz
    a += 1;
    println!("{a} {b}");
}
// çıktı: 2 2

Desen biçimleri özet tablosu

Aşağıdaki tablo bu yazıda gördüğümüz desen biçimlerini tek yerde toplar. Hepsi birbiriyle iç içe kullanılabilir; örneğin bir enum deseni içinde aralık, aralığın üzerinde @ bağlaması ve kolun sonunda bir guard aynı anda bulunabilir.

BiçimÖrnekNe yapar
Literal1, "evet", trueDeğere eşitse eşleşir
DeğişkenxHer şeyle eşleşir ve değeri bağlar
Joker_Her şeyle eşleşir, bağlamaz
Veya1 | 2Alternatiflerden biri eşleşirse eşleşir
Aralık3..=9, 'a'..='z', ..0Sayı veya char aralığında eşleşir
StructNokta { x, y: 0 }Alanları parçalar, literal alanları test eder
EnumSome(x), Mesaj::Tasi { x, y }Varyantı ve içindeki veriyi eşleştirir
Demet(a, .., z)Elemanları sırayla parçalar
Slice[ilk, kalan @ ..]Uzunluğa göre eşleşir ve parçalar
Referans&(x, y)Referansı açıp içini eşleştirir
Bağlamaid @ 3..=7Test eder ve değeri bağlar
GuardSome(x) if x > 0Desenden sonra ek koşul (yalnızca match)

Desenleri okurken soldan sağa ve dıştan içe ilerleyin: önce değerin tipi ve varyantı, sonra içteki alanlar, en son da guard koşulu kontrol edilir. Bir kol eşleşmediğinde Rust bir sonraki kola geçer; kollardan hiçbirinin eşleşmeme ihtimali kalmaması gerektiği için son kolda genellikle _ ya da bir değişken adı bulunur.

Sık yapılan hatalar

Sık yapılan hatalar: Desende dış değişkenle karşılaştırma yaptığını sanmak, _ad ile değeri farkında olmadan taşımak, Guard'ın kapsayıcılığı sağladığını sanmak, | ile guard...
  • Desende dış değişkenle karşılaştırma yaptığını sanmak: Some(y) yeni bir y tanımlar; karşılaştırma için guard yazın.
  • _ad ile değeri farkında olmadan taşımak: Değeri kullanmayacaksanız yalnızca _ yazın.
  • Guard'ın kapsayıcılığı sağladığını sanmak: Derleyici guard koşullarını analiz etmez; guard'sız bir yedek kol ekleyin.
  • | ile guard önceliğini karıştırmak: 4 | 5 | 6 if y ifadesinde koşul üç alternatifin hepsine uygulanır.
  • let ile çürütülebilir desen kullanmak: E0005 alırsınız; let...else, if let veya match kullanın.
  • 2024 edition'da örtük referans içinde mut yazmak: Deseni &[mut a, b] gibi açık yazın ya da önce değeri kopyalayın.

Sık Sorulan Sorular

Desenlerde değişken ile sabit nasıl ayırt edilir?

Rust Reference'a göre path desenleri sabitlere, enum varyantlarına ve alansız struct'lara başvurabilir ve bu durumda değerle karşılaştırma yapılır. Kapsamda const SINIR: i32 = 5; varken SINIR => ... kolu yalnızca 5 ile eşleşir; bunu Rust 1.99.1 ile denedik. Böyle bir öğeye karşılık gelmeyen tek bir ad ise her değerle eşleşir ve yeni bir değişken bağlar.

match guard ile let zinciri arasındaki fark nedir?

Guard yalnızca match kollarında kullanılır. 2024 edition'da if ve while koşullarında let ifadelerini && ile zincirleyerek benzer bir etki elde edebilirsiniz; ayrıntı için Rust 2024 edition yazısına bakın.

Aralık desenlerinde hangi tipler kullanılabilir?

Reference'a göre aralık sınırları karakter, bayt, tamsayı veya kayan noktalı sayı literal'i ya da bu tiplerde bir sabite giden yol olabilir; kayan noktalı sınırlar NaN olamaz. Aralığın en az bir değeri kapsaması gerekir.

Bir değeri hem parçalayıp hem bütün olarak kullanabilir miyim?

Evet, @ ile: butun @ [.., son] deseni slice'ın tamamını butun'a, son elemanı da son'a bağlar. Rust Reference'taki rest pattern örneği bunu gösterir.

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 · match ve if let · Enum ve Option · Slice · Struct ve metotlar · Ownership · Rust 2024 edition · Rust nedir?