Tanımlar: Paylaşılan Davranışı Tanımlama
Bir tanım, belirli bir türün sahip olduğu ve diğer türlerle paylaşabileceği işlevselliği tanımlar. Paylaşılan davranışı soyut bir şekilde tanımlamak için tanımları kullanabiliriz. Genel bir türün belirli davranışlara sahip herhangi bir tür olabileceğini belirtmek için tanım sınırlarını kullanabiliriz.
Not: Tanımlar, bazı farklılıklara rağmen diğer dillerde genellikle arayüz olarak adlandırılan bir özelliğe benzer.
Tanım Tanımlama
Bir türün davranışı, o tür üzerinde çağırabileceğimiz metodlardan oluşur. Tüm türler üzerinde aynı metodları çağırabiliyorsak, farklı tipler aynı davranışı paylaşır. Tanım tanımları, bir amacı gerçekleştirmek için gerekli olan bir dizi davranışı tanımlamak üzere metod imzalarını bir araya getirmenin bir yoludur.
Örneğin, çeşitli tür ve miktarlarda metin tutan birden fazla yapımız olduğunu varsayalım: belirli bir konumda dosyalanmış bir haberi tutan bir NewsArticle yapısı ve yeni bir tweet mi, retweet mi yoksa başka bir tweet'e yanıt mı olduğunu gösteren meta verilerle birlikte en fazla 280 karaktere sahip olabilen bir Tweet.
Bir NewsArticle veya Tweet örneğinde saklanabilecek verilerin özetlerini görüntüleyebilen aggregator adlı bir
medya toplayıcı kütüphane kasası yapmak istiyoruz. Bunu yapmak için, her türden bir özete ihtiyacımız var ve bir
örnek üzerinde bir summarize yöntemi çağırarak bu özeti talep edeceğiz. Liste 10-12, bu davranışı ifade eden bir genel
Summary tanımının tanımlanmasını göstermektedir.
Dosya adı: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String;
}
Liste 10-12: summarize metoduyla sağlanan davranıştan oluşan bir Summary tanımı
Burada, trait anahtar sözcüğünü ve ardından bu durumda Summary olan trait'in adını kullanarak bir trait bildiriyoruz.
Ayrıca, birkaç örnekte göreceğimiz gibi, bu kasaya bağlı kasaların da bu tanımı kullanabilmesi için tanımı pub olarak bildirdik.
Süslü parantezlerin içinde, bu tanımı uygulayan türlerin davranışlarını tanımlayan metod imzalarını bildiriyoruz;
bu durumda fn summarize(&self) -> String olacaktır.
Metod imzasından sonra, süslü parantezler içinde bir sürekleme sağlamak yerine noktalı virgül kullanırız.
Bu tanımı uygulayan her tür, metodun gövdesi için kendi özel davranışını sağlamalıdır. Derleyici, Summary tanımına sahip
herhangi bir türün summarize metodunun tam olarak bu imza ile tanımlanmasını zorunlu kılacaktır.
Bir tanımın gövdesinde birden fazla metod olabilir: metod imzaları her satırda bir tane listelenir ve her satır noktalı virgülle biter.
Tür Üzerinde Tanım Uygulama
Summary tanımının metodlarının istenen imzalarını tanımladığımıza göre, bunu medya toplayıcımızdaki türlere uygulayabiliriz.
Liste 10-13, Summary tanımının NewsArticle yapısı üzerinde, summarize'ın dönüş değerini oluşturmak için başlığı,
yazarı ve konumu kullanan bir süreklemesini göstermektedir. Tweet yapısı için, tweet içeriğinin zaten 280 karakterle sınırlı olduğunu
varsayarak, summarize özelliğini kullanıcı adı ve ardından tweet metninin tamamı olarak tanımlarız.
Dosya adı: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
Liste 10-13: NewsArticle ve Tweet türlerine Summary tanımının süreklenmesi
Bir tür üzerinde bir tanım süreklemek, normal metodları süreklemeye benzer. Aradaki fark, impl'den sonra süreklemek istediğimiz
özellik adını koymamız, ardından for anahtar sözcüğünü kullanmamız ve ardından tanımı uygulamak istediğimiz türün adını belirtmemizdir.
impl bloğunun içine, tanım tanımının tanımladığı metod imzalarını koyarız. Her imzadan sonra noktalı virgül eklemek yerine,
süslü parantezler kullanırız ve metod gövdesini, tanımın metodlarının belirli bir tür için sahip olmasını istediğimiz belirli
davranışla doldururuz.
Artık kütüphane NewsArticle ve Tweet üzerinde Summary tanımını süreklediğine göre, kasa kullanıcıları NewsArticle ve
Tweet örnekleri üzerindeki tanım metodlarını normal metodları çağırdığımız şekilde çağırabilir. Tek fark, kullanıcının türlerin
yanı sıra tanımı da kapsam içine alması gerektiğidir. İşte ikili bir kasanın aggregator kütüphane kasamızı nasıl kullanabileceğine dair
bir örnek:
use aggregator::{Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
Bu kod 1 new tweet: horse_ebooks: of course, as you probably already know, people yazdırır.
aggregator kasamıza bağımlı olan diğer kasalar da Summary tanımını kendi türlerinde süreklemek için kapsam içine alabilir.
Unutulmaması gereken bir kısıtlama, bir özelliği bir tür üzerinde yalnızca tanım veya türden en az birinin kasamız için yerel olması
durumunda uygulayabileceğimizdir. Örneğin, Tweet türü aggregator kasamız için yerel olduğundan, Display gibi standart kütüphane
özelliklerini Tweet gibi gizli bir tür üzerinde kasa işlevselliğimizin bir parçası olarak sürekleyebiliriz.
Ayrıca Summary tanımını Vec<T> üzerinde de sürekleyebiliriz, çünkü Summary tanımı kasamız için yereldir.
Ancak harici tanımları harici türler üzerinde uygulayamayız. Örneğin, Display tanımını Vec<T> üzerinde; kasamızda sürekleyemeyiz,
çünkü Display ve Vec<T> standart kütüphanede tanımlanmıştır ve kasamız için yerel değildir. Bu kısıtlama, tutarlılık adı verilen bir
özelliğin ve daha spesifik olarak, ana tür mevcut olmadığı için bu şekilde adlandırılan yetim kuralının bir parçasıdır.
Bu kural, başkalarının kodunun sizin kodunuzu bozamamasını ve bunun tersinin de geçerli olmamasını sağlar.
Bu kural olmasaydı, iki kasa aynı tür için aynı tanımı sürekleyebilirdi ve Rust hangi süreklemeyi kullanacağını bilemezdi.
Varsayılan Süreklemeler
Bazen, her türdeki tüm metodlar için sürekleme gerektirmek yerine, bir tanımdaki metodlardan bazıları veya tümü için varsayılan davranışa sahip olmak yararlıdır. Daha sonra, tanımı belirli bir tür üzerinde süreklerken, her bir metodun varsayılan davranışını koruyabilir veya geçersiz kılabiliriz.
Liste 10-14'te, Liste 10-12'de yaptığımız gibi yalnızca metod imzasını tanımlamak yerine Summary tanımının summarize metodu için
varsayılan bir dizgi belirtiyoruz.
Dosya adı: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
Liste 10-14: Summarize metodunun varsayılan süreklemesiyle bir Summary tanımının yazılması
NewsArticle örneklerini özetlemek üzere varsayılan bir sürekleme kullanmak istediğimiz için,
impl Summary for NewsArticle {} ile boş bir impl bloğu belirtiriz.
Artık NewsArticle üzerinde summarize metodunu doğrudan tanımlamıyor olsak da,
varsayılan bir sürekleme sağladık ve NewsArticle'ın Summary tanımını süreklediğini belirttik.
Sonuç olarak, bir NewsArticle örneği üzerinde summarize metodunu aşağıdaki gibi çağırabiliriz:
use chapter10::{self, NewsArticle, Summary};
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
};
println!("New article available! {}", article.summarize());
}
Bu kod New article available! (Read more...) çıktısını verir.
Varsayılan bir sürekleme oluşturmak, Liste 10-13'teki Tweet'deki Summary süreklemesinde herhangi bir değişiklik yapmamızı
gerektirmez. Bunun nedeni, varsayılan bir süreklemeyi geçersiz kılma söz diziminin, varsayılan bir süreklemeye sahip olmayan
bir tanım metodunu sürekleme söz dizimiyle aynı olmasıdır.
Varsayılan süreklemeler, diğer metodların varsayılan bir süreklemesi olmasa bile aynı tanımdaki diğer metodları çağırabilir.
Bu şekilde, bir özellik çok sayıda yararlı işlevsellik sağlayabilir ve uygulayıcıların bunun yalnızca küçük bir bölümünü
belirtmesini gerektirebilir. Örneğin, Summary tanımını, süreklenmesi gerekli olan bir summarize_author metoduna sahip olacak
şekilde tanımlayabilir ve ardından summarize_author metodunu çağıran varsayılan bir süreklemeye sahip bir summarize metodu tanımlayabiliriz:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
Summary'in bu sürümünü kullanmak için, özelliği bir türe süreklediğimizde yalnızca summary_author'u tanımlamamız gerekir:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
summarize_author'ı tanımladıktan sonra, Tweet yapısının örnekleri üzerinde summarize'ı çağırabiliriz ve
summarize'ın varsayılan süreklemesi, sağladığımız summarize_author tanımını çağıracaktır.
summarize_author tanımını süreklediğimiz için, Summary tanımı bize daha fazla kod yazmamızı gerektirmeden
summarize metodunun davranışını vermiştir.
use chapter10::{self, Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
Bu kod 1 new tweet: (Read more from @horse_ebooks...) çıktısını verecektir.
Aynı metodun geçersiz kılınan bir süreklemesinden varsayılan süreklemeyi çağırmanın mümkün olmadığını unutmayın.
Parametre olarak Tanımlar
Artık tanımları nasıl tanımlayacağınızı ve sürekleyeceğinizi bildiğinize göre,
birçok farklı türü kabul eden fonksiyonları tanımlamak için tanımları nasıl kullanacağımızı keşfedebiliriz.
Liste 10-13'te NewsArticle ve Tweet türleri üzerinde süreklediğimiz Summary tanımını, Summary tanımını sürekleyen bir
türden olan item parametresi üzerinde summarize metodunu çağıran notify fonksiyonunu tanımlamak için kullanacağız.
Bunu yapmak için, aşağıdaki gibi impl Trait söz dizimini kullanırız:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
Öğe parametresi için somut bir tür yerine,
impl anahtar sözcüğünü ve tanım adını belirtiriz. Bu parametre, belirtilen tanımı uygulayan herhangi bir
türü kabul eder. notify'ın gövdesinde, item üzerinde Summary tanımından gelen summarize gibi herhangi bir metodu çağırabiliriz.
notify'ı çağırabilir ve NewsArticle veya Tweet'in herhangi bir örneğini aktarabiliriz. Fonksiyonu String veya
i32 gibi başka bir türle çağıran kod derlenmez çünkü bu türler Summary tanımını uygulamaz.
Tanıma Bağlılık Söz Dizimi
impl Trait söz dizimi basit durumlar için çalışır, ancak aslında tanıma bağlılık olarak bilinen daha uzun bir form için
söz dizimi tatlılığıdır; şöyle görünür:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
Bu uzun form önceki bölümdeki örneğe eş değerdir ancak daha ayrıntılıdır. Tanıma bağlılık, iki nokta üst üste işaretinden sonra ve köşeli parantezler içinde yaygın tür parametresinin bildirimiyle birlikte yerleştiririz.
impl Trait söz dizimi kullanışlıdır ve basit durumlarda daha özlü bir kod sağlarken, tanıma bağlılık söz dizimi
karmaşık durumlarda daha farklı bir şekilde kendini ifade edebilir. Örneğin, Summary öğesini uygulayan iki parametreye sahip olabiliriz.
Bunu impl Trait sö zdizimi ile yapmak şu şekilde görünür:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
Bu fonksiyonun item1 ve item2'nin farklı türlere sahip olmasına izin vermesini istiyorsak
(her iki tür de Summary'yi süreklediği sürece) impl Trait kullanmak uygundur.
Ancak her iki parametrenin de aynı türde olmasını istiyorsak, aşağıdaki gibi, tanıma bağlılığı kullanmalıyız:
pub fn notify<T: Summary>(item1: &T, item2: &T) {
item1 ve item2 parametrelerinin türü olarak belirtilen T yaygın türü; fonksiyonu, item1 ve item2 için argüman olarak
aktarılan değerin somut türünün aynı olması gerektiği şekilde kısıtlar.
+ Söz Dizimi ile Birden Fazla Tanıma Bağlılığın Belirtilmesi
Birden fazla tanıma bağlılığı da belirleyebiliriz. Diyelim ki notify'ın öğe üzerinde özetlemenin yanı sıra görüntüleme
biçimlendirmesini de kullanmasını istedik: notify tanımında öğenin hem Display hem de Summary'i süreklemesi gerektiğini belirtiriz.
Bunu + söz dizimini kullanarak da yapabiliriz:
pub fn notify(item: &(impl Summary + Display)) {
+ söz dizimi, yaygın türlerdeki tanıma bağlılıkta da geçerlidir:
pub fn notify<T: Summary + Display>(item: &T) {
Belirtilen iki tanıma bağlılık ile, notify öğesinin gövdesi summarize'ı çağırabilir ve item'ı
biçimlendirmek için {} kullanabilir.
where ile Daha Net Tanıma Bağlılık
Çok fazla tanıma bağlılık kullanmanın dezavantajları vardır. Her yaygın kendine özgü tanıma bağlılığa sahiptir,
bu nedenle birden fazla yaygın tür parametresi olan fonksiyonlar, fonksiyonun adı ve parametre listesi arasında
çok sayıda tanıma bağlılık bilgisi içerebilir ve bu da fonksiyon imzasının okunmasını zorlaştırır.
Bu nedenle Rust, fonksiyon imzasından sonra where içinde tanıma bağlılığı belirtmek için alternatif bir söz dizime sahiptir.
Yani bunu yazmak yerine:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
where kullanabiliriz:
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
Bu fonksiyonun imzası daha az karmaşıktır: fonksiyon adı, parametre listesi ve dönüş türü birbirine yakındır, çok sayıda tamıma bağlılığı olmayan bir fonksiyona benzer.
Tanımları Sürekleyen Dönüş Türleri
Burada gösterildiği gibi, bir tanımı sürekleyen bir türden bir değer döndürmek için return konumunda
impl Trait söz dizimini de kullanabiliriz:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
Dönüş türü için impl Summary kullanarak, returns_summarizable fonksiyonunun somut türü belirtmeden Summary
tanımını sürekleyen bir türü döndürdüğünü belirtiyoruz. Bu durumda, returns_summarizable Tweet döndürür,
ancak bu fonksiyonu çağıran kodun bunu bilmesine gerek yoktur.
Bir geri dönüş türünü yalnızca uyguladığı özelliğe göre belirtme yeteneği, özellikle Bölüm 13'te ele aldığımız kapanış ifadeleri ve
yineleyiciler bağlamında kullanışlıdır. Kapanış ifadeleri ve yineleyiciler yalnızca derleyicinin bildiği türler veya
belirtilmesi çok uzun olan türler oluşturur. impl Trait söz dizimi, çok uzun bir tür yazmanıza gerek kalmadan bir fonksiyonun
Iterator tanımını sürekleyen bir tür döndürdüğünü kısaca belirtmenizi sağlar.
Ancak, impl Trait'i yalnızca tek bir tür döndürüyorsanız kullanabilirsiniz. Örneğin, dönüş türü impl Summary olarak belirtilen
bir NewsArticle veya Tweet döndüren bu kod çalışmaz:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
NewsArticle {
headline: String::from(
"Penguins win the Stanley Cup Championship!",
),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
}
} else {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
}
Derleyicide impl Trait söz diziminin nasıl süreklendiğine ilişkin kısıtlamalar nedeniyle NewsArticle veya Tweet döndürülmesine
izin verilmez. Bu davranışa sahip bir fonksiyonun nasıl yazılacağını Bölüm 17'deki
“Farklı Türlerde Değerlere İzin Veren Tanım Nesnelerini Kullanma”
kısmında ele alacağız.
Tanıma Bağlılık ile largest Fonksiyonunu Düzeltme
Artık yaygın tür parametresinin bağlılıklarını kullanarak istediğiniz davranışı nasıl belirleyeceğinizi bildiğinize göre,
yaygın tür parametresi kullanan largest fonksiyonunun tanımını düzeltmek için Liste 10-5'e dönelim! Bu kodu en son çalıştırmayı
denediğimizde bu hatayı almıştık:
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `T`
--> src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10` due to previous error
largest'in gövdesinde, daha büyük (>) operatörünü kullanarak T türündeki iki değeri karşılaştırmak istedik.
Bu operatör standart kütüphane özelliği std::cmp::PartialOrd üzerinde varsayılan bir metod olarak tanımlandığından,
T'ye tanıma bağlılıktan dolayı PartialOrd'u süreklememiz gerekir, böylece largest fonksiyonu karşılaştırabileceğimiz
herhangi bir türden dilimler üzerinde çalışabilir. PartialOrd'u kapsam içine almamıza gerek yok çünkü o zaten kapsama otomatik
olarak dahildir... Yani, largest'in imzasını aşağıdaki gibi değiştirin:
fn largest<T: PartialOrd>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
Bu sefer kodu derlediğimizde farklı hatalar alıyoruz:
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0508]: cannot move out of type `[T]`, a non-copy slice
--> src/main.rs:2:23
|
2 | let mut largest = list[0];
| ^^^^^^^
| |
| cannot move out of here
| move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
| help: consider borrowing here: `&list[0]`
error[E0507]: cannot move out of a shared reference
--> src/main.rs:4:18
|
4 | for &item in list {
| ----- ^^^^
| ||
| |data moved here
| |move occurs because `item` has type `T`, which does not implement the `Copy` trait
| help: consider removing the `&`: `item`
Some errors have detailed explanations: E0507, E0508.
For more information about an error, try `rustc --explain E0507`.
error: could not compile `chapter10` due to 2 previous errors
Buranın en önemli noktası, kopyalanmamış bir dilim olan [T] türünün dışına çıkamaz (cannot move out of type [T], a non-copy slice) hatasıdır.
largest fonksiyonunun yaygın olmayan versiyonlarında, yalnızca en büyük i32 veya char'ı bulmaya çalışıyorduk.
Bkz. “Sadece Yığıt Kullanan Veriler: Kopyalama” konusuna bakın, i32 ve char gibi bilinen bir
boyuta sahip türler yığıtta saklanabilir, böylece Copy tanımı süreklenebilir. Ancak, largest fonksiyonunu genelleştirdiğimizde,
list parametresinin içinde Copy tanımını süreklemeyen türler olması mümkün hale geldi. Sonuç olarak,
değeri list[0]'ten largest değişkenine taşıyamayız, bu da bu hataya neden olur.
Bu kodu yalnızca Copy tanımını sürekleyen türlerle birlikte çağırmak için, T'nin tanıma bağlılık listesine Copy'i ekleyebiliriz!
Liste 10-15, fonksiyona aktardığımız dilimdeki değerlerin türleri i32 ve char gibi PartialOrd ve Copy tanımlarını süreklediği sürece
derlenecek yaygın largest fonksiyonunun tam kodunu gösterir.
Dosya adı: src/main.rs
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T { let mut largest = list[0]; for &item in list { if item > largest { largest = item; } } largest } fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!("The largest number is {}", result); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest(&char_list); println!("The largest char is {}", result); }
Liste 10-15: PartialOrd ve Copy tanımlarını sürekleyen herhangi bir yaygın tür
üzerinde çalışan largest fonksiyonunun düzgün çalışan versiyonu
largest fonksiyonunu Copy tanımını sürekleyen türlerle sınırlamak istemiyorsak,
T'nin Copy yerine Clone tanımına sahip olduğunu belirtebiliriz. Böylece, largest fonksiyonunun sahibi olmasını istediğimizde
dilimdeki her değeri klonlayabiliriz. Clone fonksiyonunu kullanmak, String gibi yığın verisine sahip türler söz konusu olduğunda
potansiyel olarak daha fazla yığın tahsisi yapacağımız anlamına gelir ve büyük miktarda veriyle çalışıyorsak yığın tahsisleri yavaş olabilir.
Ayrıca, fonksiyonun dilimdeki bir T değerine bir referans döndürmesini sağlayarak largest'ı sürekleyebiliriz.
Dönüş türünü T yerine &T olarak değiştirirsek, böylece fonksiyonun gövdesini bir referans döndürecek şekilde değiştirirsek,
Clone veya Copy tanıma bağlılıklarına ihtiyacımız olmaz ve yığın tahsisatlarından kaçınabiliriz.
Bu alternatif çözümleri kendi başınıza yazmayı deneyin! Yaşam süreleri ile ilgili hatalara takılırsanız,
okumaya devam edin: “Yaşam Süreleri ile Referansları Doğrulama” bölümü durumu açıklayacaktır,
ancak bu meydan okumaları çözmek için yaşam süreleri gerekli değildir.
Metodları Koşullu Olarak Süreklemek için Tanıma Bağlılığı Kullanma
Yaygın tür parametreleri kullanan bir impl bloğu ile bağlı bir tanım kullanarak,
belirtilen tanımları sürekleyen türler için metodları koşullu olarak sürekleyebiliriz. Örneğin, Liste 10-16'daki Pair<T> türü her
zaman yeni bir Pair<T> örneği döndürmek için new fonksiyonunu çağırır (Bölüm 5'teki “Metodları Tanımlama”
bölümünden Self'in impl bloğunun türü için bir tür takma ad olduğunu hatırlayın, bu durumda Pair<T>'dir).
Ancak bir sonraki impl bloğunda, Pair<T> yalnızca iç tipi T, karşılaştırmayı sağlayan PartialOrd tanımını ve
yazdırmayı sağlayan Display tanımını süreklerse cmp_display metodunu sürekleyebilir.
Dosya adı: src/lib.rs
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
Liste 10-16: Tanıma bağlılığa bağlı olarak metodları yaygın bir tür üzerinde koşullu , olarak süreklemek
Ayrıca, başka bir tanımı sürekleyen herhangi bir tür için bir tanımı koşullu olarak sürekleyebiliriz.
Bir tanımın, tanıma bağlılığı karşılayan herhangi bir tür üzerindeki süreklemelerine
kapsamlı sürekleme denir ve Rust standart kütüphanesinde yaygın olarak kullanılır.
Örneğin, standart kütüphane Display tanımını sürekleyen herhangi bir tür üzerinde ToString tanımını sürekler.
Standart kütüphanedeki impl bloğu bu koda benzer:
impl<T: Display> ToString for T {
// --snip--
}
Standart kütüphane bu kapsamlı süreklemeye sahip olduğundan, Display tanımını sürekleyen herhangi bir tür üzerinde
ToString tanımı tarafından tanımlanan to_string metodunu çağırabiliriz. Örneğin, tam sayılar Display tanımını
süreklediği için tam sayıları karşılık gelen String değerlerine dönüştürebiliriz:
#![allow(unused)] fn main() { let s = 3.to_string(); }
Kapsamlı süreklemeler, tanımın dokümantasyonunun “Implementors” bölümünde görünür.
Tanımlar ve tanıma bağlılık, yinelemeyi azaltmak için yaygın tür parametrelerini kullanan kod yazmamıza ve aynı zamanda derleyiciye yaygın türün belirli bir davranışa sahip olmasını istediğimizi belirtmemize olanak tanır. Derleyici daha sonra kodumuzla birlikte kullanılan tüm somut tiplerin doğru davranışı sağlayıp sağlamadığını kontrol etmek için tanıma bağlılık bilgisini kullanabilir. Dinamik olarak yazılan dillerde, metodu tanımlamayan bir tür üzerinde bir metod çağırırsak çalışma zamanında bir hata alırız. Ancak Rust bu hataları derleme zamanına taşır, böylece kodumuz daha çalışmadan önce sorunları düzeltmek zorunda kalırız. Ayrıca, derleme zamanında zaten kontrol ettiğimiz için çalışma zamanında davranışı kontrol eden kod yazmak zorunda kalmayız. Bunu yapmak, yaygınların esnekliğinden vazgeçmek zorunda kalmadan performansı artırır.