Rust'ta iterator, bir dizi elemanı sırayla üreten ve Iterator trait'ini uygulayan herhangi bir değerdir. Trait'in zorunlu tek metodu next'tir: her çağrıda bir sonraki elemanı Some(eleman) olarak verir, eleman kalmayınca None döndürür. map, filter, zip gibi onlarca metot bu tek metodun üzerine varsayılan uygulamalar olarak gelir. En önemli özellik tembelliktir: map ya da filter çağırmak tek başına hiçbir iş yapmaz, sonucu collect, sum veya bir for döngüsü gibi tüketen bir adım isteyene kadar zincir bekler.
Bu yazı Iterator trait'inin yapısını, iter, iter_mut ve into_iter farkını, adaptörleri ve tüketicileri, collect ile farklı koleksiyonlar üretmeyi, kendi iterator'ınızı yazmayı ve sonsuz iterator'ları anlatıyor. Iterator metotlarının neredeyse hepsi closure aldığı için, closure'ların FnMut ve benzeri trait'lerini bilmek işinizi kolaylaştırır; bu konuyu closure yazısında işledik. Koleksiyon tarafında Vec ve HashMap yazılarına da bakabilirsiniz.
Iterator trait'i ve next metodu

Standart kütüphanedeki tanımın çekirdeği iki satırdır: bir ilişkili tip type Item ve fn next(&mut self) -> Option<Self::Item>. Item, iterator'ın ürettiği elemanların tipidir. next metodu &mut self aldığı için iterator'ı elle ilerletirken değişkeni mut tanımlamanız gerekir; her çağrı iterator'ın iç konumunu değiştirir. for döngüsünde bunu yapmanıza gerek kalmaz, çünkü döngü iterator'ın sahipliğini alır ve arka planda değiştirilebilir hâle getirir.
fn main() {
let v = vec![10, 20, 30];
let mut it = v.iter();
println!("{:?}", it.next());
println!("{:?}", it.next());
println!("{:?}", it.next());
println!("{:?}", it.next());
let mut w = vec![1, 2, 3];
for x in w.iter_mut() {
*x *= 10;
}
println!("{w:?}");
let isimler = vec![String::from("Ali"), String::from("Ece")];
for ad in isimler.into_iter() {
println!("sahiplendim: {ad}");
}
}
// çıktı: Some(10)
// çıktı: Some(20)
// çıktı: Some(30)
// çıktı: None
// çıktı: [10, 20, 30]
// çıktı: sahiplendim: Ali
// çıktı: sahiplendim: EceBir koleksiyondan iterator üretmenin üç yaygın yolu vardır ve her biri farklı tipte eleman verir. iter() elemanlara paylaşılan referans (&T) üretir; koleksiyon yerinde kalır. iter_mut() değiştirilebilir referans (&mut T) üretir; elemanları yerinde güncellemenizi sağlar, bu yüzden yukarıda *x *= 10 yazdık. into_iter() ise elemanların kendisini (T) verir ve koleksiyonu tüketir; döngüden sonra isimler artık kullanılamaz. for x in &v yazımı v.iter() ile, for x in &mut v yazımı v.iter_mut() ile aynıdır. Her koleksiyon üçünü birden sunmaz: std belgesi, anahtarları değişirse tutarlılığı bozulacağı için HashSet türünün yalnızca iter() verdiğini örnek gösterir.
| Yöntem | Eleman tipi | Koleksiyona etkisi | for kısaltması |
|---|---|---|---|
| iter() | &T | Ödünç alınır, sonra kullanılabilir | for x in &v |
| iter_mut() | &mut T | Değiştirilebilir ödünç, elemanlar güncellenebilir | for x in &mut v |
| into_iter() | T | Sahiplik alınır, koleksiyon tüketilir | for x in v |
for döngüsü aslında IntoIterator trait'i üzerine kurulu bir sözdizimi kolaylığıdır. Derleyici for x in deger yazımını önce IntoIterator::into_iter(deger) çağrısına, sonra None gelene kadar next çağıran bir loop bloğuna çevirir. Standart kütüphane, her Iterator için kendisini döndüren bir IntoIterator uygulaması da içerir. Bu sayede kendi yazdığınız iterator'ı doğrudan for ile kullanabilirsiniz; kendi koleksiyon tipiniz için IntoIterator uygularsanız o da döngüye girebilir hâle gelir.
Tembellik: zincir ne zaman çalışır?
Iterator adaptörleri yeni bir iterator döndürür ama elemanlara dokunmaz. Aşağıdaki kod derlenir, fakat map içindeki closure bir kez bile çalışmaz; derleyici de bunu bir uyarıyla hatırlatır. Uyarının notu konuyu tek cümlede özetliyor: iterator'lar tembeldir ve tüketilmedikçe bir şey yapmaz. Yalnızca yan etki için bir şey yapmak istiyorsanız map yerine for döngüsü ya da for_each kullanın.
fn main() {
let v = vec![1, 2, 3];
v.iter().map(|x| x + 1);
}
// warning: unused `Map` that must be used
// = note: iterators are lazy and do nothing unless consumedTembelliğin ikinci ve daha ilginç sonucu, zincirin eleman eleman ilerlemesidir. Önce bütün elemanlar filtrelenip sonra hepsi dönüştürülmez; her eleman zincirin sonuna kadar gider, sonra sıradaki çekilir. Aşağıdaki örnekte take(2) iki eleman topladığı anda iş biter: 5 ve 6 için filter hiç çağrılmaz. Bu davranış hem gereksiz işi önler hem de sonsuz iterator'larla çalışmayı mümkün kılar.
fn main() {
let sonuc: Vec<i32> = (1..=6)
.filter(|x| {
println!("filter: {x}");
x % 2 == 0
})
.map(|x| {
println!("map: {x}");
x * 10
})
.take(2)
.collect();
println!("{sonuc:?}");
}
// çıktı: filter: 1
// çıktı: filter: 2
// çıktı: map: 2
// çıktı: filter: 3
// çıktı: filter: 4
// çıktı: map: 4
// çıktı: [20, 40]Sık kullanılan adaptörler

