Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Trait’lerle Ortak Davranış Tanımlama

Bir trait, belirli bir türün sahip olduğu ve diğer türlerle paylaşabileceği işlevselliği tanımlar. Ortak davranışı soyut bir biçimde tanımlamak için trait’leri kullanabiliriz. Jenerik bir türün, belirli bir davranışa sahip herhangi bir tür olabileceğini belirtmek için ise trait sınırlarını (trait bounds) kullanabiliriz.

Not: Trait’ler, aralarında bazı farklar bulunsa da diğer dillerde genellikle arayüz (interface) olarak adlandırılan özelliğe benzer.

Bir Trait Tanımlama

Bir türün davranışı, o tür üzerinde çağırabileceğimiz metotlardan oluşur. Farklı türlerin hepsi üzerinde aynı metotları çağırabiliyorsak bu türler aynı davranışı paylaşıyor demektir. Trait tanımları, belirli bir amacı gerçekleştirmek için gereken davranış kümesini tanımlamak üzere metot imzalarını bir araya getirmenin yoludur.

Örneğin, farklı tür ve uzunluklarda metinler tutan birden fazla struct’ımız olduğunu varsayalım: Belirli bir yerden bildirilen bir haber metnini tutan HaberMakalesi struct’ı ve en fazla 280 karakterlik bir metnin yanı sıra bu metnin yeni bir gönderi mi, yeniden paylaşım mı yoksa başka bir gönderiye yanıt mı olduğunu gösteren üst verileri barındıran SosyalGonderi struct’ı.

Bir HaberMakalesi veya SosyalGonderi örneğinde saklanabilecek verilerin özetlerini görüntüleyebilen toplayici adında bir medya toplayıcı (media aggregator) kütüphane crate’i geliştirmek istiyoruz. Bunun için her türden bir özet almamız gerekiyor ve bu özeti bir örnek üzerinde ozetle metodunu çağırarak isteyeceğiz. Liste 10-12, bu davranışı ifade eden dışarıya açık (pub) bir Ozet trait’inin tanımını göstermektedir.

Dosya adı: src/lib.rs
pub trait Ozet {
    fn ozetle(&self) -> String;
}
Liste 10-12: ozetle metodunun sağladığı davranıştan oluşan bir Ozet trait’i

Burada trait anahtar kelimesini ve ardından trait’in adını —bu örnekte Ozet— yazarak bir trait bildiriyoruz. Ayrıca birkaç örnek sonra göreceğimiz üzere, bu crate’e bağımlı olan diğer crate’lerin de bu trait’ten yararlanabilmesi için trait’i pub olarak işaretliyoruz. Süslü parantezlerin içinde ise bu trait’i uygulayan türlerin sahip olacağı davranışları betimleyen metot imzalarını —bu örnekte fn ozetle(&self) -> String— bildiriyoruz.

Metot imzasından sonra süslü parantezler içinde bir gövde tanımlamak yerine noktalı virgül koyuyoruz. Bu trait’i uygulayan her tür, metodun gövdesi için kendi özel davranışını sağlamak zorundadır. Derleyici, Ozet trait’ine sahip her türün tam olarak bu imzayla tanımlanmış bir ozetle metoduna sahip olmasını zorunlu kılacaktır.

Bir trait, gövdesinde birden fazla metot barındırabilir: Metot imzaları her satıra bir tane gelecek şekilde alt alta yazılır ve her satır noktalı virgülle biter.

Bir Tür Üzerinde Trait Uygulama

Ozet trait’inin metotları için istediğimiz imzaları tanımladığımıza göre, artık bu trait’i medya toplayıcımızdaki türler üzerinde uygulayabiliriz. Liste 10-13, ozetle metodunun dönüş değerini oluşturmak için manşeti, yazarı ve konumu kullanan HaberMakalesi struct’ı üzerindeki Ozet trait uygulamasını gösterir. SosyalGonderi struct’ı için ise gönderi içeriğinin zaten 280 karakterle sınırlandırıldığını varsayarak ozetle metodunu kullanıcı adı ve ardından gönderinin tüm metnini döndürecek şekilde tanımlıyoruz.

Dosya adı: src/lib.rs
pub trait Ozet {
    fn ozetle(&self) -> String;
}

pub struct HaberMakalesi {
    pub manset: String,
    pub konum: String,
    pub yazar: String,
    pub icerik: String,
}

impl Ozet for HaberMakalesi {
    fn ozetle(&self) -> String {
        format!("{}, {} ({})", self.manset, self.yazar, self.konum)
    }
}

pub struct SosyalGonderi {
    pub kullanici_adi: String,
    pub icerik: String,
    pub yanit: bool,
    pub yeniden_paylasim: bool,
}

impl Ozet for SosyalGonderi {
    fn ozetle(&self) -> String {
        format!("{}: {}", self.kullanici_adi, self.icerik)
    }
}
Liste 10-13: HaberMakalesi ve SosyalGonderi türleri üzerinde Ozet trait’inin uygulanması

Bir tür üzerinde trait uygulamak, normal metotları tanımlamaya çok benzer. Aradaki fark, impl anahtar kelimesinden sonra uygulamak istediğimiz trait’in adını yazmamız, ardından for anahtar kelimesini kullanmamız ve sonrasında trait’i hangi tür için uygulayacağımızı belirtmemizdir. impl bloğunun içine trait tanımında yer alan metot imzalarını yazarız. Ancak her imzanın sonuna noktalı virgül koymak yerine süslü parantez açar ve metot gövdesini, trait metotlarının o türe özgü olarak sergilemesini istediğimiz davranışla doldururuz.

Kütüphanemiz HaberMakalesi ve SosyalGonderi üzerinde Ozet trait’ini uyguladığına göre, crate kullanıcıları bu türlerin örnekleri üzerinde trait metotlarını tıpkı normal metotları çağırır gibi çağırabilir. Tek fark, kullanıcının türlerin yanı sıra trait’i de kapsama dahil etmesinin gerekmesidir. Bir ikili crate’in toplayici kütüphane crate’imizi nasıl kullanabileceğine dair bir örnek aşağıda verilmiştir:

use toplayici::{Ozet, SosyalGonderi};

fn main() {
    let gonderi = SosyalGonderi {
        kullanici_adi: String::from("horse_ebooks"),
        icerik: String::from(
            "elbette, muhtemelen zaten bildiğiniz gibi, insanlar",
        ),
        yanit: false,
        yeniden_paylasim: false,
    };

    println!("1 yeni gönderi: {}", gonderi.ozetle());
}

Bu kod ekrana 1 yeni gönderi: horse_ebooks: elbette, muhtemelen zaten bildiğiniz gibi, insanlar yazdırır.

Not: Yukarıdaki use toplayici::{Ozet, SosyalGonderi}; satırındaki Ozet kısmını silerseniz, SosyalGonderi türü kapsamda olduğu hâlde gonderi.ozetle() çağrısı derlenmez. Derleyici error[E0599]: no method named `ozetle` found hatasıyla birlikte items from traits can only be used if the trait is in scope (trait’lerden gelen öğeler yalnızca trait kapsamdaysa kullanılabilir) açıklamasını ve eklemeniz gereken use satırını önerir. Trait’i kapsama almak, derleyicinin metot aramasına o trait’in metotlarını da dahil eder. Aynı adlı metot sunan iki trait de kapsamdaysa çağrı yine belirsiz olabilir; böyle durumlarda Bölüm 20’de göreceğimiz tam nitelikli sözdizimiyle hangi trait’in kastedildiğini belirtiriz. Yalnızca standart kütüphanenin prelude’ünde yer alan Clone, ToString veya Iterator gibi trait’ler her kapsamda hazır olduğundan onlar için ek bir use satırına gerek yoktur.

