Ders 02 / 15
Trait Sınırları
Bir genel fonksiyonun tip parametresine bir trait sınırı koymak, kaynağa hiç adım eklemez — aynı gövde sınırlı ve sınırsız iki biçimde yazılıp koşturulduğunda ikisi de birebir aynı sayıyı verir; sınırın bedeli koşum defterine değil derlemeye yazılır ve bu sayıyla ölçülemez.
Yayın Güncelleme
Bu derste
- Trait sınırı sözdizimini (tekli, çoklu ve where yan tümcesi) tanımlar ve amacını açıklar
- Sınırlı bir genel gövde ile aynı işi yapan somut tipli bir gövdenin koşum sayısını karşılaştırarak sınırın koşumda adım eklemediğini gösterir
- Koşumda ölçülemeyen bir bedelin (her tip için ayrı üretilen gövde) var olduğunu belirtir ve sayıyla iddia etmekten kaçınır
İçindekiler
Önceki ders bir trait’in varsayılan gövde taşıyabildiğini ve bu gövdenin hangi tip için
çalıştığının koşumda sayılabildiğini gösterdi. Ama statik gibi bir fonksiyonun <T: Etiket>
yazımı henüz açıklanmadı — bu, bir genel fonksiyonun tip parametresine konan bir trait
sınırıdır ve bu dersin konusu bu sınırın kendisi. Soru aynı kalıyor: kaynakta yazdığın bu
gövde kaç kez koştu? Ama bu kez ölçülen gövde değil, gövdenin etrafına konan koşul.
Sınır Ne Yazıyor
Bir genel fonksiyon, fn ad<T>(x: &T) yazımıyla herhangi bir tipi kabul edebilir; ama gövde
içinde x üzerinde bir yöntem çağırmak istiyorsa, derleyicinin o yöntemin var olduğunu
bilmesi gerekir. Trait sınırı tam bunu sağlıyor: fn ad<T: Etiket>(x: &T) yazımı, “T ne
olursa olsun, Etiketi gerçekleştirmiş olmalı” diyor. Sınır konmadan x.defteri() çağrısı
derlenmez, çünkü derleyici T için hangi yöntemlerin var olduğunu bilmiyor; sınır konduktan
sonra derleyici bu bilgiyi trait bildiriminden okuyor.
Sınırsız bir genel fonksiyon, T hakkında hiçbir şey bilmiyor demektir. fn ad<T>(x: &T)
yazıldığında derleyici xe yalnız her tipte ortak olan işlemleri (taşımak, bir referans
almak) uygulamaya izin verir; x.defteri() gibi Etikete özgü bir çağrı yazılırsa derleyici
bunu reddeder, çünkü elindeki tek bilgi Tnin bir tip olduğudur, Etiketi
gerçekleştirdiği değil. Bu redd, önceki kurstaki tip anahtarının aksine burada bir çalışma
zamanı davranışı değil — kaynak hiç derlenmiyor. Trait sınırı tam bu boşluğu dolduruyor:
T: Etiket yazıldığı andan itibaren derleyici, gövdenin içinde Etiketin bildirdiği her
yöntemin x üzerinde çağrılabileceğini biliyor ve x.defteri() derlenir hale geliyor. Sınır
böylece iki tarafı aynı anda bağlıyor: çağıran tarafı (“bu fonksiyona yalnız Etiketi
gerçekleştiren tipler geçirilebilir”) ve gövde tarafını (“bu gövde Etiketin yöntemlerini
güvenle çağırabilir”).
Aynı sınır iki ayrı yerde yazılabilir. Kısa biçim tip parametresinin hemen yanına gelir:
fn sinirli_govde<T: Etiket>(x: &T). Sınır uzadığında (birden fazla trait, uzun tip adları)
aynı anlam bir where yan tümcesiyle de yazılabilir:
// where-bicimi.rs — ayni sinir, imzadan ayrilmis bicimde.
trait Etiket {
fn ad(&self) -> String;
}
struct Ornek;
impl Etiket for Ornek {
fn ad(&self) -> String {
String::from("ornek")
}
}
fn kisa<T: Etiket>(x: &T) -> String {
x.ad()
}
fn uzun<T>(x: &T) -> String
where
T: Etiket,
{
x.ad()
}
fn main() {
let o = Ornek;
println!("kisa={} uzun={}", kisa(&o), uzun(&o));
}
kisa=ornek uzun=ornek
TR3. kisa ve uzun derleyici için tamamen aynı anlama geliyor; where yan tümcesi bir
okunabilirlik seçeneği, ayrı bir mekanizma değil. Birden fazla sınır gerektiğinde
(T: Etiket + Clone gibi) kısa biçim satırı uzatırken, where biçimi her sınırı ayrı bir
satıra koyarak imzayı kısa tutabiliyor. Bu ders ikisini birbirinin yerine kullanıyor;
hangisinin seçildiği koşum sayısını hiç etkilemiyor.
Birden fazla sınır + işaretiyle birleştirilir: T: Etiket + Clone, “T hem Etiketi
gerçekleştirmeli hem de Cloneu” demektir. Bu, önceki derste tanıdık bir yapı: Etiketin
kendisi tek bir sözleşmeydi, burada iki sözleşme aynı tip parametresine birden bağlanıyor.
+ burada bir birleşim değil bir kesişim kuruyor: T yerine geçebilecek tipler kümesi,
yalnız Etiket gerçekleştirenlerin kümesiyle yalnız Clone gerçekleştirenlerin kümesinin
kesişimidir. Bir sınır eklemek, hangi tiplerin fonksiyona geçirilebileceğini her zaman
daraltır, hiçbir zaman genişletmez; bu yüzden bir imzaya yeni bir sınır eklemek geriye
dönük olarak bazı çağrıları kesebilir, ama hiçbir zaman yeni bir çağrıyı mümkün kılmaz.
Sınırlar, tip parametresinin nerede kısıtlandığını da değiştirmiyor: <T: Etiket> ile
where T: Etiket arasındaki tek fark yerleşimdir, kısıtın gücü değil. İki sınır bir arada
yazıldığında (T: Etiket + Clone) da aynı ilke geçerli: derleyici gövdeyi kontrol ederken
Tnin hem Etiketin hem Cloneun yöntemlerini taşıdığını varsayıyor; sınırlardan
biri eksik olsaydı, o trait’e ait tek bir çağrı bile gövdeyi derlenemez hale getirirdi.
Bir imza birden fazla tip parametresi taşıyabilir ve her parametrenin kendi sınırı olabilir:
fn birlestir<T: Etiket, U: Etiket>(a: &T, b: &U) yazımında T ile U aynı sınırı
paylaşıyor görünse de derleyici için bağımsız iki parametredir — a ile b farklı somut
tiplerde olabilir, tek ortak zorunluluk ikisinin de Etiketi gerçekleştirmiş olmasıdır.
Sınırlar parametre başına konur; bir parametrenin sınırı diğerini etkilemez, aynı trait adı
iki yerde tekrarlansa bile.
// birlestir.rs — iki tip parametresi, ayni trait sinirini bagimsiz tasiyor.
trait Etiket {
fn ad(&self) -> String;
}
struct Sade;
struct Ozel;
impl Etiket for Sade {
fn ad(&self) -> String {
String::from("sade")
}
}
impl Etiket for Ozel {
fn ad(&self) -> String {
String::from("ozel")
}
}
fn birlestir<T: Etiket, U: Etiket>(a: &T, b: &U) -> String {
format!("{}+{}", a.ad(), b.ad())
}
fn main() {
let s = Sade;
let o = Ozel;
println!("{}", birlestir(&s, &o));
println!("{}", birlestir(&s, &s));
}
sade+ozel sade+sade
İkinci çağrıda T ve U aynı somut tipe (Sade) çözülüyor; ilkinde ise ikisi farklı
somut tiplere (Sade ve Ozel) çözülüyor. birlestirin imzası her iki çağrıyı da kabul
ediyor, çünkü sınır yalnız “Etiketi gerçekleştirmiş olmalı” diyor, “aynı tip olmalı”
demiyor. T ile Unun aynı somut tipe çözülmesini zorunlu kılmak isteseydik imza tek bir
parametre kullanırdı (fn birlestir<T: Etiket>(a: &T, b: &T)); iki ayrı parametrenin
varlığı, tam olarak bu zorunluluğu kaldırmak için orada duruyor.
Trait sınırı yazılmadığında tip parametresi tamamen boş kalmıyor: her T, aksi
belirtilmedikçe örtük bir boyut sınırı da taşır — derleyici, Tnin bellekte sabit ve
bilinen bir boyutu olduğunu varsayar, çünkü genel bir gövde T tipinde bir değeri
yerelde tutabilmelidir. Bu örtük sınır, Etiket gibi açıkça yazılan sınırlardan ayrı bir
katmandır ve çoğu genel fonksiyon için hiç görünmez; yalnız dilim gibi boyutu değişken
tiplerle çalışan az sayıda ileri düzey yazımda açıkça gevşetilmesi gerekir. Bu ders o
gevşetmeyi kullanmıyor — burada önemli olan, sınırın görünen kısmının (Etiket) yanında
her zaman görünmeyen bir varsayılan sınırın da bulunduğunu bilmek.
Sınırın Kosumdaki Payı: Sıfır Adım
Aynı işi iki yazımla kurup koşum sayısını karşılaştıralım: biri <T: Etiket> sınırıyla genel,
öbürü doğrudan somut tip (Sade) alan, sınırsız bir fonksiyon. Üçüncü bir fonksiyon da
birden fazla sınırı (Etiket + Clone) birlikte kullanıyor.
// kere.rs — trait Etiket'in kesiti: `defteri`, tip adi ve iki gerceklestirim.
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
pub type Defter = Rc<RefCell<BTreeMap<String, u32>>>;
pub fn say(defter: &Defter, ad: &str) {
*defter.borrow_mut().entry(ad.to_string()).or_insert(0) += 1;
}
pub fn kere(defter: &Defter, ad: &str) -> u32 {
*defter.borrow().get(ad).unwrap_or(&0)
}
pub trait Etiket {
fn defteri(&self) -> &Defter;
fn tip(&self) -> &'static str;
}
#[derive(Clone)]
pub struct Sade {
pub defter: Defter,
}
impl Etiket for Sade {
fn defteri(&self) -> &Defter {
&self.defter
}
fn tip(&self) -> &'static str {
"sade"
}
}
pub struct Ozel {
pub defter: Defter,
}
impl Etiket for Ozel {
fn defteri(&self) -> &Defter {
&self.defter
}
fn tip(&self) -> &'static str {
"ozel"
}
}
// sinir-govdesi.rs — trait sinirinin kosumda hicbir adim eklemedigini olcer.
mod kere;
use kere::{kere, say, Defter, Etiket, Ozel, Sade};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
// Ayni kaynak satiri, cagrildigi her somut tip icin derleyici tarafindan
// ayrica uretiliyor - bu, kosum sayacinin gormedigi bir bedeldir.
fn sinirli_govde<T: Etiket>(x: &T) -> u32 {
let anahtar = format!("sinirli-govde-{}", x.tip());
say(x.defteri(), &anahtar);
kere(x.defteri(), &anahtar)
}
// Ayni is, sinirsiz: dogrudan somut tip aliyor, tip parametresi yok.
fn somut_govde(x: &Sade) -> u32 {
say(x.defteri(), "somut-govde");
kere(x.defteri(), "somut-govde")
}
// T hem Etiket'i hem Clone'u sagliyor olmali - iki sinir '+' ile birlesiyor.
fn kopyala_ve_say<T: Etiket + Clone>(x: &T) -> T {
say(x.defteri(), "coklu-sinir-govdesi");
x.clone()
}
fn main() {
let defter: Defter = Rc::new(RefCell::new(BTreeMap::new()));
let sade = Sade { defter: Rc::clone(&defter) };
let ozel = Ozel { defter: Rc::clone(&defter) };
let _ = sinirli_govde(&sade);
let _ = sinirli_govde(&sade);
let _ = sinirli_govde(&ozel);
let _ = somut_govde(&sade);
let _ = somut_govde(&sade);
let kopya = kopyala_ve_say(&sade);
let _ = kopyala_ve_say(&kopya);
let mut govdeler: BTreeMap<&str, u32> = BTreeMap::new();
govdeler.insert("sinirli-govde-sade", kere(&defter, "sinirli-govde-sade"));
govdeler.insert("sinirli-govde-ozel", kere(&defter, "sinirli-govde-ozel"));
govdeler.insert("somut-govde", kere(&defter, "somut-govde"));
govdeler.insert("coklu-sinir-govdesi", kere(&defter, "coklu-sinir-govdesi"));
println!("{:<22}{}", "govde", "kac kez");
for (ad, n) in &govdeler {
println!("{:<22}{}", ad, n);
}
}
govde kac kez coklu-sinir-govdesi 2 sinirli-govde-ozel 1 sinirli-govde-sade 2 somut-govde 2
TR4. sinirli-govde-sade iki kez koştu, somut-govde de iki kez — birebir aynı sayı.
İkisi de aynı işi (defteri okuyup bir satırı saymak) yapıyor; biri genel bir tip parametresi
üzerinden, öbürü doğrudan somut tip üzerinden. Trait sınırı, gövdenin kaç kez çağrıldığına
hiçbir şey eklemiyor ya da çıkarmıyor — bu, sınırın koşum defterinde hiçbir iz bırakmadığını
gösteriyor. Sınır bir derleme zamanı kuralıdır: hangi tiplerin sinirli_govdeye
geçirilebileceğini belirler, ama geçirildikten sonra çalışan kod, sınır hiç yokmuş gibi
davranır.
Arayüzler, Hatalar ve Genellikler kursu Go’nun genelliklerinde bağın kullanım yerinde
kurulduğunu ölçmüştü — bir genel fonksiyonun tip kısıtı, çağrının hangi tiplerle
yapılabileceğini kullanım yerinde sınırlıyordu. Bu kurs aynı soruyu sormuyor; burada ölçülen,
o sınırın koşuma ne eklediği — yanıt hiçbir şey. sinirli-govde-ozel satırı bunu ikinci
bir açıdan doğruluyor: aynı sinirli_govde fonksiyonu Ozel ile de çağrılabiliyor (bir kez),
çünkü Ozel de Etiketi gerçekleştiriyor; sınır, hangi tiplerin kabul edileceğini
belirliyor, ama kabul edilen her tip için gövde aynı biçimde çalışıyor.
coklu-sinir-govdesi de aynı sınıfa giriyor: iki kez çağrıldı, iki kez koştu. Burada
T: Etiket + Clone sınırı iki ayrı koşulu birleştiriyor, ama gövdenin kendisi yine tek bir
sayma çağrısı ve bir kopyalama işlemi. Sınırın sayısı arttıkça (Etiket, sonra
Etiket + Clone) koşum defterindeki satır sayısı artmıyor; sınır tip parametresine konan bir
koşuldur, gövdenin içine eklenen bir adım değildir.
Dört satırın tamamı her seferinde sınıfına giriyor: hiçbiri “yalnız gerektiğinde” ya da
“hiç” değil, çünkü her çağrı kaçınılmaz olarak karşılığını buluyor — atlanan, ertelenen ya da
tüketilmeyen bir adım yok. Bu, önceki dersteki varsayilan-govde-sade ve
yeniden-yazilan-govde satırlarıyla aynı sınıf: farklı olan yalnız gövdenin hangi koşulla
çağrılabildiği, gövdenin kendisinin çalışıp çalışmadığı değil.
Bu eşitlik, kursun ilerleyen bir dersinde farklı bir soruyla yeniden karşımıza çıkacak. Nesne
Güvenliği ve Dinamik Gönderim dersi, aynı Etiket sözleşmesini bu kez <T: Etiket> sınırı
yerine &dyn Etiket ile kullanan bir çağrı biçimiyle karşılaştıracak. Orada da sayı eşit
çıkıyor — ama nedeni burada görülenden farklı: sınırlı genellik çağrıyı derleme zamanında
çözerken, dyn biçimi aynı çağrıyı koşum sırasında bir yöntem tablosundan buluyor. Bu ders
yalnız sınırın varlığının koşuma bir şey eklemediğini gösteriyor; iki çağrı biçiminin
birbirinden nasıl ayrıldığı ayrı bir derste ele alınacak.
somut_govde fonksiyonunun hiç sınıra ihtiyacı olmaması da aynı ilkenin bir sonucu. Bir
fonksiyon zaten tek bir somut tipe (Sade) bağlandığında “hangi tipler kabul edilsin”
sorusunun yanıtı baştan bellidir — yalnız Sade. Trait sınırı, bu sorunun yanıtının
birden fazla tip olabildiği durumlar için var; tek tipe bağlı bir fonksiyonda sormaya
gerek bile yok. Bu yüzden somut_govde ile sinirli_govde arasındaki fark bir eksiklik
değil, iki farklı sorunun karşılığı: biri “bu iş için doğru tip hangisi” sorusunu hiç
sormuyor, öbürü bu soruyu sınırla yanıtlıyor.
Sınırlayıcı Ölçüm
Koşumda bedeli olmaması, sınırın bedelsiz olduğu anlamına gelmiyor. sinirli_govde
fonksiyonu Sade ve Ozel için ayrı ayrı çağrıldığında, derleyici bu fonksiyonun her somut
tip için ayrı bir gövde ürettiği bir yöntemle çalışıyor: kaynakta tek bir fn sinirli_govde<T: Etiket> satırı var, ama üretilen ikili dosyada Sade için bir gövde,
Ozel için başka bir gövde bulunuyor. Bu üretim derleme sırasında olur ve koşum defterine
hiç girmez — defter yalnız çalışma zamanındaki çağrıları sayıyor, derleme zamanında üretilen
gövde sayısını değil. Bu kursun ölçüm yöntemi bu bedeli gösteremiyor; ders bunu açıkça
söylüyor ve saymaya kalkmıyor. sinirli-govde-sade ile sinirli-govde-ozelin toplamda üç
kez koşması, arkada kaç ayrı gövdenin üretildiğiyle ilgili hiçbir bilgi taşımıyor.
Bu sınır, kursun ölçüm yönteminin genel bir sınırını da ortaya koyuyor: koşum defteri yalnız çalışma zamanında gerçekten geçilen satırları sayabiliyor. Derleme zamanında olan hiçbir şey — kaç ayrı gövde üretildiği, hangi tipin hangi sırayla derlendiği, bir sınırın kaç aday tipi elediği — bu defterle görünür kılınamaz. Kurs boyunca “sayı” sözcüğü hep çalışma zamanı sayısı anlamına geliyor; derleme zamanına ait bir soru sorulduğunda ders bunu açıkça söyleyip düzyazıyla yanıtlıyor, bir sayı uydurmuyor. Bu ayrım, ölçümün neyi kapsayıp neyi kapsamadığını baştan çizmek için gerekli: kursun geri kalanında “bu satır hiç koşmadı” cümlesi her zaman koşum zamanına ait bir gözlemdir, kaynağın var olup olmadığına dair bir iddia değildir.
Özet
- Trait sınırı, bir genel fonksiyonun tip parametresine “bu trait’i gerçekleştirmiş olmalı”
koşulunu koyar; kısa biçim (
<T: Etiket>) vewhereyan tümcesi derleyici için aynı anlama gelir. - Birden fazla sınır
+ile birleştirilir (T: Etiket + Clone); gövde tek kalır, sınır sayısı arttıkça çalışan kod artmaz. - Sınırlı bir gövde (
sinirli-govde-sade) ile aynı işi yapan sınırsız bir gövde (somut-govde) her seferinde aynı sayıda koştu: ikisi de iki kez — sınır koşum defterine hiçbir adım eklemiyor. - Sınırın gerçek bedeli koşumda değil derlemede: her somut tip için ayrı bir gövde üretiliyor, ama bu üretim koşum sayacıyla ölçülemez ve ders bunu ölçmeye kalkmıyor.
Sonraki Adım
Sınır, bir trait’i tip parametresine dıştan bağladı. Bir trait bazen sözleşmenin kendi içinde bir tip taşımak isteyebilir — çağıranın hiç belirtmediği, gerçekleştirenin belirlediği bir tip. Sıradaki ders bu ilişkili tipi, aynı sözleşmeyi bir de tip parametresiyle yazarak karşılaştırıyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.