Tüm Rust rehberleri
TEKNİK REHBER TEMEL KOLEKSİYON 2026

Koleksiyonlar & Iterator'lar
Zero-cost soyutlamalar

Vec, String, HashMap ve iterator zincirleri; lazy adapter'lar ve zero-cost abstraction — C++ STL container ve algorithm'leriyle karşılaştırmalı.

00 C dizilerinden Rust koleksiyonlarına

C'de bellek yönetimi el emeğidir, C++ STL bunu container'lara devreder; Rust ise aynı container'ları ownership ve borrow kurallarıyla derleme zamanında güvenli kılar.

C'de büyüyebilen bir tampon istediğinizde malloc ile başlar, dolunca realloc ile büyütür, işiniz bitince free edersiniz. Boyutu ayrı bir size_t'de taşırsınız, kapasiteyi başka bir değişkende. Bir hata — çift free, sınır taşması, asılı işaretçi (dangling pointer) — derleyiciye görünmez, çalışma zamanında patlar. C++ STL bu defter tutmayı std::vector, std::string, std::unordered_map gibi sınıflara taşır; ama iterator invalidation hâlâ sizin sorumluluğunuzdadır.

Rust'ın standart kütüphanesi aynı container ailesini sunar: Vec<T>, String, HashMap<K, V>, VecDeque, BTreeMap, HashSet. Fark, bellek defterinin değil, kuralların derleyiciye verilmiş olmasıdır. Her değerin tek bir sahibi (owner) vardır; sahip kapsamdan çıkınca Drop otomatik çağrılır — free'yi unutmak mümkün değildir. Ödünç alma (borrow) kuralı, aynı anda ya tek bir mutable referansa ya da çok sayıda immutable referansa izin verir.

Bu kuralın en çarpıcı sonucu iterator invalidation'ın derleme zamanında yakalanmasıdır. C++'ta bir vector'ü dolaşırken ona push_back yapmak realloc'u tetikleyip tüm iterator'ları geçersiz kılabilir — UB. Rust'ta aynı şey bir borrow ihlalidir ve derlenmez.

main.rs
fn main() {
    let mut v = vec![1, 2, 3];

    // C++'ta UB: dolaşırken push_back realloc tetikleyebilir.
    for x in &v {        // v paylaşımlı olarak ödünç alındı
        v.push(*x);     // HATA: v zaten ödünç alınmışken mutable erişim
    }                   // cannot borrow `v` as mutable ...
}
NOT

Rust'ta "container + boyut + kapasite" üçlüsü tek bir tip içinde paketlenir. Vec<T> tam olarak üç makine kelimesidir: heap işaretçisi, uzunluk (len) ve kapasite (capacity). C'deki üç ayrı değişkenin tek, ihlal edilemez bir nesneye dönüşmüş hâli.

Bu bölümde

  • C'nin malloc/realloc/free el emeği yerine Rust koleksiyonları otomatik Drop ile çalışır.
  • STL container'larının karşılığı std koleksiyonlarıdır, ama kurallar derleyiciye devredilmiştir.
  • Ownership tek-sahip; borrow ya bir mutable ya çok immutable referans verir.
  • Iterator invalidation, çalışma zamanı UB'si değil, derleme zamanı borrow hatasıdır.

01 Vec<T>

Vec<T>, std::vector<T>'nin doğrudan karşılığıdır: bitişik (contiguous), büyüyebilen, heap üzerinde tek bir dizi.

Eleman eklemek/çıkarmak için push ve pop, araya girmek için insert(i, x) ve remove(i) vardır. Bunlar STL'deki push_back, pop_back, insert, erase ile birebir eşleşir. Kritik fark erişimdedir.

