İçeriğe geç
academia.sh

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>) ve where yan 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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat