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.