v[i]Doğrudan indeksleme. Sınır dışıysa panic! — C++'taki at() gibi davranır ama operatörle.
v.get(i)Option<&T> döner: geçerliyse Some(&x), değilse None. Panik yok.
&v[a..b]Slice: sahiplenmeyen, uzunluk taşıyan bir görünüm (&[T]). C++'taki std::span'e benzer.
main.rs
fn main() {
    let mut v: Vec<i32> = Vec::new();
    v.push(10);
    v.push(20);
    v.insert(1, 15);    // [10, 15, 20]
    let last = v.pop();   // Some(20); Vec'i daraltır

    // İndeksleme panik riski taşır; get() güvenlidir.
    let a = v[0];                  // i32, panik olabilir
    let b = v.get(99);             // None — panik yok

    // Slice: kopyasız, sahiplenmeyen görünüm.
    let dilim: &[i32] = &v[0..2];
    println!("{:?} {:?} {:?}", last, b, dilim);
}

Kapasite, STL'deki gibi ayrı yönetilir. Dolu bir Vec'e push yapmak kapasiteyi (genellikle iki katına) büyütür, yeni bir tampon ayırır ve elemanları taşır — amortize O(1). Eleman sayısını biliyorsanız Vec::with_capacity(n) ile tek seferde ayırın; bu reserve'in yapıcıdaki hâlidir.

main.rs
let mut v = Vec::with_capacity(1000);
// len = 0, capacity = 1000: 1000 push'a kadar yeniden tahsis yok.
for i in 0..1000 {
    v.push(i);   // hiçbir realloc tetiklenmez
}
assert_eq!(v.len(), 1000);
assert!(v.capacity() >= 1000);
DİKKAT

v[i] ile v.get(i) arasındaki seçim bir API sözleşmesidir. İndeksin tanım gereği geçerli olduğu yerde v[i] kullanın (bir bug varsa hızlı patlar). Dışarıdan gelen, güvenilmez indekslerde her zaman get tercih edin.

Bu bölümde

  • Vec<T> = bitişik, büyüyebilen heap dizisi; std::vector'ün karşılığı.
  • push/pop/insert/remove STL eşdeğerleriyle aynı semantik.
  • İndeksleme panikler, get Option<&T> döner — güvenli erişim için ikincisi.
  • Kapasite ayrı yönetilir; with_capacity ile yeniden tahsisi önleyin.

02 String ve &str

Rust'ta metin her zaman geçerli UTF-8'dir: String sahip olan büyüyebilen tampon, &str ise ona (veya bir literale) ödünç verilen görünümdür.

İlişki Vec<T> ile &[T] ilişkisinin tıpkısıdır: String, içte Vec<u8>'dir (sahip + büyüyebilir); &str ise bir byte dilimine UTF-8 garantisi eklenmiş görünümdür. C++'ta bu ayrımın karşılığı kabaca std::string (sahip) ile std::string_view (ödünç) arasındadır. Önemli ayrım: &str bir kodlama sözleşmesi taşır, string_view taşımaz.

En çok şaşırtan nokta: s[0] yoktur. C++'ta std::string bir char (byte) dizisidir ve s[0] ilk byte'ı verir. Rust'ta byte ile karakter aynı şey değildir — bir Unicode skaler değeri (char) 1 ila 4 byte tutabilir. "ö" iki byte'tır. Tek bir s[i] indekslemesi byte mı yoksa karakter mi döndüreceği belirsiz olduğundan dilden çıkarılmıştır.

main.rs
fn main() {
    let mut s = String::from("merhaba");
    s.push_str(" dünya");   // büyüyen tampona ekleme
    s.push('!');            // tek char (UTF-8 olarak kodlanır)

    // HATA: String byte ile indekslenemez.
    // let c = s[0];        // derlenmez

    // "ş" iki byte tutar: byte sayısı != karakter sayısı
    let t = "şö";
    assert_eq!(t.len(), 4);              // byte uzunluğu
    assert_eq!(t.chars().count(), 2);   // karakter sayısı
}

