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;
}
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)
}
}
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)
}
}
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); }
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);
}
}
}
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.