Adaptör, bir iterator alıp başka bir iterator döndüren metotlara verilen addır. Std belgeleri bunların çoğunun dönüş tipini ayrı bir struct olarak tanımlar (Map, Filter, Zip gibi); bu struct'ları doğrudan yazmanız nadiren gerekir, metodu çağırmanız yeterlidir. Aşağıdaki örnek gündelik kodda en çok karşılaşacağınız adaptörleri tek dosyada topluyor.
fn main() {
let sehirler = ["Ankara", "İzmir", "Bursa", "Adana"];
for (i, s) in sehirler.iter().enumerate().skip(1) {
println!("{i}: {s}");
}
let plakalar = [6, 35, 16, 1];
let eslesme: Vec<(&str, i32)> = sehirler.iter().copied().zip(plakalar).collect();
println!("{eslesme:?}");
let a_ile: Vec<&&str> = sehirler.iter().filter(|s| s.starts_with('A')).collect();
println!("{a_ile:?}");
let ters: Vec<i32> = (1..=10).rev().step_by(3).collect();
println!("{ters:?}");
let birlesik: Vec<i32> = [1, 2].into_iter().chain([3, 4]).collect();
println!("{birlesik:?}");
let harfler: Vec<char> = ["ab", "çd"].iter().flat_map(|s| s.chars()).collect();
println!("{harfler:?}");
let pozitif: Vec<u32> = ["5", "x", "12"].iter().filter_map(|s| s.parse().ok()).collect();
println!("{pozitif:?}");
}
// çıktı: 1: İzmir
// çıktı: 2: Bursa
// çıktı: 3: Adana
// çıktı: [("Ankara", 6), ("İzmir", 35), ("Bursa", 16), ("Adana", 1)]
// çıktı: ["Ankara", "Adana"]
// çıktı: [10, 7, 4, 1]
// çıktı: [1, 2, 3, 4]
// çıktı: ['a', 'b', 'ç', 'd']
// çıktı: [5, 12]- enumerate: Her elemanı sıra numarasıyla birlikte
(indeks, eleman)çifti olarak verir. Örnekteskip(1)numaralandırmadan sonra geldiği için indeksler 1'den başlıyor; sırayı değiştirirseniz sonuç da değişir. - zip: İki iterator'ı eş zamanlı yürütür ve çiftler üretir. Kısa olan bittiğinde durur. İkinci argüman
IntoIteratorolabildiği için diziyi doğrudan verebildik. - copied ve cloned: Referans üreten bir iterator'ı değer üreten hâle getirir.
copiedCopytipler,clonedClonetipler içindir. - filter: Closure'a elemanın referansını verir.
iter()zaten&Türettiği için closure parametresi&&Tolur; örnekteVec<&&str>tipinin sebebi budur. - rev ve step_by:
reviki uçtan ilerleyebilen (DoubleEndedIterator) iterator'ları ters çevirir,step_bybelirli aralıkla eleman atlar. - chain: İki iterator'ı uç uca ekler.
- flat_map ve flatten: Her elemandan bir iterator üretip hepsini tek akışa düzleştirir. Örnekte her kelimenin karakterleri tek bir listeye döküldü;
chars()Unicode karakter verdiği içinçdoğru ayrıldı. - filter_map:
Optiondöndüren bir closure alır,Noneolanları atar,Someiçindekileri geçirir. Ayrıştırma gibi başarısız olabilen dönüşümler için birebirdir.
Tüketiciler ve collect
Kitap, next çağırarak iterator'ı bitiren metotlara tüketen adaptörler der. sum, count, max, fold ve collect iterator'ın sahipliğini alır; çağrıdan sonra aynı iterator'ı tekrar kullanamazsınız. any, all, find ve position ise &mut self alır ve sonuç belli olduğu anda durur: any ilk true değerinde, all ilk false değerinde döner. Bu yüzden bu metotlar iterator'ı kısmen tüketilmiş hâlde bırakabilir.
use std::collections::HashMap;
fn main() {
let notlar = [72, 85, 49, 90, 61];
let toplam: i32 = notlar.iter().sum();
let adet = notlar.iter().count();
let en_yuksek = notlar.iter().max();
let gecen = notlar.iter().filter(|&&n| n >= 50).count();
let carpim = (1..=5).fold(1, |acc, x| acc * x);
println!("toplam={toplam} adet={adet} max={en_yuksek:?} gecen={gecen} 5!={carpim}");
println!("{}", notlar.iter().any(|&n| n > 88));
println!("{}", notlar.iter().all(|&n| n > 40));
println!("{:?}", notlar.iter().find(|&&n| n < 50));
println!("{:?}", notlar.iter().position(|&n| n == 90));
let (gecti, kaldi): (Vec<i32>, Vec<i32>) = notlar.iter().partition(|&&n| n >= 60);
println!("{gecti:?} {kaldi:?}");
let kelime: String = ['r', 'u', 's', 't'].iter().collect();
println!("{kelime}");
let stok: HashMap<&str, u32> = [("elma", 3), ("armut", 5)].into_iter().collect();
println!("{:?}", stok.get("armut"));
let iyi: Result<Vec<i32>, _> = ["1", "2", "3"].iter().map(|s| s.parse::<i32>()).collect();
let kotu: Result<Vec<i32>, _> = ["1", "iki", "3"].iter().map(|s| s.parse::<i32>()).collect();
println!("{iyi:?}");
println!("{kotu:?}");
}
// çıktı: toplam=357 adet=5 max=Some(90) gecen=4 5!=120
// çıktı: true
// çıktı: true
// çıktı: Some(49)
// çıktı: Some(3)
// çıktı: [72, 85, 90, 61] [49]
// çıktı: rust
// çıktı: Some(5)
// çıktı: Ok([1, 2, 3])
// çıktı: Err(ParseIntError { kind: InvalidDigit })fold en genel tüketicidir: bir başlangıç değeri ve iki parametreli bir closure alır, closure her adımda biriken değeri ve sıradaki elemanı alıp yeni biriken değeri döndürür. Örnekte 1'den başlayan çarpım 1, 2, 6, 24 ve 120 değerlerinden geçerek 5 faktöriyeli verdi. sum, count ve max gibi metotları kavramsal olarak birer hazır fold gibi düşünebilirsiniz. Başlangıç değeri vermek istemiyorsanız reduce ilk elemanı başlangıç olarak kullanır ve boş iterator için None döndürür. max ve min de boş iterator için None verir; örnekteki Some(90) çıktısının Option içinde gelmesinin sebebi budur. Eleman tipinin Ord uygulaması gerektiği için f64 gibi kayan noktalı tiplerde doğrudan max çağıramazsınız; bu durumda max_by ile karşılaştırmayı kendiniz verirsiniz.
Örnekteki closure parametrelerinde |&n| ve |&&n| desenleri kafa karıştırabilir. any ve position elemanın kendisini alır; iter() elemanı &i32 olarak verdiği için tek & ile açarız. filter, find ve partition ise elemana referans verir, yani &&i32; bu yüzden iki & gerekir. Deseni yazmak yerine gövdede **n de kullanabilirsiniz. Desenlerin ayrıntısı pattern sözdizimi yazısında var.
collect metodu iterator'ın nereye toplanacağını bilmez; hedefi istediğiniz tip belirler. Bu yüzden ya değişkene tip yazarsınız ya da turbofish denen collect::<Vec<_>>() sözdizimini kullanırsınız. Hedef olarak Vec, VecDeque, HashMap, String ve FromIterator uygulayan her tip kullanılabilir. En kullanışlı biçimlerinden biri Result toplamaktır: Result<T, E> elemanlarını Result<Vec<T>, E> olarak toplarsanız, hepsi başarılıysa Ok içinde liste, biri bile başarısızsa ilk hata döner. Hata yönetimiyle birleşik kullanımını Result yazısında bulabilirsiniz.
Kendi iterator'ınızı yazmak
Kendi iterator'ınız için iki adım yeterlidir: durumu tutacak bir struct tanımlarsınız ve bu struct için Iterator uygularsınız. Item tipini belirleyip next metodunu yazdığınız anda zip, skip, map, filter, sum ve diğer bütün metotlar bedavaya gelir, çünkü hepsi next üzerine kurulu varsayılan uygulamalardır. Std belgesi, bir iterator nth veya fold gibi metotları next çağırmadan daha verimli hesaplayabiliyorsa bunları da yeniden yazabileceğini belirtir.
struct Geri {
kalan: u32,
}
impl Iterator for Geri {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.kalan == 0 {
None
} else {
self.kalan -= 1;
Some(self.kalan + 1)
}
}
}
fn main() {
let g = Geri { kalan: 3 };
for x in g {
println!("{x}");
}
let toplam: u32 = Geri { kalan: 5 }
.zip(Geri { kalan: 5 }.skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
println!("toplam = {toplam}");
}
// çıktı: 3
// çıktı: 2
// çıktı: 1
// çıktı: toplam = 18Zincirli kısmı adım adım çözelim. Birinci iterator 5, 4, 3, 2, 1 üretir; skip(1) uygulanan ikincisi 4, 3, 2, 1. zip bunları (5, 4), (4, 3), (3, 2), (2, 1) çiftlerine dönüştürür, kısa olan bittiği için beşinci çift oluşmaz. Çarpımlar 20, 12, 6, 2 olur; 3'e bölünebilenler 12 ve 6'dır ve toplamları 18'dir. İlişkili tiplerin generic parametrelerden farkını merak ediyorsanız operatör aşırı yükleme yazısındaki ilişkili tip bölümüne bakabilirsiniz.
Sonsuz iterator'lar ve iterator üreten fonksiyonlar
Iterator'ların sonlu olması gerekmez. 1.. gibi açık uçlu bir aralık sonsuza kadar sayı üretir; take ya da take_while ile sınırlarsınız. std::iter modülü hazır üreticiler de sunar: repeat aynı elemanı sonsuza kadar tekrarlar, from_fn verdiğiniz closure'ı next gibi kullanır, successors her elemanı bir öncekinden hesaplar ve closure None döndürünce durur. Aşağıda checked_mul taşma olduğunda None döneceği için ikinin kuvvetleri dizisi güvenli biçimde sona erebilir.
fn main() {
let kareler: Vec<u64> = (1..).map(|x| x * x).take_while(|&k| k < 50).collect();
println!("{kareler:?}");
let ikinin_kuvvetleri: Vec<u32> = std::iter::successors(Some(1u32), |&x| x.checked_mul(2))
.take(6)
.collect();
println!("{ikinin_kuvvetleri:?}");
let mut n = 0;
let sayac = std::iter::from_fn(|| {
n += 1;
if n <= 3 { Some(n) } else { None }
});
println!("{:?}", sayac.collect::<Vec<_>>());
let tire: String = std::iter::repeat('-').take(5).collect();
println!("{tire}");
}
// çıktı: [1, 4, 9, 16, 25, 36, 49]
// çıktı: [1, 2, 4, 8, 16, 32]
// çıktı: [1, 2, 3]
// çıktı: -----Sık yapılan hatalar

İlk klasik hata collect çağrısında hedef tipi vermemektir. Derleyici hangi koleksiyonu kurması gerektiğini bilemez ve tip bilgisi ister. Çözüm değişkene tip yazmak ya da turbofish kullanmaktır; Vec<_> biçiminde eleman tipini derleyiciye bırakabilirsiniz.
fn main() {
let a = [1, 2, 3];
let iki_kat = a.iter().map(|x| x * 2).collect();
}
// error[E0283]: type annotations needed
// Düzeltme: hedef tipi söyleyin
// let iki_kat: Vec<i32> = a.iter().map(|x| x * 2).collect();
// let iki_kat = a.iter().map(|x| x * 2).collect::<Vec<_>>();İkinci hata, tüketilmiş bir iterator'ı yeniden kullanmaya çalışmaktır. sum iterator'ın sahipliğini aldığı için ardından gelen count taşınmış bir değere erişmeye çalışır. Aynı koleksiyon üzerinde iki ayrı hesap yapacaksanız her biri için yeniden v.iter() çağırın; bu ucuz bir işlemdir. Taşınmış değer hatalarının genel mantığını E0382 yazısında ele alıyoruz.
fn main() {
let v = vec![1, 2, 3];
let it = v.iter();
let toplam: i32 = it.sum();
let adet = it.count();
}
// error[E0382]: use of moved value: `it`- Yan etki için map kullanmak: Tüketilmezse hiçbir şey olmaz. Yan etki için
forveyafor_eachyazın. - Gereksiz collect: Ara adımda
collectedip tekrariter()çağırmak fazladan bellek ayırır. Zinciri tek parça tutun, yalnızca sonucu toplayın. - Adaptör sırasını önemsiz sanmak:
enumerate().skip(1)ileskip(1).enumerate()farklı indeksler üretir.filteröncesi ve sonrasıtakeda farklı sonuç verir. - into_iter ile istemeden tüketmek: Koleksiyonu döngüden sonra kullanacaksanız
&vveyaiter()kullanın.
Performans konusunda kitap, aynı arama işinin for döngüsüyle ve iterator zinciriyle yazılmış iki sürümünü karşılaştırır ve ikisinin benzer hızda çalıştığını gösterir. Buradan çıkan sonuç, iterator'ların yüksek seviyeli görünse de çalışma zamanı maliyeti eklemeyen soyutlamalar olduğudur. Kendi kodunuzda kesin bir sonuç istiyorsanız kendi ölçümünüzü yapın.
Sık Sorulan Sorular
iter() ile into_iter() arasında hangisini seçmeliyim?
Koleksiyonu daha sonra kullanmaya devam edecekseniz iter(), elemanları başka bir yapıya taşıyacak ve koleksiyona artık ihtiyacınız olmayacaksa into_iter() seçin. Elemanları yerinde değiştirmek için iter_mut() kullanın.
Neden sum çağrısında tip yazmam gerekiyor?
sum generic bir dönüş tipine sahiptir ve Sum trait'ini uygulayan farklı tipler üretebilir. Derleyici bağlamdan çıkaramıyorsa let toplam: i32 = ... ya da sum::<i32>() yazarak hedefi söylemeniz gerekir.
Bir iterator'ı iki kez dolaşabilir miyim?
Tüketilen iterator geri sarılmaz. Kaynağı yeniden iterator'a çevirin ya da iterator Clone uyguluyorsa bir kopyasını alın. Yalnızca bir kısmını kullanıp kalanına devam etmek için by_ref metodu iterator'ı ödünç alarak adaptör uygulamanızı sağlar.
Paralel iterator standart kütüphanede var mı?
Standart kütüphanedeki Iterator sıralı çalışır. Paralel işleme için thread'lerle kendiniz bölebilir ya da üçüncü taraf kütüphanelere bakabilirsiniz; bunlar bu yazının kapsamı dışında, belgeleri için docs.rs adresini kullanabilirsiniz.
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 · Closure ve Fn trait'leri · Vec kullanımı · HashMap kullanımı · Result ve hata yönetimi · Rust nedir?