Karaktere göre dolaşmak için .chars() (Unicode skalerleri), byte'a göre için .bytes() kullanılır. .len() her zaman byte sayısını verir; bu STL'de std::string::size()'in davranışına denktir ama Rust burada sizi karakter saydığınızı zannetmekten korur.

"şö" → bytes: 0xC5 0x9F 0xC3 0xB6 (4 byte)  →  chars: 'ş' 'ö' (2 char)
main.rs
fn selamla(ad: &str) -> String {
    // &str alıyoruz: String veya literal, ikisi de geçer (zero-copy).
    format!("Merhaba, {ad}!")
}

fn main() {
    let sahip = String::from("Emirhan");
    // &String → &str otomatik (deref coercion)
    println!("{}", selamla(&sahip));
    println!("{}", selamla("literal"));  // &'static str
}
NOT

API kuralı: parametre olarak her zaman &str alın, &String değil. &String alan bir fonksiyon literal kabul edemez; &str alan ise hem String'i hem literali zero-copy alır. C++'taki "const std::string& yerine std::string_view al" tavsiyesinin aynısıdır.

Bu bölümde

  • String sahip/büyüyebilir (içte Vec<u8>), &str ödünç görünüm — string vs string_view.
  • s[0] yoktur çünkü byte ≠ karakter; UTF-8'de char 1–4 byte.
  • Dolaşma: .chars() karakter, .bytes() byte; .len() byte sayar.
  • Fonksiyon parametresi için &str tercih edin: hem String'i hem literali zero-copy alır.

03 HashMap<K, V>

HashMap<K, V>, std::unordered_map'in karşılığıdır: ortalama O(1) ekleme/erişim, sırasız iterasyon — ama ownership ve entry API'siyle.

Temel işlemler tanıdıktır: insert(k, v) ekler (eski değeri varsa Some(old) döndürür), get(&k) ise Option<&V> verir. Sahiplik kritiktir: insert hem anahtarı hem değeri map'e taşır (move). String bir anahtarsa, insert'ten sonra o String artık sizin değildir.

main.rs
use std::collections::HashMap;

fn main() {
    let mut skor: HashMap<String, i32> = HashMap::new();
    skor.insert(String::from("ahmet"), 10);
    skor.insert(String::from("zehra"), 20);

    match skor.get("ahmet") {     // &str ile sorgulanabilir
        Some(p) => println!("puan: {p}"),
        None    => println!("yok"),
    }

    // Sırasız iterasyon: çalıştırmalar arası sıra değişir.
    for (ad, puan) in &skor {
        println!("{ad}: {puan}");
    }
}

C++'ta "anahtar varsa güncelle, yoksa ekle" deyimi operator[] veya find + insert ile yazılır ve genellikle iki kez arama yapar. Rust'ın entry API'si bunu tek aramada, ergonomik biçimde çözer. or_insert(default) giriş yoksa varsayılanı yerleştirir ve her durumda değere mutable referans döndürür.

main.rs
use std::collections::HashMap;

fn main() {
    let metin = "bir iki bir üç iki bir";
    let mut say: HashMap<&str, i32> = HashMap::new();

    for kelime in metin.split_whitespace() {
        // yoksa 0 ekle, sonra &mut i32 üzerinden artır — tek arama
        *say.entry(kelime).or_insert(0) += 1;
    }
    // {"bir": 3, "iki": 2, "üç": 1}
    println!("{say:?}");
}
NOT

Rust'ın varsayılan hash fonksiyonu (SipHash 1-3) süreç başına rastgele tohumlanır. Bu, kötü niyetli girdilerin kasıtlı çakışma üreterek O(n) bucket zincirleriyle map'i kilitlemesini (HashDoS) engeller. Bedeli, unordered_map'in tipik FNV/identity hash'inden biraz daha yavaş olmasıdır; saf hız gerektiğinde FxHashMap gibi alternatif hasher'lara geçilebilir.

