Ders 01 / 15
Trait Tanımı ve Gerçekleştirimi
Bir trait, tipten bağımsız bir davranış sözleşmesi kurar ve varsayılan bir gövde taşıyabilir; on altı gövdelik bir ölçüm çekirdeği koşturulduğunda bu varsayılan gövdenin bir tip için dört kez, onu yeniden yazan bir tip için sıfır kez koştuğu görülür — kurs boyunca sorulacak soru budur: kaynakta yazdığın bu gövde kaç kez koştu?
Yayın Güncelleme
Bu derste
- Trait tanımının bir davranış sözleşmesi kurduğunu ve varsayılan gövde taşıyabildiğini gösterir
- Ölçüm çekirdeğini koşturarak varsayılan bir gövdenin bir tip için her seferinde, onu yeniden yazan tip için hiç koşmadığını doğrular
- trait ile arayüz kavramlarını birbirinden ayırır ve M09/K02 ile ölçü eksenindeki farkı açıklar
İçindekiler
Sahiplik ve Bellek Modeli kursu bir değerin ömrünün nerede bittiğini ölçtü: kapsam sonunda mı, taşındığı yerde mi, açık bir çağrıda mı, yoksa bir kabıyla birlikte mi. Bir değer ömrü boyunca elinde kalırken, onunla ne yapıldığı ayrı bir sorudur ve bu kurs oraya bakıyor. Bir davranışı bir tipten ayırmak — aynı işlevi birden fazla tipe aynı adla tanıtmak — o davranışın gövdesinin kaç kez çalıştığını da belirleyen bir karar haline geliyor. Kurs boyunca tek bir soru soruluyor: kaynakta yazdığın bu gövde kaç kez koştu?
Bu soruya üç yanıt var ve kurs boyunca bu üç ad değişmiyor.
Üç Sınıf
| Sınıf | Tanım |
|---|---|
| her seferinde | Çağrı ya da eleman başına kaçınılmaz olarak koşuyor |
| yalnız gerektiğinde | Yalnız sonucu istendiğinde, gerektiği kadar koşuyor |
| hiç | Kaynakta yazılı, ama bu koşumda bir kez bile koşmadı |
Bu üçlü daha önce sorulan iki soruyla karıştırılmamalı. Nesneye Dayalı Java kursu kararı hangi tipin verdiğini ölçtü — bildirilen tip mi, çalışma zamanı tipi mi. Arayüzler, Hatalar ve Genellikler kursu (Go) bağın nerede kurulduğunu ölçtü — bildirimde mi, kullanım yerinde mi, değerin içinde mi. Bu kurs ikisini de sormuyor; saymayı soruyor. Bir trait gerçekleştirimi yazıldığında bağ zaten kurulmuş olur — bu derste ölçülen, o bağ kurulduktan sonra gövdenin çalışma zamanında kaç kez tetiklendiğidir.
Bir trait, bir davranış sözleşmesidir: bir ya da daha fazla yöntem imzası bildirir, bu
yöntemlerin tipten bağımsız bir adla çağrılabilmesini sağlar. Programlama Temelleri
kursundaki fonksiyon tanımı tek bir tipe bağlıydı; trait bunu genelliyor — aynı yöntem adı,
onu gerçekleştiren her tip için ayrı bir gövdeye karşılık gelebiliyor. Bu, arayüz (interface)
kavramına yakın görünse de aynı şey değil: arayüz, Go kursunda bağın nerede kurulduğu
ekseninde tanımlandı (örtük gerçekleştirim, bildirim gerektirmeden); trait ise Rust’ta bir
tipin impl Ad for Tip yazımıyla açıkça bildirdiği bir sözleşmedir ve üstelik varsayılan
bir gövde taşıyabilir. Kurs boyunca trait sözcüğü çevrilmeden kullanılacak.
Zorunlu ve İsteğe Bağlı Yöntemler
Bir trait bildirimi, trait Ad { ... } gövdesinin içinde bir dizi yöntem imzası sıralar.
Her imza iki biçimden birini alabilir: yalnız bir başlık (fn ad(&self) -> Tip;, noktalı
virgülle biten, gövdesiz) ya da bir başlık artı bir gövde (fn ad(&self) -> Tip { ... }).
Birinci biçim zorunludur: trait’i gerçekleştiren her tip bu yöntemi kendi impl bloğunda
mutlaka yazmak zorundadır, aksi halde gerçekleştirim tamamlanmamış sayılır ve kaynak
derlenmez. İkinci biçim isteğe bağlıdır: gerçekleştiren tip bu yöntemi hiç yazmayabilir,
yazmazsa trait’in kendi gövdesi devreye girer; yazarsa kendi gövdesi çalışır ve trait’inki
hiç görünmez.
Bu ders boyunca kullanılan Etiket trait’i üçünü birden gösteriyor. defteri ve tip
gövdesiz bildirilmiş — her gerçekleştirim bu ikisini kendi yazmak zorunda, çünkü trait’in
kendisi bir tipin defterine ya da adına dair hiçbir varsayımda bulunamaz. ad ise bir
varsayılan gövdeyle bildirilmiş: Sade bu yöntemi hiç yazmıyor ve trait’in gövdesini
miras alıyor, Ozel ise kendi gövdesini yazıp varsayılanı örtüyor. Aynı trait bildirimi
içinde zorunlu ve isteğe bağlı yöntemler bir arada durabiliyor; bu, bir sözleşmenin
kimi maddelerinin her imzalayan için farklı olmak zorunda olduğunu (adres, isim gibi), kimi
maddelerinin ise ortak bir varsayılanla başlayıp yalnız gerekince değiştirilebileceğini
söylemesine benziyor.
impl Etiket for Sade yazımının kendisi de iki parçaya ayrılıyor: Etiket, hangi
sözleşmenin gerçekleştirildiğini söylüyor; Sade, sözleşmeyi kimin gerçekleştirdiğini.
Aynı trait, farklı tipler için ayrı impl bloklarıyla defalarca gerçekleştirilebilir —
kere.rsde Sade ve Ozel için iki ayrı impl Etiket for ... bloğu bulunuyor, ikisi de
aynı trait adını taşıyor ama gerçekleştirdikleri gövdeler bağımsız.
Ölçüm Çekirdeği
Kursun geri kalanında kesitleri alınacak çekirdek kere.rsdir. On altı ayrı gövdeyi tek
koşumda sınıflandırır ve her birini bir gözlem defterine kaydeder. Defter, önceki kursun
paylaşımlı sahiplik dersinde kurulan düzeneği kullanıyor: Rc<RefCell<BTreeMap<String, u32>>>
— birden fazla sahibi olan, iç değişebilirlik taşıyan, sıralı bir sayaç.
// kere.rs — bir govde kac kez kostu: her seferinde mi, yalniz gerektiginde mi, hic mi.
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::ops::Add;
use std::rc::Rc;
type Defter = Rc<RefCell<BTreeMap<String, u32>>>;
fn say(defter: &Defter, ad: &str) {
*defter.borrow_mut().entry(ad.to_string()).or_insert(0) += 1;
}
fn kere(defter: &Defter, ad: &str) -> u32 {
*defter.borrow().get(ad).unwrap_or(&0)
}
// --- trait: varsayilan govde ve onu yeniden yazan tip ---
trait Etiket {
fn defteri(&self) -> &Defter;
fn tip(&self) -> &'static str;
fn ad(&self) -> String {
let anahtar = format!("varsayilan-govde-{}", self.tip());
say(self.defteri(), &anahtar);
String::from("varsayilan")
}
}
struct Sade {
defter: Defter,
}
struct Ozel {
defter: Defter,
}
impl Etiket for Sade {
fn defteri(&self) -> &Defter {
&self.defter
}
fn tip(&self) -> &'static str {
"sade"
}
}
impl Etiket for Ozel {
fn defteri(&self) -> &Defter {
&self.defter
}
fn tip(&self) -> &'static str {
"ozel"
}
fn ad(&self) -> String {
say(self.defteri(), "yeniden-yazilan-govde");
String::from("ozel")
}
}
// --- genellik ile trait nesnesi: ayni is, iki cagri bicimi ---
fn statik<T: Etiket>(x: &T) -> String {
say(x.defteri(), "statik-gonderim");
x.ad()
}
fn dinamik(x: &dyn Etiket) -> String {
say(x.defteri(), "dinamik-gonderim");
x.ad()
}
// --- islec asiri yukleme ---
#[derive(Clone)]
struct Uzunluk {
deger: i32,
defter: Defter,
}
impl Add for Uzunluk {
type Output = Uzunluk;
fn add(self, oteki: Uzunluk) -> Uzunluk {
say(&self.defter, "islec-govdesi");
Uzunluk {
deger: self.deger + oteki.deger,
defter: Rc::clone(&self.defter),
}
}
}
// --- hata yolu ---
#[derive(Debug)]
struct SayiHatasi;
impl From<std::num::ParseIntError> for SayiHatasi {
fn from(_: std::num::ParseIntError) -> SayiHatasi {
SayiHatasi
}
}
fn cozumle(defter: &Defter, metin: &str, etiket: &str) -> Result<i32, SayiHatasi> {
let sayi: i32 = metin.parse()?;
say(defter, etiket);
Ok(sayi * 2)
}
fn hazir_varsayilan(defter: &Defter) -> i32 {
say(defter, "hazir-varsayilan");
-1
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));
let mut sinif: Vec<(&str, &str)> = Vec::new();
// 1-2. varsayilan govde: yeniden yazmayan tipte kosuyor, yazan tipte hic kosmuyor
let sade = Sade {
defter: Rc::clone(&defter),
};
let ozel = Ozel {
defter: Rc::clone(&defter),
};
let _ = sade.ad();
let _ = ozel.ad();
let _ = ozel.ad();
// 3-4. ayni is iki cagri biciminde
let _ = statik(&sade);
let _ = statik(&sade);
let _ = dinamik(&sade as &dyn Etiket);
let _ = dinamik(&ozel as &dyn Etiket);
// 5. islec govdesi
let a = Uzunluk {
deger: 3,
defter: Rc::clone(&defter),
};
let b = Uzunluk {
deger: 4,
defter: Rc::clone(&defter),
};
let toplam = a.clone() + b;
let toplam = toplam + a;
let _ = toplam.deger;
// 6. hazir varsayilan: deger VARKEN bile kosuyor
let dolu: Option<i32> = Some(7);
let _ = dolu.unwrap_or(hazir_varsayilan(&defter));
// 7-8. tembel varsayilan: deger varken hic, yokken bir kez
let bos: Option<i32> = None;
let _ = dolu.unwrap_or_else(|| {
say(&defter, "tembel-varsayilan-gereksiz");
-1
});
let _ = bos.unwrap_or_else(|| {
say(&defter, "tembel-varsayilan-gerekli");
-1
});
// 9-10. soru isaretinden sonrasi
let _ = cozumle(&defter, "21", "soru-sonrasi-basarili");
let _ = cozumle(&defter, "yirmibir", "soru-sonrasi-basarisiz");
// 11. hata donusumu
if cozumle(&defter, "yirmibir", "yok-sayilan").is_err() {
say(&defter, "hata-donusumu");
}
// 12. tuketilmeyen zincir
let _tembel = vec![1, 2, 3, 4].into_iter().map(|x| {
say(&defter, "tuketilmeyen-zincir");
x * 2
});
// 13. tuketilen zincir: her elemanda
let toplam: i32 = vec![1, 2, 3, 4]
.into_iter()
.map(|x| {
say(&defter, "tuketilen-zincir");
x * 2
})
.sum();
let _ = toplam;
// 14. erken biten zincir: yalniz gerektigi kadar
let bulunan = vec![1, 2, 3, 4]
.into_iter()
.map(|x| {
say(&defter, "erken-biten-zincir");
x * 2
})
.find(|x| *x > 2);
let _ = bulunan;
// 15. cagrilmayan kapanis
let _kapanis = || say(&defter, "cagrilmayan-kapanis");
// --- rapor ---
let govdeler = [
("varsayilan-govde-sade", "her seferinde"),
("yeniden-yazilan-govde", "her seferinde"),
("statik-gonderim", "her seferinde"),
("dinamik-gonderim", "her seferinde"),
("islec-govdesi", "her seferinde"),
("hazir-varsayilan", "her seferinde"),
("tuketilen-zincir", "her seferinde"),
("hata-donusumu", "her seferinde"),
("tembel-varsayilan-gerekli", "yalniz gerektiginde"),
("soru-sonrasi-basarili", "yalniz gerektiginde"),
("erken-biten-zincir", "yalniz gerektiginde"),
("varsayilan-govde-ozel", "hic"),
("tembel-varsayilan-gereksiz", "hic"),
("soru-sonrasi-basarisiz", "hic"),
("tuketilmeyen-zincir", "hic"),
("cagrilmayan-kapanis", "hic"),
];
println!("{:<28}{:<22}{}", "govde", "sinif", "kac kez");
for (ad, sinifi) in govdeler {
let n = kere(&defter, ad);
println!("{:<28}{:<22}{}", ad, sinifi, n);
sinif.push((ad, sinifi));
}
println!();
for s in ["her seferinde", "yalniz gerektiginde", "hic"] {
let adet = sinif.iter().filter(|(_, k)| *k == s).count();
println!("{:<22}{}", s, adet);
}
println!("toplam govde: {}", sinif.len());
}
govde sinif kac kez varsayilan-govde-sade her seferinde 4 yeniden-yazilan-govde her seferinde 3 statik-gonderim her seferinde 2 dinamik-gonderim her seferinde 2 islec-govdesi her seferinde 2 hazir-varsayilan her seferinde 1 tuketilen-zincir her seferinde 4 hata-donusumu her seferinde 1 tembel-varsayilan-gerekli yalniz gerektiginde 1 soru-sonrasi-basarili yalniz gerektiginde 1 erken-biten-zincir yalniz gerektiginde 2 varsayilan-govde-ozel hic 0 tembel-varsayilan-gereksiz hic 0 soru-sonrasi-basarisiz hic 0 tuketilmeyen-zincir hic 0 cagrilmayan-kapanis hic 0 her seferinde 8 yalniz gerektiginde 3 hic 5 toplam govde: 16
TR1. Bu koşum belirlenimcidir: beş ayrı koşumda kaynağın aynı on altı satırı, aynı sırayla
ve aynı sınıf dağılımıyla üretildiği doğrulandı. On altı gövdenin 8’i her seferinde, 3’ü
yalnız gerektiğinde koştu, 5’i hiç koşmadı. Bu sayı kursun geri kalanında değişmiyor; her
ders yalnız kendi kesitini alıyor. Rapor bir sıralı sözlük (BTreeMap) kullanıyor — bir karma
sözlüğün gezinme sırası koşumdan koşuma değişebileceği için, sayım burada bile sıralamayı
güvenceye bağlı bir yapıya bırakıyor.
Varsayılan Gövde İki Tip İçin İki Ayrı Sonuç Veriyor
Etiket trait’i üç yöntem bildiriyor: defteri ve tip hiç gövde taşımıyor — her
gerçekleştirim bunları kendisi yazmak zorunda. Üçüncü yöntem, ad, bir varsayılan gövde
taşıyor: impl Etiket for Sade bu gövdeye hiç dokunmadığı için, Sade üzerinde ad
çağrıldığında trait’in kendi gövdesi çalışıyor. Ozel ise ad‘ı kendi gövdesiyle yeniden
yazıyor — trait’teki varsayılan gövde Ozel için hiçbir zaman devreye girmiyor.
Tablodaki iki satır bu ikiliyi doğrudan gösteriyor. varsayilan-govde-sade dört kez koştu:
sade.ad() bir kez doğrudan çağrıldı, statik(&sade) iki kez çağrıldı ve her çağrı kendi
içinde x.ad()‘ı tetikledi, dinamik(&sade ...) bir kez çağrıldı ve o da aynı şekilde
x.ad()‘ı tetikledi — toplam dört çağrı, dördü de trait’in varsayılan gövdesine düştü.
yeniden-yazilan-govde ise üç kez koştu: ozel.ad() iki kez doğrudan, dinamik(&ozel ...)
bir kez — üçü de Ozelin kendi yazdığı gövdeyi çalıştırdı, trait’in varsayılanına hiç
uğramadı. İkisi de her seferinde sınıfına giriyor: çağrıldıkları her yerde kaçınılmaz
olarak koşuyorlar, aradaki fark hangi gövdenin koştuğunda.
TR2. Arayüzler, Hatalar ve Genellikler kursu bir gerçekleştirimin bağının nerede
kurulduğunu ölçtü — Go’da bir tip bir arayüzü örtük olarak, bildirim yapmadan
gerçekleştiriyordu. Rust’ta bu bağ impl Etiket for Sade satırıyla açıkça kuruluyor;
kurs bu farkı bir kez kaydedip geçiyor. Burada asıl ölçülen, bağ kurulduktan sonra o
gerçekleştirimin gövdesinin koşum sırasında kaç kez tetiklendiği — varsayilan-govde-sade
ile yeniden-yazilan-govde arasındaki 4 ile 3 farkı, bağın kendisinden değil çağrı
sayısından geliyor.
hazir-varsayilan, islec-govdesi ve hata yolundaki hata-donusumu de aynı sınıfa giriyor —
her seferinde koşuyorlar, çünkü çekirdekteki her biri tam olarak çağrıldığı kadar çalışıyor.
Kursun ilerleyen dersleri bu satırların her birini ayrı ayrı büyütecek: Trait Sınırları
statik-gonderimin sınıra bağlı olup olmadığını, İlişkili Tipler islec-govdesinin
imzasındaki Output alanını, Nesne Güvenliği ve Dinamik Gönderim statik-gonderim ile
dinamik-gonderimi yan yana koyacak.
Sınırlayıcı Ölçüm
varsayilan-govde-ozel satırı sıfır. Bu, varsayılan gövdenin var olmadığı anlamına
gelmiyor — kaynakta duruyor, trait Etiket bildiriminin içinde, tıpkı Sade için koştuğu
haliyle. Sıfır olmasının nedeni, Ozelin kendi ad yöntemini yazarak bu gövdeye hiç
uğramaması. Aynı kaynak satırı bir tip için dört kez çalışırken başka bir tip için hiç
çalışmıyor; koşum sayısı gövdenin varlığını değil, hangi tiplerin bu gövdeyi kullandığını
ölçüyor.
Bu ayrım kursun geri kalanında tekrar karşımıza çıkacak: hiç sınıfına giren beş gövdenin
hiçbiri kaynaktan silinmiş değil, beşi de bu koşumda hiç tetiklenmemiş. Bir sayının sıfır
olması, “bu kod gereksiz” demek değil; “bu koşumda bu yol seçilmedi” demek.
Varsayılan gövdenin kendisi de bunu görünür kılacak biçimde yazıldı: anahtar dizgesi
self.tip() çağrısının döndürdüğü değer bir kalıba yerleştirilerek kuruluyor, sabit bir
dizgeyle değil. Trait’in kendi gövdesi, kendisini çalıştıran tipin adını hiç
bilmiyor — self.tip() bir dize üretiyor ve gövde onu ödünç alıp anahtara ekliyor. Bu, tek
bir varsayılan gövdenin, onu miras alan her tip için ayrı bir satırda sayılabilmesini
sağlıyor; Sade ve Ozel aynı varsayılan gövdeyi paylaşsaydı bile (bu örnekte paylaşmıyorlar,
çünkü Ozel kendi gövdesini yazdı) iki ayrı anahtar altında ayrı ayrı sayılabilirlerdi. Bu
küçük tasarım kararı, çekirdeğin geri kalan derslerde neden kesit alınabilir olduğunu da
açıklıyor: her gövde kendi anahtarını taşıyor, bir kesit alındığında geri kalan anahtarlar
etkilenmiyor.
Özet
- Trait, tipten bağımsız bir davranış sözleşmesi kurar ve gerçekleştiren tip yazmazsa devreye giren bir varsayılan gövde taşıyabilir.
- Ölçüm çekirdeği on altı gövdeyi sınıflandırıyor: 8’i her seferinde, 3’ü yalnız gerektiğinde, 5’i hiç koşuyor; bu sayı kurs boyunca değişmiyor.
- Aynı varsayılan gövde bir tip için dört kez (her seferinde) koşarken, onu yeniden yazan bir tip için sıfır kez (hiç) koşuyor — gövde kaynakta aynı, koşum sayısı farklı.
- trait ile arayüz ayrı kavramlardır: arayüz Go’da bağın nerede kurulduğu ekseninde tanımlandı, trait burada açık bildirimle kurulan ve varsayılan gövde taşıyabilen bir sözleşmedir.
- Sıfır koşum, gövdenin kaynakta yok olduğu anlamına gelmez; o tip için o yola hiç uğranmadığı anlamına gelir.
Sonraki Adım
Trait’i gerçekleştiren bir tipin gövdesi ölçülebildi, ama bir gövdeyi genel bir fonksiyonun içinde kullanabilmek için o fonksiyonun tip parametresine bir sınır koymak gerekiyor. Sıradaki ders bu sınırın koşum sayısına hiç dokunup dokunmadığını sorar — aynı gövde sınırlı ve sınırsız iki biçimde yazılıp koşturularak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.