Rust'ta generic (jenerik) tip, bir fonksiyonu, struct'ı, enum'u ya da metodu belirli bir veri tipine bağlamadan yazmanızı sağlayan tip parametresidir. fn en_buyuk<T: PartialOrd>(liste: &[T]) -> &T gibi bir imzada T yer tutucudur; fonksiyonu i32 dilimiyle çağırdığınızda T yerine i32, char dilimiyle çağırdığınızda char geçer. Derleyici her somut tip için ayrı bir kopya üretir (monomorphization), bu yüzden generic kod çalışma anında elle yazılmış koddan yavaş değildir.
Bu yazıda generic fonksiyonları, birden fazla tip parametresi taşıyan struct ve enum'ları, impl<T> bloklarını, yalnızca belirli bir tipe özel metotları, const generic parametreleri, turbofish sözdizimini ve en sık karşılaşılan derleme hatalarını çalışan örneklerle göreceksiniz. Tip parametresine "şu davranışı desteklemeli" kısıtını koyan trait bound konusu burada yalnızca gerektiği kadar geçiyor; ayrıntısı trait tanımlama ve impl yazısında. Serinin tamamı için Rust öğrenme rehberi sayfasına bakabilirsiniz.
Neden generic? Tekrarlanan kodu tek tanıma indirmek

The Rust Book bu konuyu çok tanıdık bir sorunla açar: bir dilimdeki en büyük i32 değerini bulan fonksiyon ile en büyük char değerini bulan fonksiyonun gövdesi harfi harfine aynıdır, yalnızca imzadaki tipler farklıdır. Aynı mantığı iki kez yazmak hem bakım yükü getirir hem de bir düzeltmenin bir kopyada unutulmasına yol açar. Generic, bu iki fonksiyonu tek bir tanıma indirir. Tip parametresini fonksiyon adı ile parametre listesi arasına, açılı parantez içinde bildirirsiniz; ardından parametrelerde ve dönüş tipinde bu adı kullanırsınız.
Ad seçiminde serbestsiniz, ancak topluluk geleneği kısa ve UpperCamelCase adlardır; "type" kelimesinin kısaltması olan T en yaygın tercihtir. İkinci ve üçüncü parametre için genellikle U, hata tipi için E, anahtar ve değer için K ve V görürsünüz. İlk denemede çoğu kişi aşağıdaki gibi bir kod yazar ve derleyiciden hemen bir hata alır:
fn en_buyuk<T>(liste: &[T]) -> &T {
let mut en_buyuk = &liste[0];
for oge in liste {
if oge > en_buyuk {
en_buyuk = oge;
}
}
en_buyuk
}
fn main() {
let sayilar = vec![34, 50, 25, 100, 65];
println!("{}", en_buyuk(&sayilar));
}
// error[E0369]: binary operation `>` cannot be applied to type `&T`Hata mesajı çok öğreticidir: derleyici T hakkında hiçbir şey bilmediği için, T yerine geçebilecek her tipin > ile karşılaştırılabileceğini varsayamaz. Hata, fonksiyon gövdesinin T için olası tüm tiplerle çalışmayacağını söyler; yani denetim, fonksiyonu hangi tiple çağırdığınıza bakılmadan gövdenin kendisi üzerinde yapılır. Çözüm, T için sıralanabilir olma koşulunu yazmaktır. Derleyicinin help satırında önerdiği gibi std::cmp::PartialOrd trait'ini bound olarak ekleriz. Standart kütüphane bu trait'i hem i32 hem char için uyguladığı için aynı fonksiyon iki tiple de çalışır:
fn en_buyuk<T: PartialOrd>(liste: &[T]) -> &T {
let mut en_buyuk = &liste[0];
for oge in liste {
if oge > en_buyuk {
en_buyuk = oge;
}
}
en_buyuk
}
fn main() {
let sayilar = vec![34, 50, 25, 100, 65];
println!("En büyük sayı: {}", en_buyuk(&sayilar));
let harfler = vec!['ç', 'a', 'z', 'ş'];
println!("En büyük harf: {}", en_buyuk(&harfler));
let kelimeler = ["elma", "armut", "üzüm"];
println!("En büyük kelime: {}", en_buyuk(&kelimeler));
}
// çıktı: En büyük sayı: 100
// çıktı: En büyük harf: ş
// çıktı: En büyük kelime: üzümÇıktıdaki harf ve kelime sonuçları alfabetik Türkçe sıralamayı değil, Unicode kod noktası sırasını yansıtır: char karşılaştırması kod noktası değerine, &str karşılaştırması ise bayt bayt UTF-8 içeriğine bakar. "ş" (U+015F) "z" harfinden büyük çıkar. Türkçe alfabeye göre sıralama gerekiyorsa kendi karşılaştırma fonksiyonunuzu yazmanız gerekir. Fonksiyonun boş dilimle çağrılması da ayrı bir tuzaktır: &liste[0] boş dilimde panic üretir. Gerçek kodda dönüş tipini Option<&T> yapmak daha güvenlidir; enum ve Option yazısı bu deseni anlatır.
Generic struct: tek ve çok tip parametresi
Struct tanımında tip parametresini struct adından hemen sonra bildirirsiniz. struct Nokta<T> { x: T, y: T } tanımı, x ve y alanlarının aynı tipte olmasını şart koşar; tip ne olursa olsun ikisi de o tiptir. Bu yüzden bir alana tam sayı, diğerine ondalıklı sayı vermek derleme hatasıdır:
struct Nokta<T> {
x: T,
y: T,
}
fn main() {
let olmaz = Nokta { x: 5, y: 4.0 };
}
// error[E0308]: mismatched typesDerleyici x: 5 değerini görünce bu örnek için T tipinin bir tam sayı tipi olduğuna karar verir; ardından gelen 4.0 bu kararla çelişir. İki alanın farklı tipte olabilmesini istiyorsanız iki ayrı tip parametresi tanımlayın: struct Nokta<T, U>. İstediğiniz kadar parametre ekleyebilirsiniz, ancak Book'un uyarısı yerindedir: çok sayıda tip parametresine ihtiyaç duyuyorsanız bu, kodun daha küçük parçalara bölünmesi gerektiğine işaret olabilir.
impl bloklarında generic ve tipe özel metotlar