Bu bölümde

  • HashMap<K, V> = ortalama O(1), sırasız — unordered_map'in karşılığı.
  • insert anahtarı ve değeri map'e taşır (move); sahiplik el değiştirir.
  • entry(k).or_insert(d) tek aramada "yoksa ekle, mutable ref döndür".
  • Varsayılan SipHash rastgele tohumlu — HashDoS'a dirençli, biraz daha yavaş.

04 Diğer koleksiyonlar

İhtiyaca göre Vec ve HashMap'in ötesinde dört yardımcı: çift uçlu kuyruk, sıralı ağaç tabanlı map ve iki küme tipi.

VecDeque<T>, halka tampon (ring buffer) üzerine kurulu çift uçlu kuyruktur: her iki uçtan da O(1) push_front/push_back ve pop_front/pop_back sağlar. C++'ta std::deque'nin yerini tutar — kuyruk (queue) veya çift uçlu yapı gerektiğinde Vec'in baştan ekleme O(n) maliyetinden kaçınmak için kullanılır.

BTreeMap<K, V> ve BTreeSet<T>, B-ağacı üzerinde anahtarları sıralı tutar; tıpkı std::map/std::set gibi O(log n) işlemler ve sıralı dolaşım sunar. Anahtarları her zaman sıralı gezmek veya aralık sorgusu (range) yapmak istiyorsanız bunları seçin. HashSet<T> ise HashMap üzerine kurulu, sırasız, ortalama O(1) üyelik kümesidir — std::unordered_set karşılığı.

RustC++ STLSıraKarmaşıklıkNe zaman
Vec<T>vectorekleme sırasıamortize O(1) sona eklemevarsayılan liste, sıralı erişim
VecDeque<T>dequeekleme sırasıO(1) iki uçkuyruk / çift uçlu yapı
HashMap<K,V>unordered_mapsırasızort. O(1)hızlı anahtar→değer
BTreeMap<K,V>mapanahtara göre sıralıO(log n)sıralı gezme, range sorgusu
HashSet<T>unordered_setsırasızort. O(1)hızlı üyelik testi
BTreeSet<T>setsıralıO(log n)sıralı, tekil eleman kümesi
main.rs
use std::collections::{VecDeque, BTreeMap, HashSet};

fn main() {
    let mut kuyruk: VecDeque<i32> = VecDeque::new();
    kuyruk.push_back(1);
    kuyruk.push_front(0);          // O(1) baştan ekleme
    assert_eq!(kuyruk.pop_front(), Some(0));

    let mut sirali = BTreeMap::new();
    sirali.insert(3, "c");
    sirali.insert(1, "a");
    // dolaşım her zaman anahtar sırasında: 1, 3
    for (k, v) in &sirali { println!("{k}:{v}"); }

    let benzersiz: HashSet<i32> = [1, 2, 2, 3].into_iter().collect();
    assert_eq!(benzersiz.len(), 3);  // tekrarlar elendi
}

Bu bölümde

  • VecDeque = halka tamponlu çift uçlu kuyruk; iki uçtan O(1), std::deque karşılığı.
  • BTreeMap/BTreeSet = sıralı, O(log n), range sorgusu — std::map/set.
  • HashSet = sırasız, ortalama O(1) üyelik kümesi.
  • Seçim, sıralı erişim ile salt hız arasındaki gerçek ihtiyaca bağlıdır (tabloya bakın).

05 Iterator trait

Rust'ta tüm dolaşım tek bir soyutlamaya dayanır: Iterator trait'i ve onun tek zorunlu metodu next.

Trait'in özü minimaldir: bir ilişkili tip (Item) ve next metodu. Her next çağrısı ya bir sonraki elemanı (Some(item)) ya da bitişi (None) döndürür. C++ STL'deki "begin/end işaretçi çifti + operator++ + operator*" üçlüsünün yerine tek, daha güvenli bir arayüz geçer — sınır kontrolü Option'a kodlanmıştır.

