ch10-02-traits
Özellikler: Paylaşılan Davranışı Tanımlama
Bir özellik, belirli bir türün sahip olduğu ve diğer türlerle paylaşabileceği işlevselliği tanımlar. Özellikleri, soyut bir şekilde paylaşılan davranışı tanımlamak için kullanabiliriz. Özellik sınırları kullanarak, bir generic türün belirli bir davranışı olan herhangi bir tür olabileceğini belirtebiliriz.
Not: Özellikler, bazı dillerde arayüzler olarak adlandırılan bir özellikle benzerdir, ancak bazı farklılıklar vardır.
Bir Özellik Tanımlama
Bir türün davranışı, o tür üzerinde çağırabileceğimiz metotlardan oluşur. Farklı türler, bu türlerin tümünde aynı metotları çağırabiliyorsak aynı davranışı paylaşır. Özellik tanımları, belirli bir amacı başarmak için gerekli olan bir dizi davranışı tanımlamak için metot imzalarını bir araya getirme yoludur.
Örneğin, çeşitli metin türleri ve miktarları tutan birden fazla yapı (struct) olduğunu varsayalım: belirli bir konumda dosyalanmış bir haber hikayesini tutan bir NewsArticle
yapısı ve en fazla 280 karakter içeren, yeni bir tweet, retweet veya başka bir tweete yanıt olup olmadığını gösteren meta veriler içerebilen bir Tweet
.
Bir NewsArticle
veya Tweet
örneğinde saklanabilecek verilerin özetlerini gösterebilen aggregator
adında bir medya agregatör kütüphanesi oluşturmak istiyoruz. Bunu yapmak için, her türden bir özet almamız gerekiyor ve bu özeti bir örnekte summarize
metodu çağrılarak talep edeceğiz. Liste 10-12, bu davranışı ifade eden genel bir Summary
özelliğinin tanımını gösterir.
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-12/src/lib.rs}}
Burada, trait
anahtar kelimesini kullanarak bir özellik tanımlıyoruz ve ardından bu özelliğin adı (bu durumda Summary
) gelmektedir. Ayrıca, bu özelliği pub
olarak tanımlayarak, bu crate'e bağımlı olan diğer crate'lerin de bu özelliği kullanabilmesini sağlarız; birkaç örnekte bunu göreceğiz. Süslü parantezlerin içinde, bu özelliği uygulayan türlerin davranışlarını tanımlayan metot imzalarını tanımlıyoruz; bu durumda fn summarize(&self) -> String
ifadesine sahibiz.
Metot imzasından sonra, süslü parantezler içinde bir uygulama sağlamak yerine bir noktalı virgül kullanıyoruz. Bu özelliği uygulayan her tür, metodun gövdesi için kendi özel davranışını sağlamalıdır. Derleyici, Summary
özelliğine sahip herhangi bir türün, bu imzayla summarize
metodunun tanımlanmış olacağını zorunlu kılacaktır.
Bir özellik, gövdesinde birden fazla metoda sahip olabilir: metot imzaları her bir satıra bir tane gelecek şekilde listelenir ve her satır bir noktalı virgül ile sona erer.
Bir Tür Üzerine Özellik Uygulama
Şimdi Summary
özelliğinin metotlarının istenen imzalarını tanımladıktan sonra, bunu medya agregatörümüzdeki türler üzerinde uygulayabiliriz. Liste 10-13, NewsArticle
yapısına Summary
özelliği uygulanmasını göstermektedir; bu yapı, başlık, yazar ve konum kullanarak summarize
dönüş değerini oluşturur. Tweet
yapısı için, summarize
yöntemini kullanıcı adı ve ardından tweetin tamamı olarak tanımlıyoruz; tweet içeriğinin zaten 280 karakterle sınırlı olduğunu varsayıyoruz.
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-13/src/lib.rs:here}}
Bir tür üzerinde bir özelliği uygulamak, normal metotları uygulamak gibidir. Tek fark, impl
ifadesinden sonra uygulamak istediğimiz özellik adını yazmamız sonra for
anahtar kelimesini kullanarak uygulamak istediğimiz türün adını belirtmemizdir. impl
bloğu içerisinde, özellik tanımında tanımlanan metot imzalarını ekliyoruz. Her bir imzanın sonuna noktalı virgül eklemek yerine, süslü parantezleri kullanarak metodun gövdesini uygulamak istediğimiz belirli davranışla dolduruyoruz.
Artık kütüphane, NewsArticle
ve Tweet
üzerinde Summary
özelliğini uyguladı, crate'in kullanıcıları NewsArticle
ve Tweet
örnekleri üzerinde özellik metotlarını, normal metotları çağırır gibi çağırabilir. Tek fark, kullanıcının türlerin yanı sıra özelliği de kapsam içine alması gerektiğidir. İşte bir binary crate'in aggregator
kütüphane crate'ini nasıl kullanabileceğine dair bir örnek:
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-01-calling-trait-method/src/main.rs}}
Bu kod, 1 new tweet: horse_ebooks: of course, as you probably already know, people
çıktısını verir.
aggregator
crate'ine bağımlı diğer crate'ler de kendi türlerine Summary
uygulamak için Summary
özelliğini kapsam içine alabilir. Not edilmesi gereken bir kısıtlama, bir özellik üzerinde bir türü yalnızca ya özellik ya da tür, ya da ikisi de crate'imize yerel olduğu sürece uygulayabileceğimizdir. Örneğin, Tweet
gibi bir özel tür olan Tweet
üzerinde Display
gibi standart kütüphane özelliklerini uygulayabiliriz, çünkü Tweet
türü aggregator
crate'imize yereldir. Ayrıca Vec
üzerinde Summary
uygulayabiliriz çünkü Summary
özelliği aggregator
crate'imize yereldir.
Ancak, dış özellikleri dış türler üzerinde uygulayamıyoruz. Örneğin, aggregator
crate'imizin içinde Vec
üzerinde Display
özelliğini uygulayamıyoruz çünkü Display
ve Vec
her ikisi de standart kütüphanede tanımlanmıştır ve aggregator
crate'imize yerel değildir. Bu kısıtlama, tutarlılık adı verilen bir özellikten ve daha spesifik olarak yetim kuralı adıyla bilinir; çünkü ana tür mevcut değildir. Bu kural, başkalarının kodunun sizin kodunuzu ve tersinin bozmamasını sağlar. Kural olmadan, iki crate aynı tür için aynı özelliği uygulayabilir ve Rust hangi uygulamanın kullanılacağını bilemezdi.
Varsayılan Uygulamalar
Bazen, her tür için tüm metotların uygulamalarını gerektirmek yerine, bir özelliğin bazı ya da tüm metotları için varsayılan bir davranışa sahip olmak faydalı olabilir. Daha sonra, belirli bir tür üzerinde özelliği uygularlarken, her bir metodun varsayılan davranışını koruyabilir ya da değiştirebiliriz.
Liste 10-14'te, Summary
özelliğinin summarize
metodu için varsayılan bir dize belirtiyoruz; bunu sadece yöntem imzasını tanımladığımız gibi, Liste 10-12'de yaptığımız gibi yapmıyoruz.
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-14/src/lib.rs:here}}
NewsArticle
örneklerini özetlemek için varsayılan bir uygulama kullanmak üzere, impl Summary for NewsArticle {}
ile boş bir impl
bloğu belirtiyoruz.
NewsArticle
üzerinde summarize
metodunu artık doğrudan tanımlamıyor olsak da, varsayılan bir uygulama sağladık ve NewsArticle
'ın Summary
özelliğini uyguladığını belirttik. Sonuç olarak, NewsArticle
örneği üzerinde summarize
metodunu hala çağırabiliriz, bu şekilde:
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-02-calling-default-impl/src/main.rs:here}}
Bu kod, New article available! (Read more...)
çıktısını verir.
Varsayılan bir uygulama oluşturmak, Liste 10-13'te Tweet
üzerinde Summary
uygulamasında değişiklik yapmamızı gerektirmez. Bunun nedeni, varsayılan bir uygulamayı değiştirmek için kullanılacak söz diziminin, varsayılan bir uygulama olmayan bir özellik metodu uygulamak için kullanılan söz dizimiyle aynı olmasıdır.
Varsayılan uygulamalar, aynı özellikteki diğer metotları çağırabilir; diğer metotların varsayılan uygulaması olmasa bile. Bu şekilde, bir özellik çok fazla yararlı işlev sağlayabilir ve yalnızca uygulayıcılardan küçük bir kısmını belirtmeleri istenir. Örneğin, summarize_author
adında bir metot tanımlayabiliriz; bu metot uygulanmak zorundadır ve sonra varsayılan bir uygulama, summarize_author
metodunu çağıran bir summarize
metodu tanımlanabilir:
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/lib.rs:here}}
Bu Summary
versiyonunu kullanmak için, tür üzerinde özelliği uygularken yalnızca summarize_author
'ı tanımlamamız gerekir:
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/lib.rs:impl}}
summarize_author
'ı tanımladıktan sonra, Tweet
yapısının örneklerinde summarize
'ı çağırabiliriz ve summarize
ın varsayılan uygulaması, sağladığımız summarize_author
tanımını çağırır. summarize_author
'ı uyguladığımız için, Summary
özelliği, bizim başka bir kod yazmamıza gerek kalmadan summarize
metodunun işlevselliğini sağlamıştır. İşte nasıl göründüğü:
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/main.rs:here}}
Bu kod, 1 new tweet: (Read more from @horse_ebooks...)
çıktısını verir.
Dikkat edilmesi gereken nokta, aynı metodun geçersiz kılınmış bir uygulamasından varsayılan uygulamanın çağrılmasının mümkün olmamasıdır.
Özellikleri Parametre Olarak Kullanma
Artık özellik tanımlamayı ve uygulamayı bildiğinize göre, birçok farklı türü kabul etmek için özellikleri nasıl kullanabileceğimizi keşfedebiliriz. İlgili fonksiyonların Summary
özelliğini NewsArticle
ve Tweet
türlerinde Liste 10-13'te uyguladığımızı kullanarak bir notify
fonksiyonu tanımlayacağız; bu fonksiyon, item
parametresinde summarize
metodunu çağırır ki bu parametre Summary
özelliğini uygulayan herhangi bir türdendir. Bunu yapmak için, impl Trait
sözdizimini kullanıyoruz:
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-04-traits-as-parameters/src/lib.rs:here}}
item
parametresinde belirli bir tür yerine, impl
anahtar kelimesini ve özellik adını belirtiriz. Bu parametre, belirtilen özelliği uygulayan herhangi bir türü kabul eder. notify
gövdesinde, item
üzerinde Summary
özelliğinden gelen her türlü metodunu çağırabiliriz; örneğin summarize
. notify
fonksiyonunu çağırabilir ve NewsArticle
veya Tweet
örneklerinden herhangi birini geçirebiliriz. Fonksiyonu herhangi bir başka türle çağıran kod, örneğin bir String
veya i32
, derlenmeyecek çünkü bu türler Summary
özelliğini uygulamamaktadır.
Özellik Sınırı Sözdizimi
impl Trait
sözdizimi, basit durumlar için işe yarar ancak aslında özellik sınırı olarak bilinen daha uzun bir biçim için sözdizimi şekeridir; şöyle görünmektedir:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
Bu daha uzun biçim, önceki bölümdeki örnekle eşdeğerdir ancak daha ayrıntılıdır. Özellik sınırlarını bir tür parametresinin tanımıyla üst üste koyarız; bir iki nokta (:) ve açılı parantezler ().
impl Trait
sözdizimi, basit durumlar için uygun ve daha özlü kod yazmak için kullanılabilirken, daha kompleks durumlar için daha kapsamlı özellik sınırı sözdizimi kullanılabilir. Örneğin, iki parametrenin Summary
uygulamasını sağlaması mümkündür. impl Trait
sözdizimiyle bunu şöyle gerçekleştiririz:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
item1
ve item2
'nin farklı türlere sahip olmasına izin verecek şekilde bu fonksiyonu yazmak için impl Trait
kullanışlıdır (Her iki tür de Summary
uyguladığı sürece). Ancak, her iki parametrenin de aynı türde olmasını istiyorsak, o zaman bir özellik sınırı kullanmalıyız; aşağıdaki gibi:
pub fn notify<T: Summary>(item1: &T, item2: &T) {
item1
ve item2
parametrelerinin türü olarak belirtilen generic tür T
, fonksiyonu, item1
ve item2
için geçirilen argümanın somut türünün aynı olması gerektiğini kısıtlar.
+
Sözdizimi ile Birden Fazla Özellik Sınırı Belirleme
Birden fazla özellik sınırını da belirleyebiliriz. notify
fonksiyonunun summarize
ile birlikte görünüm formatlaması kullandığını varsayalım; notify
tanımında item
'in hem Display
hem de Summary
uygulamasını sağlaması gerektiğini belirtiriz. Bunu +
sözdizimi kullanarak yapabiliriz:
pub fn notify(item: &(impl Summary + Display)) {
+
sözdizimi, generic türler üzerindeki özellik sınırları için de geçerlidir:
pub fn notify<T: Summary + Display>(item: &T) {
İki özellik sınırı belirtildiğinde, notify
gövdesinde summarize
çağırabilir ve item
'i {}
ile biçimlendirebiliriz.
where
Kapsamıyla Daha Net Özellik Sınırları
Çok fazla özellik sınırını kullanmanın dezavantajları vardır. Her generic'in kendi özellik sınırları vardır; bu nedenle birden fazla generic tür parametreli fonksiyonlar, fonksiyon adının ve parametre listesinin arasında çok fazla özellik sınır bilgisi içerebilir, bu da fonksiyon imzasını zor okunur hale getirebilir. Bu nedenle, Rust, fonksiyon imzasından sonra where
ifadesini kullanarak, özellik sınırlarını belirtmek için alternatif bir sözdizimi sunar. Yani, şunu yazmak yerine:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
şu şekilde where
ifadesi kullanabiliriz:
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-07-where-clause/src/lib.rs:here}}
Bu fonksiyonun imzası daha derli toplu hale gelir: fonksiyon adı, parametre listesi ve dönüş türü daha yakındır, çok sayıda özellik sınırı olan bir fonksiyondan daha sadedir.
Özellikleri Uygulayan Türler Döndürme
impl Trait
sözdizimini dönüş pozisyonunda, belirli bir özelliği uygulayan bazı türden bir değeri döndürmek için de kullanabiliriz; şunu şöyle gösteririz:
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-05-returning-impl-trait/src/lib.rs:here}}
Dönüş türü için impl Summary
kullanarak, returns_summarizable
fonksiyonunun somut türünü adlandırmadan, Summary
özelliğini uygulayan bazı türlerin döndürüleceğini belirtiyoruz. Bu durumda, returns_summarizable
bir Tweet
döndürür ancak bu fonksiyonu çağıran kod bununla ilgili bilgiye sahip olmamalıdır.
Bir dönüş türünü sadece somut türü türettiği özellik ile belirtme yeteneği, özellikle kapalı ifadeler ve iteratörler bağlamında faydalıdır; ki bunları 13. Bölümde ele alıyoruz. Kapalı ifadeler ve iteratörler, yalnızca derleyici tarafından bilinen veya belirtmesi çok uzun türler oluşturur. impl Trait
sözdizimi, bir fonksiyonun, çok uzun türleri yazmadan, Iterator
özelliğini uygulayan bir türü döndürdüğünü belirtmek için kısa bir yol sağlar.
Ancak, impl Trait
yalnızca bir tür döndürüyorsanız kullanılabilir. Örneğin, dönüş türü olarak impl Summary
belirtilmiş bir kod, bir NewsArticle
veya bir Tweet
döndürmeye çalıştığında çalışmaz:
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-06-impl-trait-returns-one-type/src/lib.rs:here}}
Bir NewsArticle
veya Tweet
döndüren bir fonksiyon yazılmasına izin verilmemektedir; bunun nedeni, impl Trait
sözdiziminin derleyicide nasıl uygulandığı ile ilgilidir. Bu davranışı sağlayacak bir fonksiyon yazmayı, 18. Bölümde "Farklı Türlerde Değerler için Özellik Nesneleri Kullanma" bölümünde ele alacağız.
Özellik Sınırlarını Koşullu Olarak Metotları Uygulamak için Kullanma
Generic tür parametrelerini kullanan impl
bloğunda bir özellik sınırı kullanarak, belirtilen özellikleri uygulayan türler için metotları koşullu olarak uygulayabiliriz. Örneğin, Liste 10-15'te, Pair
türü her zaman Pair
'nin yeni bir örneğini döndüren new
fonksiyonunu uygular. Ancak sonraki impl
bloğunda, Pair
yalnızca iç türü T
PartialOrd
özelliğini uyguladığı ve Display
özelliğini uyguladığı durumda cmp_display
metodunu uygular.
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-15/src/lib.rs}}
Bir türün başka bir özelliği uyguladığı durumlarda, özellik sağlama konusunda da koşullu uyarlama yapabiliriz. Herhangi bir tür üzerinde herhangi bir özelliği uygulamanın implementasyonuna Blanket Implementations denir ve Rust standart kütüphanesinde yaygın şekilde kullanılır. Örneğin, standart kütüphane, Display
özelliğini uygulayan herhangi bir tür üzerinde ToString
özelliğini uygular. Standart kütüphanedeki impl
bloğu bu koda benzer görünmektedir:
impl<T: Display> ToString for T {
// --snip--
}
Standart kütüphanesinin bu blanket uygulaması sayesinde, Display
özelliğini uygulayan herhangi bir tür üzerinde ToString
özelliğinden tanımlanan to_string
metodunu çağırabiliriz. Örneğin, tam sayıların karşılık gelen String
değerlerine dönüşmesi şöyle olabilir çünkü tam sayılar Display
uygular:
let s = 3.to_string();
Blanket uygulama, bir özelliğin "Uygulayıcılar" kısmında belgelerinde yer alır.
Özellikler ve özellik sınırları, kodumuzu tekrarı azaltacak şekilde generic tür parametreleri kullanma imkanı sunar, ancak aynı zamanda derleyiciye generic türün belirli bir davranışa sahip olmasını istediğimizi belirtme yeteneği verir. Derleyici, bu tür sınırları bilgilerini kullanarak, kodumuzla kullanılan somut türlerin tümünün doğru davranışı sağladığını kontrol edebilir. Dinamik olarak türlenen dillerde, bir metodu tanımlamayan bir tür üzerinde bir metot çağırdığımızda, çalışma zamanında bir hata alırız. Ancak Rust, bu hataları derleme zamanına taşır, böylelikle kodumuz çalışmadan önce sorunları düzeltmek zorundayız. Ayrıca, çalışma zamanında davranışı kontrol edecek kod yazmak zorunda kalmadan, derleme zamanında zaten kontrol ettiğimiz için performansı artırırız ve generiklerin sağladığı esneklikten de vazgeçmemiş oluruz.