Generic bir struct için metot yazarken impl anahtar kelimesinden hemen sonra da tip parametresini bildirmeniz gerekir: impl<T> Nokta<T>. Buradaki ilk <T> "bu blokta T adında bir tip parametresi var" der, ikincisi ise metotların hangi tip üzerine yazıldığını belirtir. impl sonrasında bildirim yapılmazsa Rust, Nokta<T> içindeki T harfini T adında somut bir tip olarak arar. Bu blokta yazılan her metot, T yerine hangi tip gelirse gelsin her Nokta örneğinde kullanılabilir.
Bazen bir metot yalnızca belirli bir tip için anlamlıdır. Örneğin orijine uzaklığı hesaplamak karekök ve üs alma gerektirir; bu işlemler yalnızca ondalıklı tiplerde vardır. Bu durumda impl sonrasına hiçbir tip parametresi yazmadan somut tipi kullanırsınız: impl Nokta<f64>. Bu blokta tanımlanan metot yalnızca Nokta<f64> için vardır; Nokta<i32> üzerinde çağırmaya çalışırsanız metot bulunamaz. Metotların kendi tip parametreleri de olabilir; aşağıdaki karistir metodu struct'ın parametrelerinden (X1, Y1) bağımsız olarak X2 ve Y2 parametrelerini kendisi bildirir:
#[derive(Debug)]
struct Nokta<X1, Y1> {
x: X1,
y: Y1,
}
impl<X1, Y1> Nokta<X1, Y1> {
fn x(&self) -> &X1 {
&self.x
}
// X2 ve Y2 yalnızca bu metoda ait
fn karistir<X2, Y2>(self, diger: Nokta<X2, Y2>) -> Nokta<X1, Y2> {
Nokta { x: self.x, y: diger.y }
}
}
// Yalnızca iki alanı da f64 olan noktalar için
impl Nokta<f64, f64> {
fn orijine_uzaklik(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let p1 = Nokta { x: 5, y: 10.4 };
let p2 = Nokta { x: "Merhaba", y: 'ç' };
println!("p1.x = {}", p1.x());
let p3 = p1.karistir(p2);
println!("{:?}", p3);
let p4 = Nokta { x: 3.0, y: 4.0 };
println!("uzaklık = {}", p4.orijine_uzaklik());
}
// çıktı: p1.x = 5
// çıktı: Nokta { x: 5, y: 'ç' }
// çıktı: uzaklık = 5Örnekte p3 değişkeninin tipi Nokta<i32, char> olur: x alanı p1 değişkeninden, y alanı p2 değişkeninden gelir. karistir metodu self değerini sahiplenerek aldığı için çağrıdan sonra p1 ve p2 artık kullanılamaz; bu, generic'le değil sahiplikle ilgili bir kuraldır ve ownership yazısında anlatılır. #[derive(Debug)] satırı da derleyicinin {:?} ile yazdırma kodunu üretmesini sağlar; alan tipleri Debug uygulamadığında bu türetme o tip parametreleri için çalışmaz. Ayrıntı derive yazısında.
Generic enum: Option ve Result zaten generic
Standart kütüphanenin en çok kullanılan iki tipi generic enum'dur. Option<T> ya bir T değeri taşıyan Some(T) ya da hiçbir değer taşımayan None varyantıdır. Result<T, E> iki tip parametresi taşır: başarıda Ok(T), hatada Err(E). Dosya açma örneğinde T yerine std::fs::File, E yerine std::io::Error geçer. Kendi enum'unuzu da aynı sözdizimiyle yazarsınız: varyant verilerinde tip parametresini kullanmanız yeterlidir. Book'un pratik önerisi şudur: yalnızca tuttukları değerin tipi farklı olan birden çok struct veya enum tanımı görüyorsanız, generic tip kullanarak tekrarı kaldırabilirsiniz. Hata yönetimi tarafı için Result yazısına bakın.
Const generic: sabit değer parametresi
Tip parametrelerine ek olarak Rust, öğeleri sabit bir değer üzerinden de parametreleştirebilir. En yaygın kullanım dizi uzunluğudur: [i32; 3] ile [i32; 5] farklı tiplerdir ve her uzunluk için ayrı fonksiyon yazmak yerine const N: usize parametresi tanımlarsınız. Reference'a göre const parametresinin tipi yalnızca u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, char veya bool olabilir. Parametreler sırasında önce lifetime'lar, ardından tip ve const parametreleri karışık sırada gelir.
struct Tampon<const N: usize> {
veri: [u8; N],
}
impl<const N: usize> Tampon<N> {
fn yeni() -> Self {
Tampon { veri: [0; N] }
}
fn kapasite(&self) -> usize {
N
}
}
fn toplam<const N: usize>(dizi: [i32; N]) -> i32 {
let mut t = 0;
for x in dizi {
t += x;
}
t
}
fn main() {
println!("{}", toplam([1, 2, 3]));
println!("{}", toplam([10, 20, 30, 40, 50]));
let t: Tampon<16> = Tampon::yeni();
println!("kapasite = {}, ilk bayt = {}", t.kapasite(), t.veri[0]);
}
// çıktı: 6
// çıktı: 150
// çıktı: kapasite = 16, ilk bayt = 0Const parametrelerinin bir sınırı vardır: bir tip veya dizi tekrar ifadesi içinde yalnızca tek başına kullanılabilirler. [u8; N] geçerlidir, ancak [u8; {N + 1}] gibi aritmetik içeren bir ifade kararlı Rust'ta reddedilir. Ayrıca fonksiyon gövdesinde tanımlanan iç öğeler (iç const, static, iç fonksiyon, iç struct) dıştaki generic parametreleri göremez. Çağrı yerinde değeri açıkça vermek isterseniz literal (f::<1>()), tek parçalı bir sabit adı ya da süslü parantez içinde bir sabit ifade (f::<{ 1 + 2 }>()) yazarsınız.
Turbofish: tip parametresini çağrıda açıkça vermek
Derleyici tip parametresini çoğu zaman argümanlardan veya sonucun atandığı değişkenden çıkarır. Çıkaramadığı durumda tipi iki yoldan birini kullanarak bildirirsiniz: değişkene tip notu yazmak (let n: i32 = ...) ya da çağrıda ::<> sözdizimini, topluluğun deyişiyle "turbofish"i kullanmak. Argüman almayan ve yalnızca dönüş tipinde T geçen fonksiyonlar bu durumun klasik örneğidir; parse ve collect da aynı nedenle sık sık turbofish ister:
fn varsayilan<T: Default>() -> T {
T::default()
}
fn main() {
let a = varsayilan::<i32>();
let b: String = varsayilan();
let c = "42".parse::<u8>();
let d = Vec::<u16>::with_capacity(4);
println!("{a} {b:?} {c:?} {}", d.capacity());
}
// çıktı: 0 "" Ok(42) 4varsayilan::<i32>() ve let b: String = varsayilan(); aynı işi farklı yerden bildirir. İkisini de yazmazsanız derleyici T için bir tip seçemez ve tip notu isteyen bir hata verir. Reference önemli bir ayrıntıya da dikkat çeker: parametre konumunda impl Trait kullanan bir fonksiyonda tip anonim olduğu için çağıran turbofish ile tip veremez. Bir fonksiyonu <T: Trait> biçiminden impl Trait biçimine (veya tersine) çevirmek, generic argüman sayısını değiştirdiği için kütüphane kullanıcıları açısından uyumluluğu bozan bir değişiklik olabilir.
Performans: monomorphization ne yapar?

Generic kullanmanın çalışma anında bir bedeli var mı sorusunun cevabı hayırdır. Rust derleme sırasında monomorphization uygular: generic kodun çağrıldığı her yere bakar ve kullanılan her somut tip için ayrı, generic olmayan bir kopya üretir. Some(5) ve Some(5.0) kullanan bir program derlendiğinde derleyici kavramsal olarak Option enum'unun biri i32, diğeri f64 için iki ayrı tanımını oluşturur. Çalışan kod, her tanımı elle kopyalamışsınız gibi davranır; çağrılar derleme anında çözüldüğü için buna statik dağıtım (static dispatch) denir.
Book, aynı yöntemi şöyle de özetler: derleyici, ilk örnekte elle yaptığımız genelleştirmenin tersini yapar ve generic tanımı yeniden somut tanımlara açar. Aynı koleksiyonda farklı tipleri bir arada tutmanız gerektiğinde ise generic tek başına yetmez, çünkü bir generic parametre aynı anda yalnızca tek bir somut tiple doldurulabilir. Bu durumda çalışma anında dağıtım yapan trait object'lere geçersiniz; iki yaklaşımın karşılaştırması dyn Trait yazısında.
| Kullanım yeri | Sözdizimi | Not |
|---|---|---|
| Fonksiyon | fn f<T>(x: T) -> T | Parametre adı fonksiyon adından sonra |
| Struct | struct Nokta<T> { x: T } | Alanlar aynı T ise aynı tipte olmalı |
| Enum | enum Option<T> { Some(T), None } | Varyant verisinde kullanılır |
| impl bloğu | impl<T> Nokta<T> { } | T, impl sonrasında bildirilir |
| Tipe özel impl | impl Nokta<f64> { } | Metot yalnızca o tipte vardır |
| Metot parametresi | fn karistir<X2, Y2>(self, ...) | Struct parametrelerinden bağımsız |
| Const generic | fn f<const N: usize>(a: [i32; N]) | Değer üzerinden parametre |
| Turbofish | f::<i32>() | Tip çıkarılamadığında açıkça verilir |
Sık yapılan hatalar
- Bound yazmadan işlem yapmak.
Tüzerinde>,+,{}ile yazdırma veyaclone()kullanmak için ilgili trait'i bound olarak eklemeniz gerekir (PartialOrd,Add,Display,Clone). Derleyicininhelpsatırı genellikle doğru trait'i önerir. implsonrasında parametreyi unutmak.impl Nokta<T>yazarsanız derleyiciTadında somut bir tip arar ve bulamadığını söyler. Doğrusuimpl<T> Nokta<T>.- Tek parametreyle farklı alan tipleri beklemek.
Nokta<T>içindeki tümTalanları aynı tiptedir; farklı tip için ikinci parametre ekleyin. - Tip çıkarımına fazla güvenmek. Argümanda geçmeyen tip parametreleri için tip notu ya da turbofish gerekir.
- Farklı tipleri tek
Vec<T>içinde toplamaya çalışmak. Generic parametre bir anda tek tipe karşılık gelir; karışık koleksiyon için enum veyaBox<dyn Trait>kullanın. - Const parametresiyle aritmetik yapmak.
[T; N + 1]gibi tipler kararlı Rust'ta yazılamaz; tasarımıNtek başına kullanılacak şekilde kurun.
Sık Sorulan Sorular
Generic ile trait arasındaki fark nedir?
Generic, "burada bir tip olacak" diyen yer tutucudur; trait ise "bu tip şu metotlara sahip olmalı" diyen davranış sözleşmesidir. İkisi birlikte kullanılır: T: PartialOrd yazdığınızda generic parametreye trait bound eklemiş olursunuz.
Generic kod programı yavaşlatır mı?
Hayır. Monomorphization sayesinde her somut tip için ayrı kod üretilir ve çağrılar derleme anında çözülür. The Rust Book'a göre çalışan kod, her tanımı elle kopyalamışsınız gibi performans gösterir.
Tip parametresi adı mutlaka T mi olmalı?
Hayır, geçerli herhangi bir tanımlayıcı kullanılabilir. Gelenek kısa UpperCamelCase adlardır: T, U, E, K, V. Book, parametrenin tip adlandırma kuralına (UpperCamelCase) uyduğunu ve T harfinin çoğu Rust programcısının varsayılan tercihi olduğunu belirtir.
Lifetime parametreleri de generic mi?
Evet. <'a, T> gibi listelerde lifetime parametreleri de generic parametredir ve her zaman önce yazılır. Ayrıntılar lifetime yazısında.
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 · Rust nedir · Rust trait tanımlama · Rust dyn Trait · Rust lifetime · Rust enum ve Option · E0277 ve E0308 tip hataları