main.rs
// Standart kütüphanedeki tanımın özü:
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // ... map, filter, collect gibi onlarca metod varsayılan olarak
    // sadece next() üstüne tanımlıdır.
}

Bir koleksiyondan iterator elde etmenin üç yolu vardır ve farkları doğrudan ownership'tir. Bunları seçmek C++'taki const_iterator / iterator / move-iterator seçimine benzer ama derleyici zorlar.

iter()&T verir — paylaşımlı, salt-okunur. Koleksiyon dokunulmadan kalır.
iter_mut()&mut T verir — yerinde değiştirilebilir. Elemanlar güncellenir.
into_iter()T verir — koleksiyonu tüketir (move), elemanların sahipliğini devreder.
main.rs
fn main() {
    let mut v = vec![1, 2, 3];

    for x in v.iter()      { // x: &i32 (salt-okunur) }
    for x in v.iter_mut()  { *x *= 2; }  // x: &mut i32, yerinde değiştir
    for x in v.into_iter() { // x: i32 (sahiplenir); v artık kullanılamaz }
}

for döngüsünün kendisi sözdizimsel şekerdir (syntactic sugar). for x in koleksiyon { ... } aslında into_iter() çağırır ve bir loop içinde next()'i None gelene dek döndürür. &v üzerinde dolaşmak v.iter()'a, &mut v ise v.iter_mut()'a çözülür.

main.rs
// Bu döngü...
for x in &v { println!("{x}"); }

// ...derleyici tarafından kabaca şuna açılır:
let mut it = v.iter();
while let Some(x) = it.next() {
    println!("{x}");
}
DİKKAT

into_iter() koleksiyonu tüketir. Çağırdıktan sonra orijinal değişkene erişmek borrow/move hatasıdır. Koleksiyonu sonradan kullanacaksanız iter() veya iter_mut() ile referans üzerinden dolaşın.

Bu bölümde

  • Iterator = type Item + fn next(&mut self) -> Option<Item>; gerisi varsayılan metod.
  • iter()&T, iter_mut()&mut T, into_iter()T (tüketir).
  • for döngüsü into_iter() + next() üstüne kurulu şekerdir.
  • Bitiş, ayrı bir "end" işaretçisiyle değil None ile kodlanır.

06 Lazy adapter'lar

Adapter'lar bir iterator'ı alıp yeni bir iterator döndürür; hepsi tembeldir (lazy) — bir tüketici çağrılana dek tek bir eleman bile işlenmez.

En sık kullanılanlar tanıdık gelecektir: map (dönüştür), filter (ele), enumerate (indeks ekle), zip (iki iterator'ı eşle), take/skip (ilk n'i al/atla), take_while (koşul bozulana dek al), rev (tersine çevir), flat_map (her elemandan iterator üret ve düzleştir). Her biri Vec üretmez; yalnızca bir sonraki next()'in ne yapacağını tarif eder.

main.rs
fn main() {
    // Bu satır HİÇBİR ŞEY hesaplamaz; sadece tarifi kurar.
    let tarif = (1..=10)
        .filter(|x| x % 2 == 0)   // çiftler
        .map(|x| x * x);            // kareleri

    // collect() — tüketici — çağrıldığında zincir CANLANIR:
    let sonuc: Vec<i32> = tarif.collect();
    assert_eq!(sonuc, vec![4, 16, 36, 64, 100]);
}

Tembellik, gereksiz işten kaçınmayı bedavaya getirir. take(3) ile sınırlanan bir zincir, kaynaktan yalnızca üç eleman çeker — sonsuz bir iterator bile güvenle kullanılabilir, çünkü hiçbir ara Vec tahsis edilmez.

main.rs
fn main() {
    let isimler = ["ada", "can", "deniz"];
    let yaslar  = [7, 12, 9];

    // enumerate + zip + take birlikte, hepsi lazy:
    for (i, (ad, yas)) in isimler.iter()
        .zip(yaslar.iter())     // (&ad, &yas) çiftleri
        .enumerate()            // (indeks, çift)
        .take(2)               // yalnızca ilk 2 — gerisi çekilmez
    {
        println!("{i}: {ad} ({yas})");
    }

    // Sonsuz kaynak + take: yalnızca 3 eleman üretilir.
    let ilk_uc: Vec<u64> = (0..).map(|x| x * 10).take(3).collect();
    assert_eq!(ilk_uc, vec![0, 10, 20]);
}
NOT

Bu, C++20'nin std::ranges ve views (örn. views::filter, views::transform) yaklaşımının doğrudan karşılığıdır — view'lar da tembeldir ve | ile zincirlenir. Rust'ta zincirleme nokta (.) ile yapılır; semantik aynıdır: bir tüketici gelene dek hiçbir şey çalışmaz.

Bu bölümde

  • Adapter'lar (map, filter, enumerate, zip, take, skip, rev, flat_map) iterator → iterator.
  • Hepsi lazy: bir tüketici çağrılana dek tek eleman bile işlenmez.
  • Tembellik gereksiz işi eler; sonsuz iterator'lar take ile güvenli kullanılır.
  • C++20 ranges/views'ın birebir muadili; ara Vec tahsisi yok.

07 Tüketiciler (consumers)

Tüketiciler tembel zinciri çalıştıran metodlardır; next()'i sonuna dek tüketip somut bir değer üretirler — <algorithm>'in yerini tutarlar.

En esnek tüketici collect'tir: iterator'ı bir koleksiyona toplar. Hedef tipi ya turbofish ile (.collect::<Vec<_>>()) ya da değişken tip açıklamasıyla belirtilir; Rust geri kalanı çıkarır. Aynı collect hedefe göre Vec, String, HashMap hatta Result üretebilir.

TüketiciDönerC++ <algorithm> muadili
sum() / product()toplam / çarpımaccumulate
fold(init, f)genel katlamaaccumulate (binary op)
count()eleman sayısıdistance
any() / all()boolany_of / all_of
find(p)Option<T>find_if
position(p)Option<usize>find_if + distance
max() / min()Option<T>max_element / min_element
main.rs
fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let toplam: i32 = v.iter().sum();              // 15
    let carpim: i32 = v.iter().product();          // 120
    let cift_var = v.iter().any(|&x| x % 2 == 0); // true
    let ilk_buyuk = v.iter().find(|&&x| x > 3);     // Some(&4)
    let en_buyuk = v.iter().max();                 // Some(&5)

    // fold: genel katlama (init + her elemanı işle)
    let kareler_toplami = v.iter().fold(0, |acc, &x| acc + x * x);
    assert_eq!(kareler_toplami, 55);
}

collect'in gücü hedef tipindedir. Aynı zincir String'e (karakterleri birleştir), HashMap'e (çiftleri topla) ya da Result<Vec<_>, E>'ye (ilk hatada dur) toplanabilir.

main.rs
use std::collections::HashMap;

fn main() {
    // → String
    let s: String = ['a', 'b', 'c'].into_iter().collect();

    // → HashMap (turbofish ile)
    let m = [(1, "bir"), (2, "iki")]
        .into_iter()
        .collect::<HashMap<_, _>>();

    // → Result<Vec, _>: tüm parse'lar başarılıysa Ok(Vec), ilk hatada Err
    let r: Result<Vec<i32>, _> =
        ["1", "2", "3"].iter().map(|s| s.parse::<i32>()).collect();

    assert_eq!(s, "abc");
    assert_eq!(m.len(), 2);
    assert_eq!(r, Ok(vec![1, 2, 3]));
}
NOT

collect::<Result<Vec<_>, E>>() deseni, "hepsi başarılı olmalı, yoksa ilk hata" mantığını tek satıra sığdırır. <algorithm>'de bunun temiz bir karşılığı yoktur; elle döngü + erken çıkış yazardınız.

Bu bölümde

  • Tüketiciler tembel zinciri çalıştırır; <algorithm>'in iterator karşılığı.
  • collect turbofish veya tip açıklamasıyla hedefe göre Vec/String/HashMap üretir.
  • sum, fold, count, any/all, find, position, max/min tek geçişte sonuç verir.
  • collect::<Result<Vec<_>, _>>() ile "hepsi-ya-da-ilk-hata" toplanır.

08 Zero-cost abstraction

Iterator zinciri, soyut görünmesine rağmen elle yazılmış bir for döngüsüyle aynı makine koduna derlenir — soyutlama bedavadır.

"Zero-cost abstraction" Rust'ın (ve C++'ın) çekirdek vaadidir: kullanmadığınız hiçbir şeyin bedelini ödemezsiniz, kullandığınız soyutlama ise elle yazacağınızdan daha pahalı değildir. Iterator zincirleri bunun en saf örneğidir. map/filter/sum birer fonksiyon çağrısı gibi görünür; ama LLVM optimizasyonundan sonra geriye yalın bir döngü kalır.