toplayici crate’ine bağımlı olan diğer crate’ler de Ozet trait’ini kendi türleri üzerinde uygulamak üzere kapsama dahil edebilir. Burada dikkat edilmesi gereken önemli bir kısıtlama vardır: Bir trait’i bir tür üzerinde yalnızca trait’in kendisi, türün kendisi veya her ikisi birden kendi crate’imize yerel (local) ise uygulayabiliriz. Örneğin, SosyalGonderi türü toplayici crate’imize yerel olduğu için toplayici crate’imizin bir parçası olarak SosyalGonderi gibi özel bir tür üzerinde Display gibi standart kütüphane trait’lerini uygulayabiliriz. Benzer şekilde, Ozet trait’i toplayici crate’imize yerel olduğu için toplayici crate’i içinde Vec<T> üzerinde de Ozet trait’ini uygulayabiliriz.

Ancak harici trait’leri harici türler üzerinde uygulayamayız. Örneğin, toplayici crate’imiz içinde Vec<T> üzerinde Display trait’ini uygulayamayız; çünkü hem Display hem de Vec<T> standart kütüphanede tanımlanmıştır ve toplayici crate’imize yerel değildir. Bu kısıtlama, tutarlılık (coherence) adı verilen özelliğin ve daha özelinde —ebeveyn tür mevcut olmadığı için bu adı alan— yetim kuralının (orphan rule) bir parçasıdır. Bu kural, başkalarının kodunun sizin kodunuzu, sizin kodunuzun da başkalarının kodunu bozamamasını güvence altına alır. Bu kural olmasaydı iki farklı crate aynı tür için aynı trait’i uygulayabilir ve Rust hangi uygulamayı kullanacağını bilemezdi.

Not: Yetim kuralının bağımlılık çakışmalarını nasıl önlediğini somut bir senaryoyla düşünelim: Projenize eklediğiniz iki harici bağımlılığın ikisi de standart kütüphanedeki Vec<String> türü için Display trait’ini kendince tanımlayabilseydi, bir Vec<String> değerini {} ile yazdırmak istediğinizde derleyici hangi crate’in uygulamasını çağıracağına karar veremezdi; üstelik bir bağımlılığın yeni sürümüne böyle bir impl eklendiğinde çalışan kodunuz bir anda derlenemez hâle gelirdi. Yine de Vec<String> gibi harici bir türe Display gibi harici bir trait kazandırmanız gereken durumlarda, bu türü struct Sarmalayici(Vec<String>); şeklinde tek alanlı yerel bir demet struct’ı içine alarak kendi yerel türünüzü oluşturabilirsiniz. Ek bir öbek tahsisi gerektirmeyen ve genellikle derleyicinin ek çağrı maliyetini ortadan kaldırabildiği bu tekniğe newtype deseni denir; ayrıntılarını Bölüm 20’deki “Dış Trait’leri Newtype Deseniyle Uygulamak” bölümünde inceleyeceğiz.

Varsayılan Uygulamaları Kullanma

Bazen her tür üzerindeki tüm metotların tek tek yazılmasını zorunlu kılmak yerine, bir trait içindeki metotların bazıları veya tamamı için varsayılan uygulamalar (default implementations) tanımlamak faydalıdır. Böylece trait’i belirli bir tür üzerinde uygularken her bir metodun varsayılan davranışını olduğu gibi koruyabilir ya da geçersiz kılabiliriz (override).

Liste 10-14’te, Liste 10-12’de yaptığımız gibi yalnızca metot imzasını tanımlamak yerine Ozet trait’inin ozetle metodu için varsayılan bir string belirliyoruz.

Dosya adı: src/lib.rs
pub trait Ozet {
    fn ozetle(&self) -> String {
        String::from("(Devamını oku...)")
    }
}

pub struct HaberMakalesi {
    pub manset: String,
    pub konum: String,
    pub yazar: String,
    pub icerik: String,
}

impl Ozet for HaberMakalesi {}

pub struct SosyalGonderi {
    pub kullanici_adi: String,
    pub icerik: String,
    pub yanit: bool,
    pub yeniden_paylasim: bool,
}

impl Ozet for SosyalGonderi {
    fn ozetle(&self) -> String {
        format!("{}: {}", self.kullanici_adi, self.icerik)
    }
}
Liste 10-14: ozetle metodunun varsayılan bir uygulamasıyla birlikte Ozet trait’ini tanımlama

HaberMakalesi örneklerini özetlerken bu varsayılan uygulamayı kullanmak için impl Ozet for HaberMakalesi {} şeklinde boş bir impl bloğu yazarız.

