Rust'ta trait, bir tipin sahip olması gereken davranışı metot imzaları olarak tanımlayan yapıdır. trait Ozet { fn ozetle(&self) -> String; } şeklinde tanımlarsınız, impl Ozet for Haber { ... } ile bir tipe uygularsınız ve fn duyur(oge: &impl Ozet) ya da fn duyur<T: Ozet>(oge: &T) ile "bu davranışı taşıyan her tip" kabul eden fonksiyonlar yazarsınız. Diğer dillerdeki interface kavramına benzer, ancak varsayılan metotlar, koşullu uygulamalar ve orphan kuralı gibi kendine özgü yanları vardır.
Bu yazıda trait tanımlamayı, tiplere uygulamayı, varsayılan metot yazmayı, trait bound sözdizimlerini (impl Trait, T: Trait, +, where), dönüş konumunda impl Trait kullanımını, blanket implementation kavramını, supertrait'leri ve aynı adlı metotlarda tam nitelikli çağrıyı çalışan örneklerle anlatıyoruz. Generic tip parametrelerinin temeli generics yazısında, çalışma anında dağıtım yapan dyn Trait ise trait object yazısında. Tüm seri için Rust öğrenme rehberi.
Trait tanımlamak ve bir tipe uygulamak

Bir tipin davranışı, o tip üzerinde çağırabildiğiniz metotlardan oluşur. Farklı tipler aynı metotları sunuyorsa aynı davranışı paylaşıyor demektir; trait bu ortak davranışı adlandırır. Tanım trait anahtar kelimesi ve trait adıyla başlar. Süslü parantezin içine gövdesiz metot imzaları yazılır ve her imza noktalı virgülle biter. Derleyici, trait'i uygulayan her tipin bu imzayı birebir aynı biçimde tanımlamasını zorunlu kılar. Başka crate'lerin de kullanabilmesi için trait'i pub yapabilirsiniz.
Uygulama sözdizimi normal impl bloğuna benzer, tek farkı impl sonrasına trait adını, ardından for anahtar kelimesini ve tipi yazmanızdır. Blok içinde imzaları tekrar edip gövdelerini doldurursunuz. Book'taki haber toplayıcı örneğini Türkçeleştirerek kuralım:
pub trait Ozet {
fn yazar(&self) -> String;
// Varsayılan gövde: uygulayan tip isterse değiştirir
fn ozetle(&self) -> String {
format!("({} tarafından yazıldı, devamı için tıklayın)", self.yazar())
}
}
pub struct Haber {
pub baslik: String,
pub sehir: String,
pub muhabir: String,
}
impl Ozet for Haber {
fn yazar(&self) -> String {
self.muhabir.clone()
}
// Varsayılanı geçersiz kılıyoruz
fn ozetle(&self) -> String {
format!("{}, {} ({})", self.baslik, self.sehir, self.muhabir)
}
}
pub struct Gonderi {
pub kullanici: String,
pub metin: String,
}
impl Ozet for Gonderi {
fn yazar(&self) -> String {
format!("@{}", self.kullanici)
}
// ozetle yazılmadı: varsayılan gövde kullanılır
}
fn main() {
let h = Haber {
baslik: String::from("Kar yağışı başladı"),
sehir: String::from("Erzurum"),
muhabir: String::from("Ayşe Yılmaz"),
};
let g = Gonderi {
kullanici: String::from("rustsever"),
metin: String::from("Bugün trait öğrendim"),
};
println!("{}", h.ozetle());
println!("{}", g.ozetle());
println!("{} karakter", g.metin.chars().count());
}
// çıktı: Kar yağışı başladı, Erzurum (Ayşe Yılmaz)
// çıktı: (@rustsever tarafından yazıldı, devamı için tıklayın)
// çıktı: 20 karakterÖrnekte üç önemli davranış görülüyor. Birincisi, varsayılan metotlar aynı trait'in henüz gövdesi olmayan metotlarını çağırabilir: ozetle, yazar metodunu kullanır ve uygulayan tip yalnızca yazar metodunu yazarak ozetle davranışını bedavaya alır. İkincisi, varsayılanı geçersiz kılmanın sözdizimi gövdesiz bir metodu uygulamakla aynıdır. Üçüncüsü, Book'un açıkça belirttiği bir sınır: bir metodu geçersiz kıldığınızda aynı metodun varsayılan sürümünü kendi gövdenizden çağıramazsınız. Bütün metotlar için varsayılan gövde varsa boş bir blok yazmanız yeterlidir: impl Ozet for Haber {}.
Orphan kuralı: kimin trait'i, kimin tipi?
Bir trait'i bir tipe uygulayabilmeniz için trait'in, tipin ya da ikisinin birden sizin crate'inizde tanımlanmış olması gerekir. Kendi tipiniz olan Gonderi için standart kütüphanenin Display trait'ini uygulayabilirsiniz; kendi trait'iniz olan Ozet trait'ini de Vec<T> için uygulayabilirsiniz. Ancak harici bir trait'i harici bir tipe uygulayamazsınız. Bu kısıt coherence özelliğinin, daha özelde orphan (yetim) kuralının parçasıdır. Kural olmasaydı iki farklı crate aynı trait'i aynı tip için uygulayabilir ve Rust hangisini kullanacağını bilemezdi.
use std::fmt;
impl fmt::Display for Vec<i32> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} öğe", self.len())
}
}
fn main() {}
// error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crateBu sınırı aşmanın yerleşik yolu newtype desenidir: harici tipi tek alanlı bir tuple struct ile sararsınız. Sarmalayıcı sizin crate'inize ait olduğu için harici trait'i ona uygulayabilirsiniz. Bedeli, sarmalayıcının iç tipin metotlarını kendiliğinden taşımamasıdır; gerekirse metotları self.0 üzerinden tek tek yönlendirirsiniz ya da Deref uygularsınız:
use std::fmt;
struct Liste(Vec<String>);
impl fmt::Display for Liste {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let l = Liste(vec![String::from("çay"), String::from("şeker")]);
println!("l = {l}");
}
// çıktı: l = [çay, şeker]Trait'i parametre olarak kullanmak: impl Trait ve trait bound