main.rs
// Üst seviye soyutlama: niyet açık, kod kısa.
fn toplam_iter(v: &[i32]) -> i32 {
    v.iter().filter(|&&x| x > 0).map(|&x| x * 2).sum()
}

// Elle yazılmış eşdeğer.
fn toplam_dongu(v: &[i32]) -> i32 {
    let mut acc = 0;
    for &x in v {
        if x > 0 { acc += x * 2; }
    }
    acc
}
// --release ile: ikisi de bit bit AYNI assembly. Kapanışlar inline
// olur, ara nesneler kaybolur. Performans farkı ölçülemez.

Bunu mümkün kılan üç mekanizma var. Birincisi, adapter'lar somut tipler döndürür (örn. Map<Filter<...>>) — sanal çağrı (vtable) yok, hepsi statik dağıtım (static dispatch). İkincisi, kapanışlar (closures) çağrı yerine inline edilir. Üçüncüsü, bilinen aralıklarda derleyici bounds-check elemesi yapar: iterator garantili biçimde sınır içinde kaldığından çalışma zamanı sınır kontrolleri tamamen kaldırılır.

kaynak iter → filter (inline closure) → map (inline closure) → sum (akümülatör)  ⇒  tek döngü, sıfır ara nesne
NOT

Bounds-check elemesi kavramsal olarak şudur: elle yazdığınız C döngüsünde v[i] erişimi sınır kontrolü içermez (sizin sorumluluğunuzdadır). Rust'ta her indeksleme normalde kontrol içerir, ama iterator zincirinde derleyici indeksin tanım gereği geçerli olduğunu kanıtlayabildiği için kontrolü siler. Sonuç: güvenlik garantisini koruyan ama C döngüsüyle aynı talimatları üreten bir döngü.