Artık ozetle metodunu doğrudan HaberMakalesi üzerinde tanımlamıyor olsak da, trait içinde varsayılan bir uygulama sunduk ve HaberMakalesi türünün Ozet trait’ini uyguladığını belirttik. Sonuç olarak, bir HaberMakalesi örneği üzerinde ozetle metodunu yine şu şekilde çağırabiliriz:

use toplayici::{self, HaberMakalesi, Ozet};

fn main() {
    let makale = HaberMakalesi {
        manset: String::from("Penguenler Stanley Cup Şampiyonluğunu kazandı!"),
        konum: String::from("Pittsburgh, PA, ABD"),
        yazar: String::from("Iceburgh"),
        icerik: String::from(
            "Pittsburgh Penguenleri bir kez daha NHL'deki en iyi \
             hokey takımı.",
        ),
    };

    println!("Yeni makale mevcut! {}", makale.ozetle());
}

Bu kod ekrana Yeni makale mevcut! (Devamını oku...) yazdırır.

Varsayılan bir uygulama oluşturmak, Liste 10-13’teki SosyalGonderi üzerindeki Ozet uygulamasında herhangi bir değişiklik yapmamızı gerektirmez. Bunun nedeni, varsayılan bir uygulamayı geçersiz kılma sözdiziminin, varsayılan uygulaması olmayan bir trait metodunu uygulama sözdizimiyle birebir aynı olmasıdır.

Varsayılan uygulamalar, aynı trait içindeki diğer metotları —o metotların varsayılan bir uygulaması olmasa bile— çağırabilir. Bu sayede bir trait pek çok kullanışlı işlevselliği hazır olarak sunarken, trait’i uygulayan türlerden bunun yalnızca küçük bir parçasını tanımlamalarını isteyebilir. Örneğin Ozet trait’ini, uygulanması zorunlu bir yazari_ozetle metoduna ve bu yazari_ozetle metodunu çağıran varsayılan uygulamalı bir ozetle metoduna sahip olacak şekilde tanımlayabiliriz:

pub trait Ozet {
    fn yazari_ozetle(&self) -> String;

    fn ozetle(&self) -> String {
        format!(
            "({} yazarından daha fazlasını okuyun...)",
            self.yazari_ozetle()
        )
    }
}

pub struct SosyalGonderi {
    pub kullanici_adi: String,
    pub icerik: String,
    pub yanit: bool,
    pub yeniden_paylasim: bool,
}

impl Ozet for SosyalGonderi {
    fn yazari_ozetle(&self) -> String {
        format!("@{}", self.kullanici_adi)
    }
}

Ozet trait’inin bu sürümünü kullanmak için, trait’i bir tür üzerinde uygularken yalnızca yazari_ozetle metodunu tanımlamamız yeterlidir:

pub trait Ozet {
    fn yazari_ozetle(&self) -> String;

    fn ozetle(&self) -> String {
        format!(
            "({} yazarından daha fazlasını okuyun...)",
            self.yazari_ozetle()
        )
    }
}

pub struct SosyalGonderi {
    pub kullanici_adi: String,
    pub icerik: String,
    pub yanit: bool,
    pub yeniden_paylasim: bool,
}

impl Ozet for SosyalGonderi {
    fn yazari_ozetle(&self) -> String {
        format!("@{}", self.kullanici_adi)
    }
}

yazari_ozetle metodunu tanımladıktan sonra SosyalGonderi struct’ının örnekleri üzerinde ozetle metodunu çağırabiliriz; ozetle’nin varsayılan uygulaması da bizim sağladığımız yazari_ozetle tanımını çağırır. yazari_ozetle metodunu uyguladığımız için Ozet trait’i, fazladan kod yazmamıza gerek bırakmadan ozetle metodunun davranışını bize kazandırmış olur. Kullanımı şöyle görünür:

use toplayici::{self, Ozet, SosyalGonderi};