Trait'in asıl gücü, onu taşıyan her tipi kabul eden fonksiyonlar yazmaktır. En kısa yazım parametre tipine impl Ozet yazmaktır. Bu parametre, Ozet uygulayan herhangi bir tipi kabul eder ve fonksiyon gövdesinde yalnızca trait'ten gelen metotları çağırabilirsiniz. String veya i32 gibi trait'i uygulamayan bir tip geçirirseniz kod derlenmez.
impl Trait aslında daha uzun bir biçimin, yani trait bound sözdiziminin kısaltmasıdır: fn duyur<T: Ozet>(oge: &T). Basit durumlarda ikisi aynı işi görür, ama aralarında önemli bir fark vardır. fn f(a: &impl Ozet, b: &impl Ozet) yazarsanız a ve b farklı tipler olabilir; tek koşul ikisinin de Ozet uygulamasıdır. fn f<T: Ozet>(a: &T, b: &T) yazarsanız iki argümanın aynı somut tipte olmasını şart koşarsınız. Birden fazla trait istemek için + kullanılır (impl Ozet + Display veya T: Ozet + Display). Bound listesi uzadığında imza okunmaz hale gelir; bu durumda bound'ları dönüş tipinden sonra where bloğuna taşırsınız:
use std::fmt::{Debug, Display};
trait Ozet {
fn ozetle(&self) -> String;
}
impl Ozet for i64 {
fn ozetle(&self) -> String {
format!("sayı {self}")
}
}
impl Ozet for &str {
fn ozetle(&self) -> String {
format!("metin '{self}'")
}
}
// a ve b farklı tipler olabilir
fn ikisi(a: &impl Ozet, b: &impl Ozet) {
println!("{} / {}", a.ozetle(), b.ozetle());
}
// a ve b aynı tipte olmak zorunda
fn ayni<T: Ozet>(a: &T, b: &T) {
println!("{} = {}", a.ozetle(), b.ozetle());
}
// Çoklu bound ve where
fn rapor<T, U>(t: &T, u: &U) -> String
where
T: Ozet + Display,
U: Clone + Debug,
{
format!("{t} | {} | {:?}", t.ozetle(), u.clone())
}
fn main() {
ikisi(&7_i64, &"kedi");
ayni(&1_i64, &2_i64);
println!("{}", rapor(&42_i64, &vec!["a", "b"]));
}
// çıktı: sayı 7 / metin 'kedi'
// çıktı: sayı 1 = sayı 2
// çıktı: 42 | sayı 42 | ["a", "b"]ayni(&1_i64, &"x") gibi bir çağrı derlenmez, çünkü T tek bir tipe çözülmelidir. Reference ayrıca impl Trait parametresinin anonim bir tip parametresi olduğunu, bu yüzden çağıranın turbofish ile tip belirtemeyeceğini not eder. Kütüphane yazıyorsanız iki biçim arasında geçiş yapmak çağıranlar için uyumluluğu bozabilir.
Dönüş tipinde impl Trait
impl Trait dönüş konumunda da kullanılabilir: fn yeni_gonderi() -> impl Ozet imzası "bu fonksiyon Ozet uygulayan bir tip döndürür, adını söylemiyorum" der. Çağıran yalnızca trait metotlarını kullanabilir. Bu özellik özellikle closure ve iterator döndüren fonksiyonlarda işe yarar, çünkü bu tiplerin adı ya yalnızca derleyicinin bildiği bir addır ya da yazılamayacak kadar uzundur; ayrıntılar closure ve iterator yazılarında.
Kritik sınır şudur: dönüş konumundaki impl Trait, fonksiyonun bütün dönüş yollarında tek bir somut tipe çözülmelidir. Bir koşula göre bazen Haber, bazen Gonderi döndürmeye çalışırsanız derleyici iki dalın tiplerinin uyuşmadığını söyler:
trait Ozet {
fn ozetle(&self) -> String;
}
struct Haber;
struct Gonderi;
impl Ozet for Haber {
fn ozetle(&self) -> String { String::from("haber") }
}
impl Ozet for Gonderi {
fn ozetle(&self) -> String { String::from("gönderi") }
}
fn sec(haber_mi: bool) -> impl Ozet {
if haber_mi { Haber } else { Gonderi }
}
fn main() {
println!("{}", sec(true).ozetle());
}
// error[E0308]: `if` and `else` have incompatible typesÇalışma anında farklı tipler döndürmeniz gerekiyorsa çözüm Box<dyn Ozet> döndürmektir; bu desen dyn Trait yazısında çalışan bir örnekle anlatılıyor. Rust 2024 edition'da dönüş konumundaki impl Trait, kapsamdaki bütün generic parametreleri (lifetime'lar dahil) kendiliğinden yakalar; yakalamayı daraltmak için use<..> bound'u kullanılır. Bu değişiklik 2024 edition yazısında ele alınıyor.
Koşullu metotlar ve blanket implementation
Generic bir impl bloğuna trait bound eklediğinizde, o bloktaki metotlar yalnızca koşulu sağlayan tipler için var olur. Aşağıdaki Cift<T> tipinin yeni fonksiyonu her T için tanımlıdır, fakat buyugu_goster metodu yalnızca T hem PartialOrd hem Display uyguluyorsa çağrılabilir. Aynı fikri trait düzeyinde uygularsanız blanket implementation elde edersiniz: "şu trait'i uygulayan her tip için bu trait'i de uygula". Standart kütüphane ToString trait'ini Display uygulayan her tip için bu şekilde sağlar; 3.to_string() çağrısı bu yüzden çalışır. Bir trait'in belgesindeki "Implementors" bölümü bu tür uygulamaları listeler.
use std::fmt::Display;
struct Cift<T> {
x: T,
y: T,
}
impl<T> Cift<T> {
fn yeni(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Cift<T> {
fn buyugu_goster(&self) {
if self.x >= self.y {
println!("En büyük x = {}", self.x);
} else {
println!("En büyük y = {}", self.y);
}
}
}
// Blanket implementation: Display uygulayan her tip Etiket kazanır
trait Etiket {
fn etiketle(&self) -> String;
}
impl<T: Display> Etiket for T {
fn etiketle(&self) -> String {
format!("<{}>", self)
}
}
fn main() {
Cift::yeni(3, 9).buyugu_goster();
Cift::yeni("armut", "elma").buyugu_goster();
println!("{} {} {}", 42.etiketle(), 'ğ'.etiketle(), "ılık".etiketle());
}
// çıktı: En büyük y = 9
// çıktı: En büyük y = elma
// çıktı: <42> <ğ> <ılık>Supertrait ve aynı adlı metotlar
Bir trait'in başka bir trait'e dayanmasını istiyorsanız tanımda iki nokta ile supertrait belirtirsiniz: trait Cerceve: Display. Bu durumda Cerceve uygulamak isteyen tipin önce Display uygulaması gerekir; karşılığında Cerceve içindeki varsayılan metotlar to_string gibi Display olanaklarını kullanabilir. Display uygulamayan bir tipe Cerceve uygulamaya çalışırsanız E0277 hatası alırsınız.
Rust, iki farklı trait'in aynı adlı metot tanımlamasını ve bir tipin ikisini birden uygulamasını engellemez. Tipin kendi metodu da aynı adı taşıyabilir. kisi.selamla() çağrısında derleyici varsayılan olarak tipin doğrudan kendi metodunu seçer; trait sürümü için Resmi::selamla(&kisi) yazarsınız. self almayan ilişkili fonksiyonlarda bu da yetmez, çünkü hangi tipin uygulamasının kastedildiği belirsizdir; o zaman tam nitelikli sözdizimi gerekir: <Tip as Trait>::fonksiyon().
use std::fmt;
trait Cerceve: fmt::Display {
fn cercevele(&self) -> String {
let ic = self.to_string();
let cizgi = "*".repeat(ic.chars().count() + 4);
format!("{cizgi}\n* {ic} *\n{cizgi}")
}
}
struct Kisi;
impl fmt::Display for Kisi {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Gül")
}
}
impl Cerceve for Kisi {}
trait Resmi {
fn selamla(&self) -> &str;
fn tur() -> &'static str;
}
impl Resmi for Kisi {
fn selamla(&self) -> &str { "İyi günler" }
fn tur() -> &'static str { "resmî" }
}
impl Kisi {
fn selamla(&self) -> &str { "Selam" }
fn tur() -> &'static str { "samimi" }
}
fn main() {
let k = Kisi;
println!("{}", k.cercevele());
println!("{} / {}", k.selamla(), Resmi::selamla(&k));
println!("{} / {}", Kisi::tur(), <Kisi as Resmi>::tur());
}
// çıktı: *******
// çıktı: * Gül *
// çıktı: *******
// çıktı: Selam / İyi günler
// çıktı: samimi / resmîÇerçeve genişliği hesaplanırken len() yerine chars().count() kullanıldığına dikkat edin: "Gül" UTF-8'de 4 bayt ama 3 karakterdir. Bayt ile karakter farkı String ve str yazısında ayrıntılı anlatılıyor.
İlişkili tipler ve varsayılan tip parametreleri

Trait'ler metot dışında ilişkili tip de taşıyabilir. En bilinen örnek Iterator trait'idir: type Item; satırı bir yer tutucudur ve next metodu Option<Self::Item> döndürür. Generic parametreden farkı şudur: Iterator<T> gibi generic bir trait aynı tip için birden çok kez (farklı T değerleriyle) uygulanabilirdi ve her çağrıda hangisinin kastedildiğini belirtmek gerekirdi. İlişkili tipte ise bir tip için tek bir impl Iterator olabilir, bu yüzden Item bir kez seçilir ve çağrılarda tip notu gerekmez. Trait'ler ayrıca Add<Rhs = Self> örneğindeki gibi varsayılan tip parametresi alabilir. Bu iki konu operatör aşırı yüklemeyle birlikte ops trait'leri yazısında işleniyor.
| Sözdizimi | Anlamı | Ne zaman |
|---|---|---|
| fn f(x: &impl T) | T uygulayan herhangi bir tip | Basit tek parametre |
| fn f<U: T>(a: &U, b: &U) | İki argüman aynı somut tip | Tiplerin eşleşmesi gerekiyorsa |
| U: T + Display | Birden çok trait şartı | Birkaç davranış gerekiyorsa |
| where U: T, V: Clone | Bound listesi imzanın sonunda | Bound listesi uzunsa |
| fn f() -> impl T | Adı gizli tek somut tip | Closure ve iterator dönüşü |
| impl<U: Display> T for U | Blanket implementation | Bir trait üzerinden toplu uygulama |
| trait A: B | Supertrait | A, B davranışına dayanıyorsa |
| <Tip as Trait>::f() | Tam nitelikli çağrı | Aynı adlı fonksiyonlar çakışıyorsa |
Sık yapılan hatalar
- Trait'i kapsama almamak. Tip içe aktarılmış olsa bile trait
useile kapsama alınmadıkça metodu çağrılamaz. - Harici trait'i harici tipe uygulamak. E0117 alırsınız; newtype sarmalayıcı kullanın.
- Dönüş konumunda
impl Traitile iki farklı tip döndürmek. Bütün yollar aynı somut tipi döndürmelidir; aksi durumdaBox<dyn Trait>kullanın. - İki parametrenin aynı tipte olmasını beklerken
impl Traityazmak. Aynı tip şartı için adlandırılmış generic parametre gerekir. - Geçersiz kılınan metottan varsayılanı çağırmaya çalışmak. Bu mümkün değildir; ortak kodu ayrı bir yardımcı metoda taşıyın.
- İmzayı trait'tekinden farklı yazmak.
&selfyerine&mut selfveya farklı dönüş tipi yazılırsa derleyici imza uyuşmazlığı bildirir.
Sık Sorulan Sorular
Trait ile interface aynı şey mi?
Benzerdir ama aynı değildir. The Rust Book trait'leri diğer dillerdeki interface özelliğine benzetir ve bazı farklar olduğunu belirtir: varsayılan metotlar, mevcut tiplere sonradan uygulama, blanket implementation ve orphan kuralı Rust'a özgü ayrıntılardır.
impl Trait mi, generic parametre mi kullanmalıyım?
Tek parametreli basit fonksiyonlarda impl Trait daha okunur. Birden çok parametrenin aynı tipte olması gerekiyorsa, çağıranın turbofish ile tip vermesini istiyorsanız ya da bound'lar karmaşıksa adlandırılmış parametre ve where kullanın.
Kendi trait'imi i32 veya String için uygulayabilir miyim?
Evet. Trait sizin crate'inizde tanımlıysa standart kütüphane tipleri dahil herhangi bir tipe uygulayabilirsiniz. Engellenen yalnızca harici trait ile harici tipin birleşimidir.
Trait'e alan (field) ekleyebilir miyim?
Hayır, trait yalnızca metot, ilişkili tip ve ilişkili sabit gibi öğeler tanımlar. Veriye ihtiyaç varsa trait'e bir getter metodu ekleyip her tipin kendi alanından döndürmesini sağlarsınız; örnekteki yazar metodu bunu yapıyor.
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 generics · Rust dyn Trait · Rust derive · Rust operatör aşırı yükleme · E0277 trait bound hatası