Pratik sonuç: idiyomatik, okunaklı iterator zincirlerini gönül rahatlığıyla yazın. "Daha hızlı olur" diye elle döngüye dönmek neredeyse her zaman gereksizdir — ürettikleri kod aynıdır, ama iterator versiyonunda off-by-one ve sınır taşması hataları yapısal olarak imkânsızdır.

Bu bölümde

  • Iterator zinciri --release ile elle döngüyle aynı assembly'ye derlenir.
  • Adapter'lar static dispatch + inline closure: vtable veya ara nesne yok.
  • Bounds-check elemesi sayesinde çalışma zamanı sınır kontrolleri kaybolur.
  • C döngüsüyle performans denkliği + yapısal olarak imkânsız off-by-one hatası.

09 Gerçek örnek: veri işleme pipeline

Öğrendiğimiz her şeyi tek bir akışta birleştirelim: ham metni satırlara böl, parse et, filtrele, dönüştür ve özetle — hepsi tek iterator zinciriyle.

Senaryo: bir sıcaklık log'u var; her satır "sensör,sıcaklık" biçiminde. Geçersiz satırları (yanlış biçim, parse edilemeyen sayı) atlamak, eşiği aşan ölçümleri saymak ve ortalamayı hesaplamak istiyoruz. C++'ta bu std::getline döngüsü + std::stoi + try/catch karışımı olurdu; Rust'ta tek bir tembel zincir.