fn main() {
    let gonderi = SosyalGonderi {
        kullanici_adi: String::from("horse_ebooks"),
        icerik: String::from(
            "elbette, muhtemelen zaten bildiğiniz gibi, insanlar",
        ),
        yanit: false,
        yeniden_paylasim: false,
    };

    println!("1 yeni gönderi: {}", gonderi.ozetle());
}

Bu kod ekrana 1 yeni gönderi: (@horse_ebooks yazarından daha fazlasını okuyun...) yazdırır.

Bir metodun varsayılan uygulamasını, aynı metodun geçersiz kılınan uygulamasının içinden çağırmanın mümkün olmadığına dikkat edin.

Trait’leri Parametre Olarak Kullanma

Artık trait’lerin nasıl tanımlanacağını ve uygulanacağını bildiğinize göre, pek çok farklı türü kabul edebilen fonksiyonlar tanımlamak için trait’leri nasıl kullanacağımızı inceleyebiliriz. Liste 10-13’te HaberMakalesi ve SosyalGonderi türleri üzerinde uyguladığımız Ozet trait’ini kullanarak, Ozet trait’ini uygulayan bir türdeki oge parametresi üzerinde ozetle metodunu çağıran bir bildir fonksiyonu tanımlayacağız. Bunun için impl Trait sözdizimini şu şekilde kullanırız:

pub trait Ozet {
    fn ozetle(&self) -> String;
}

pub struct HaberMakalesi {
    pub manset: String,
    pub konum: String,
    pub yazar: String,
    pub icerik: String,
}

impl Ozet for HaberMakalesi {
    fn ozetle(&self) -> String {
        format!("{}, {} ({})", self.manset, self.yazar, self.konum)
    }
}

pub struct SosyalGonderi {
    pub kullanici_adi: String,
    pub icerik: String,
    pub yanit: bool,
    pub yeniden_paylasim: bool,
}

impl Ozet for SosyalGonderi {
    fn ozetle(&self) -> String {
        format!("{}: {}", self.kullanici_adi, self.icerik)
    }
}

pub fn bildir(oge: &impl Ozet) {
    println!("Son dakika haberi! {}", oge.ozetle());
}

oge parametresi için somut bir tür yazmak yerine impl anahtar kelimesini ve ilgili trait adını belirtiyoruz. Bu parametre, belirtilen trait’i uygulayan herhangi bir türü kabul eder. bildir fonksiyonunun gövdesinde, oge üzerinde Ozet trait’inden gelen ozetle gibi her türlü metodu çağırabiliriz. bildir fonksiyonunu çağırırken argüman olarak herhangi bir HaberMakalesi veya SosyalGonderi örneğine referans geçirebiliriz. Fonksiyonu String veya i32 gibi başka bir türle çağıran kod ise bu türler Ozet trait’ini uygulamadığından derlenmeyecektir.

Trait Sınırı Sözdizimi

impl Trait sözdizimi doğrudan ve yalın durumlarda gayet kullanışlıdır; ancak aslında trait sınırı (trait bound) olarak bilinen daha uzun bir biçimin sözdizimsel şekeridir (syntax sugar) ve şu şekilde görünür:

pub fn bildir<T: Ozet>(oge: &T) {
    println!("Son dakika haberi! {}", oge.ozetle());
}

Bu uzun biçim önceki bölümdeki örnekle tamamen eşdeğerdir ancak daha ayrıntılıdır. Trait sınırlarını, açılı ayraçlar içindeki jenerik tür parametresi bildiriminde iki nokta üst üsteden (:) hemen sonra yazarız.

impl Trait sözdizimi pratiktir ve basit durumlarda daha özlü kod yazmamızı sağlar; daha kapsamlı olan trait sınırı sözdizimi ise diğer durumlarda daha karmaşık kısıtlamaları ifade edebilir. Örneğin, Ozet trait’ini uygulayan iki parametre almak isteyebiliriz. Bunu impl Trait sözdizimiyle yazmak şöyle görünür:

pub fn bildir(oge1: &impl Ozet, oge2: &impl Ozet) {

Fonksiyonun oge1 ve oge2 parametrelerinin —her iki tür de Ozet trait’ini uyguladığı sürece— birbirinden farklı somut türlere sahip olmasına izin vermesini istiyorsak impl Trait kullanmak uygundur. Ancak her iki parametrenin de birebir aynı somut türe sahip olmasını zorunlu kılmak istiyorsak şu şekilde bir trait sınırı kullanmalıyız:

pub fn bildir<T: Ozet>(oge1: &T, oge2: &T) {

oge1 ve oge2 parametrelerinin türü olarak belirtilen jenerik T türü, fonksiyonu öyle kısıtlar ki oge1 ve oge2 argümanları olarak geçirilen değerlerin somut türü aynı olmak zorundadır.

+ Sözdizimi ile Birden Fazla Trait Sınırı

Birden fazla trait sınırı da belirtebiliriz. Diyelim ki bildir fonksiyonunun oge üzerinde ozetle metodunu çağırmasının yanı sıra Display biçimlendirmesini de kullanmasını istiyoruz: bildir tanımında oge’nin hem Display hem de Ozet trait’lerini uygulaması gerektiğini belirtiriz. Bunu + sözdizimiyle yapabiliriz:

pub fn bildir(oge: &(impl Ozet + Display)) {

+ sözdizimi, jenerik türler üzerindeki trait sınırlarında da geçerlidir:

pub fn bildir<T: Ozet + Display>(oge: &T) {

Bu iki trait sınırı belirtildiğinde, bildir fonksiyonunun gövdesi hem ozetle metodunu çağırabilir hem de oge değerini {} ile biçimlendirebilir.

where Cümlecikleriyle Daha Açık Trait Sınırları

Çok fazla trait sınırı kullanmanın okunabilirlik açısından dezavantajları vardır. Her jenerik tür parametresinin kendi trait sınırları olduğundan, birden fazla jenerik tür parametresi alan fonksiyonlarda fonksiyon adı ile parametre listesi arasına çok fazla trait sınırı bilgisi girebilir ve bu da fonksiyon imzasının okunmasını güçleştirir. Bu nedenle Rust, trait sınırlarını fonksiyon imzasından sonra gelen bir where cümleciği içinde belirtmek için alternatif bir sözdizimi sunar. Dolayısıyla şunu yazmak yerine:

fn bir_fonksiyon<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

şu şekilde bir where cümleciği kullanabiliriz:

fn bir_fonksiyon<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    unimplemented!()
}

Böylece fonksiyonun imzası kalabalıktan arınır: Çok sayıda trait sınırı içermeyen sıradan bir fonksiyonda olduğu gibi fonksiyon adı, parametre listesi ve dönüş türü birbirine yakın durur.

Trait Uygulayan Türler Döndürme

Bir trait’i uygulayan herhangi bir türdeki değeri döndürmek için dönüş konumunda da impl Trait sözdizimini kullanabiliriz:

pub trait Ozet {
    fn ozetle(&self) -> String;
}

pub struct HaberMakalesi {
    pub manset: String,
    pub konum: String,
    pub yazar: String,
    pub icerik: String,
}

impl Ozet for HaberMakalesi {
    fn ozetle(&self) -> String {
        format!("{}, {} ({})", self.manset, self.yazar, self.konum)
    }
}

pub struct SosyalGonderi {
    pub kullanici_adi: String,
    pub icerik: String,
    pub yanit: bool,
    pub yeniden_paylasim: bool,
}

impl Ozet for SosyalGonderi {
    fn ozetle(&self) -> String {
        format!("{}: {}", self.kullanici_adi, self.icerik)
    }
}

fn ozetlenebilir_dondur() -> impl Ozet {
    SosyalGonderi {
        kullanici_adi: String::from("horse_ebooks"),
        icerik: String::from(
            "elbette, muhtemelen zaten bildiğiniz gibi, insanlar",
        ),
        yanit: false,
        yeniden_paylasim: false,
    }
}

Dönüş türü olarak impl Ozet kullanarak, ozetlenebilir_dondur fonksiyonunun somut türün adını açıkça belirtmeksizin Ozet trait’ini uygulayan bir tür döndürdüğünü ifade ederiz. Bu örnekte ozetlenebilir_dondur bir SosyalGonderi döndürür, ancak fonksiyonu çağıran kodun bu somut türü bilmesi gerekmez.

Dönüş türünü yalnızca uyguladığı trait üzerinden belirtebilme olanağı, özellikle Bölüm 13’te ele alacağımız kapanışlar ve iteratörler bağlamında son derece kullanışlıdır. Kapanışlar ve iteratörler, yalnızca derleyicinin bildiği isimsiz türler ya da açıkça yazılması çok uzun süren karmaşık türler üretir. impl Trait sözdizimi, çok uzun bir tür adını tek tek yazmak zorunda kalmadan bir fonksiyonun Iterator trait’ini uygulayan bir tür döndürdüğünü özlü biçimde belirtmenizi sağlar.

Bununla birlikte, dönüş konumunda impl Trait’i yalnızca tek bir somut tür döndürüyorsanız kullanabilirsiniz. Örneğin, dönüş türü impl Ozet olarak belirtilen ancak koşula göre bir HaberMakalesi ya da bir SosyalGonderi döndürmeye çalışan aşağıdaki kod derlenmez:

pub trait Ozet {
    fn ozetle(&self) -> String;
}

pub struct HaberMakalesi {
    pub manset: String,
    pub konum: String,
    pub yazar: String,
    pub icerik: String,
}

impl Ozet for HaberMakalesi {
    fn ozetle(&self) -> String {
        format!("{}, {} ({})", self.manset, self.yazar, self.konum)
    }
}

pub struct SosyalGonderi {
    pub kullanici_adi: String,
    pub icerik: String,
    pub yanit: bool,
    pub yeniden_paylasim: bool,
}

impl Ozet for SosyalGonderi {
    fn ozetle(&self) -> String {
        format!("{}: {}", self.kullanici_adi, self.icerik)
    }
}

fn ozetlenebilir_dondur(degistir: bool) -> impl Ozet {
    if degistir {
        HaberMakalesi {
            manset: String::from(
                "Penguenler Stanley Cup Şampiyonluğunu kazandı!",
            ),
            konum: String::from("Pittsburgh, PA, ABD"),
            yazar: String::from("Iceburgh"),
            icerik: String::from(
                "Pittsburgh Penguenleri bir kez daha NHL'deki en iyi \
                 hokey takımı.",
            ),
        }
    } else {
        SosyalGonderi {
            kullanici_adi: String::from("horse_ebooks"),
            icerik: String::from(
                "elbette, muhtemelen zaten bildiğiniz gibi, insanlar",
            ),
            yanit: false,
            yeniden_paylasim: false,
        }
    }
}

impl Trait sözdiziminin derleyicide nasıl uygulandığına bağlı kısıtlamalar nedeniyle aynı fonksiyondan hem HaberMakalesi hem de SosyalGonderi döndürülmesine izin verilmez. Bu davranışa sahip bir fonksiyonun nasıl yazılacağını Bölüm 18’deki “Ortak Davranışı Soyutlamak İçin Trait Nesnelerini Kullanma” bölümünde ele alacağız.

Not: Dönüş konumundaki -> impl Ozet ifadesi çalışma zamanında dinamik bir tür seçimi yapmaz; derleyici fonksiyonun gövdesini inceleyerek döndürülen tek bir somut türü derleme zamanında saptar ve çağıran kodu bu somut türe göre derler. Bu, değerin mutlaka yığında saklanacağı anlamına gelmez; yerleşim ve optimizasyonlar kullanımına bağlıdır. Yukarıdaki kodda HaberMakalesi ile SosyalGonderi iki ayrı somut tür olduğundan, boyutları aynı olsaydı bile derleyici error[E0308]: 'if' and 'else' have incompatible types (if ve else kolları uyumsuz türlere sahip) hatası verir. Çalışma zamanındaki bir koşula göre aynı trait’i uygulayan farklı somut türler döndürmeniz gerektiğinde —derleyicinin hata çıktısındaki ipucunda da önerdiği üzere— değerleri öbekte (heap) tutup dönüş türünü Box<dyn Ozet> şeklinde bir trait nesnesi olarak tanımlayabilirsiniz (Box::new(HaberMakalesi { ... }) ve Box::new(SosyalGonderi { ... })). Bu yaklaşımın nasıl çalıştığını ve dinamik gönderim (dynamic dispatch) ödünleşimlerini Bölüm 18’de ayrıntılarıyla göreceğiz.

Trait Sınırlarını Kullanarak Metotları Koşullu Uygulama

Jenerik tür parametreleri kullanan bir impl bloğuyla birlikte bir trait sınırı belirterek, yalnızca belirtilen trait’leri uygulayan türler için koşullu olarak metotlar tanımlayabiliriz. Örneğin, Liste 10-15’teki Cift<T> türü, yeni bir Cift<T> örneği döndüren new ilişkili fonksiyonunu her zaman uygular (Bölüm 5’teki “Metot Sözdizimi” bölümünden hatırlayacağınız üzere Self, içinde bulunulan impl bloğunun türü —yani burada Cift<T>— için bir tür takma adıdır). Ancak bir sonraki impl bloğunda Cift<T>, karsilastir_goster metodunu yalnızca içindeki T türü hem karşılaştırma yapılmasını sağlayan PartialOrd trait’ini hem de ekrana yazdırmayı sağlayan Display trait’ini uyguluyorsa barındırır.

Dosya adı: src/lib.rs
use std::fmt::Display;

struct Cift<T> {
    x: T,
    y: T,
}

impl<T> Cift<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Cift<T> {
    fn karsilastir_goster(&self) {
        if self.x >= self.y {
            println!("En büyük üye x = {}", self.x);
        } else {
            println!("En büyük üye y = {}", self.y);
        }
    }
}
Liste 10-15: Trait sınırlarına bağlı olarak jenerik bir tür üzerinde metotları koşullu uygulama

Ayrıca, belirli bir trait’i uygulayan her tür için başka bir trait’i de koşullu olarak uygulayabiliriz. Trait sınırlarını karşılayan herhangi bir tür üzerinde gerçekleştirilen bu tür genel trait uygulamalarına örtü uygulamaları (blanket implementations, kapsayıcı uygulamalar olarak da adlandırılır) denir ve bunlar Rust standart kütüphanesinde yaygın biçimde kullanılır. Örneğin standart kütüphane, Display trait’ini uygulayan her tür üzerinde ToString trait’ini uygular. Standart kütüphanedeki ilgili impl bloğu şu koda benzer:

impl<T: Display> ToString for T {
    // --snip--
}

Standart kütüphanede bu örtü uygulaması bulunduğundan, Display trait’ini uygulayan herhangi bir tür üzerinde ToString trait’inin tanımladığı to_string metodunu çağırabiliriz. Örneğin tam sayılar Display trait’ini uyguladığı için bir tam sayıyı karşılık gelen String değerine şu şekilde dönüştürebiliriz:

#![allow(unused)]
fn main() {
let s = 3.to_string();
}

Örtü uygulamaları, ilgili trait’in dokümantasyonunda “Implementors” (Uygulayıcılar) başlığı altında listelenir.

Trait’ler ve trait sınırları, kod tekrarını azaltmak için jenerik tür parametreleri kullanan kodlar yazmamızı sağlarken, aynı zamanda derleyiciye bu jenerik türün belirli bir davranışa sahip olmasını istediğimizi belirtmemize de olanak tanır. Derleyici daha sonra bu trait sınırı bilgisini kullanarak kodumuzla birlikte kullanılan tüm somut türlerin doğru davranışı sunduğunu denetler. Dinamik tür sistemine sahip dillerde, bir tür üzerinde o türde tanımlı olmayan bir metodu çağırsaydık hatayla ancak çalışma zamanında karşılaşırdık. Oysa Rust bu hataları derleme zamanına taşır; böylece kodumuz henüz çalışmaya bile başlamadan sorunları düzeltmemizi zorunlu kılar. Üstelik davranışı derleme zamanında zaten doğruladığımız için çalışma zamanında bir türün ilgili davranışı destekleyip desteklemediğini denetleyen ek kodlar yazmamıza da gerek kalmaz. Bu sayede jeneriklerin esnekliğinden ödün vermeden performansı artırmış oluruz.