main.rs
fn main() {
    let log = "\
oda1,21.5
oda2,bozuk
oda1,30.0
oda3,18.2
eksik_satir
oda2,42.7";

    // Satır → parse → geçersizleri ele → sıcaklıkları topla
    let sicakliklar: Vec<f64> = log
        .lines()                                  // satırlara böl
        .filter_map(|satir| {                     // parse + ayıkla
            let (_ad, deger) = satir.split_once(',')?;  // virgül yoksa None
            deger.trim().parse::<f64>().ok()       // sayı değilse None
        })
        .collect();

    let sayi = sicakliklar.len();
    let toplam: f64 = sicakliklar.iter().sum();
    let ortalama = toplam / sayi as f64;
    let sicak = sicakliklar.iter().filter(|&&t| t > 25.0).count();

    println!("geçerli: {sayi}, ortalama: {ortalama:.1}, >25°: {sicak}");
    // geçerli: 4, ortalama: 28.1, >25°: 2
}

Burada filter_map, "dönüştür ve aynı anda None olanları at" işini tek adımda yapar — ? operatörü ve .ok() ile hatalı satırlar sessizce elenir. Şimdi tersini isteyelim: bir satır bile geçersizse tüm işlemi iptal et. İşte collect::<Result<Vec<_>, _>>() deseninin parladığı yer.

main.rs
fn kati_parse(csv: &str) -> Result<Vec<f64>, std::num::ParseFloatError> {
    csv.lines()
        .map(|satir| {
            let deger = satir.split_once(',').map(|(_, v)| v).unwrap_or(satir);
            deger.trim().parse::<f64>()   // Result<f64, _>
        })
        // Vec<Result> DEĞİL: ilk Err'de durur, Result<Vec> döner.
        .collect()
}

fn main() {
    match kati_parse("21.5\n30.0\n18.2") {
        Ok(v)  => println!("tümü geçerli: {v:?}"),
        Err(e) => eprintln!("hata: {e}"),
    }
}
NOT

İki desen iki farklı hata politikasını temsil eder. filter_map(... .ok()) "hatalıyı atla, devam et" (best-effort) der; collect::<Result<Vec<_>, _>>() ise "hepsi geçerli olmalı, ilk hatada dur" (fail-fast) der. İhtiyaca göre birini seçmek, tüm döngü mantığını değil tek bir metodu değiştirmek demektir.

Kapanış olarak: bu rehberde C'nin manuel bellek emeğinden başlayıp Rust'ın güvenli koleksiyonlarına, oradan tembel iterator zincirlerine geçtik. Sonuç, C++ STL kadar ifade gücü yüksek ama derleyicinin ownership ile zorladığı bir model — ve hepsi zero-cost. İdiyomatik Rust yazmanın anlamı budur: kısa, okunaklı zincirler kurun; güvenlik ve performans derleyicinin işi olsun.

Bu bölümde

  • Tek bir tembel zincir: linesfilter_map (parse + ayıkla) → collect → özetle.
  • filter_map + ?/.ok() = "hatalıyı atla, devam et" (best-effort).
  • collect::<Result<Vec<_>, _>>() = "ilk hatada dur" (fail-fast).
  • İfade gücü STL düzeyinde, güvenlik derleyicide, maliyet sıfır — idiyomatik Rust.