Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Akıllı İşaretçilere Normal Referanslar Gibi Davranmak

Deref trait’ini uygulamak, * başvuru çözme operatörünün (dereference operator) davranışını özelleştirmenizi sağlar. Bu trait’i, akıllı işaretçi normal referans gibi davranacak şekilde uygularsanız, referanslarla çalışan kodu akıllı işaretçilerle de kullanabilirsiniz.

Önce başvuru çözme operatörünün normal referanslarla nasıl çalıştığına bakalım. Ardından Box<T> gibi davranan özel bir tür tanımlamayı deneyeceğiz ve neden başvuru çözmenin referanslardaki gibi çalışmadığını göreceğiz. Sonra Deref trait’ini uygulamanın akıllı işaretçileri referanslara benzer biçimde çalıştırmayı nasıl mümkün kıldığına bakacağız. En sonda da Rust’ın deref zorlama (deref coercion) özelliğini ve bunun referanslarla akıllı işaretçileri birlikte nasıl kullanmamızı kolaylaştırdığını göreceğiz.

Referansın Göstediği Değere Gitmek

Normal referans bir işaretçi türüdür. İşaretçiyi düşünmenin kolay yollarından biri, başka yerde tutulan bir değere uzanan ok gibi görmektir. Liste 15-6’da bir i32 değerine referans oluşturuyor ve başvuru çözme operatörüyle bu referansın gösterdiği değere gidiyoruz.

Filename: src/main.rs
fn main() {
    let sayi = 5;
    let referans = &sayi;

    assert_eq!(5, sayi);
    assert_eq!(5, *referans);
}
Listing 15-6: Bir i32 degerine giden referansi, basvuru cozme operatoruyle izlemek

sayi değişkeni 5 değerini tutar. referansı sayiya referans olacak şekilde ayarlarız. sayinın 5 olduğuna dair doğrulama yapabiliriz. Ama referans içindeki değeri doğrulamak istersek, *referans yazarak referansın işaret ettiği değere gitmemiz gerekir; böylece derleyici gerçek değerleri karşılaştırabilir.

assert_eq!(5, referans); yazsaydık derleme hatası alırdık:

$ cargo run
   Compiling deref-ornegi v0.1.0 (file:///projects/deref-ornegi)
error[E0277]: can't compare `{integer}` with `&{integer}`
 --> src/main.rs:6:5
  |
6 |     assert_eq!(5, referans);
  |     ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`
  |
  = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}`
  = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `deref-ornegi` (bin "deref-ornegi") due to 1 previous error

Sayı ile sayıya referans farklı türler olduğu için doğrudan karşılaştırılamaz. Referansın işaret ettiği değere gitmek için başvuru çözme operatörünü kullanmalıyız.

Box<T>yi Referans Gibi Kullanmak

Liste 15-6’daki kodu, referans yerine Box<T> kullanacak biçimde yeniden yazabiliriz. Liste 15-7’de kutu üzerinde kullanılan başvuru çözme operatörü, Liste 15-6’daki referans üzerinde kullanılanla aynı şekilde çalışır.

Filename: src/main.rs
fn main() {
    let sayi = 5;
    let kutu = Box::new(sayi);

    assert_eq!(5, sayi);
    assert_eq!(5, *kutu);
}
Listing 15-7: Box<i32> uzerinde basvuru cozme operatorunu kullanmak

Fark şu: bu kez kutu, sayinın kopyalanmış değerini işaret eden bir kutudur. Son doğrulamada *kutu ile kutunun gösterdiği değere, referansta yaptığımızın aynı şekilde gidebiliyoruz. Şimdi bunun neden mümkün olduğunu görmek için kendi kutu türümüzü tanımlayalım.

Kendi Akıllı İşaretçimizi Tanımlamak

Standart kütüphanenin sunduğu Box<T>ye benzer sarmalayıcı bir tür oluşturalım. Amaç, akıllı işaretçilerin varsayılan olarak referanslardan nasıl farklı davrandığını deneyimlemek. Ardından başvuru çözmeyi nasıl ekleyebileceğimizi göreceğiz.

Not: Birazdan oluşturacağımız BenimKutu<T> ile gerçek Box<T> arasında önemli bir fark var: bizim sürümümüz veriyi öbekte tutmayacak. Burada odak Deref olduğu için verinin nerede saklandığı değil, işaretçi gibi davranışı önemli.

Box<T> aslında tek öğeli bir demet struct’ıdır; Liste 15-8 de aynı mantıkla BenimKutu<T> türünü tanımlar. Box<T>deki new fonksiyonuna benzeyen bir yeni fonksiyonu da tanımlıyoruz.

Filename: src/main.rs
struct BenimKutu<T>(T);

impl<T> BenimKutu<T> {
    fn yeni(x: T) -> BenimKutu<T> {
        BenimKutu(x)
    }
}

fn main() {}
Listing 15-8: BenimKutu<T> turunu tanimlamak

BenimKutu adında bir struct tanımlayıp jenerik T parametresi ekliyoruz; çünkü her türden değeri tutabilsin istiyoruz. BenimKutu::yeni, T türünden bir parametre alır ve bu değeri saklayan BenimKutu döndürür.

Şimdi Liste 15-7’deki main fonksiyonunu Liste 15-8’e ekleyip Box<T> yerine BenimKutu<T> kullanalım. Liste 15-9 derlenmez; çünkü Rust BenimKutu üzerinde başvuru çözmenin nasıl yapılacağını bilmiyor.

Filename: src/main.rs
struct BenimKutu<T>(T);

impl<T> BenimKutu<T> {
    fn yeni(x: T) -> BenimKutu<T> {
        BenimKutu(x)
    }
}

fn main() {
    let sayi = 5;
    let kutu = BenimKutu::yeni(sayi);

    assert_eq!(5, sayi);
    assert_eq!(5, *kutu);
}
Listing 15-9: BenimKutu<T>yi referans ve Box<T> gibi kullanmaya calismak

Hata şöyle olur:

$ cargo run
   Compiling deref-ornegi v0.1.0 (file:///projects/deref-ornegi)
error[E0614]: type `BenimKutu<{integer}>` cannot be dereferenced
  --> src/main.rs:14:19
   |
14 |     assert_eq!(5, *kutu);
   |                   ^^ can't be dereferenced

For more information about this error, try `rustc --explain E0614`.
error: could not compile `deref-ornegi` (bin "deref-ornegi") due to 1 previous error

BenimKutu<T> üzerinde bu yeteneği tanımlamadığımız için başvuru çözülemez. Bunu sağlamak için Deref trait’ini uygulamalıyız.

Deref Trait’ini Uygulamak

  1. bölümdeki [“Bir Tür Üzerinde Trait Uygulamak”][impl-trait] başlığında gördüğümüz gibi, trait uygulamak için gerekli yöntemleri yazmamız gerekir. Standart kütüphanedeki Deref trait’i, selfi ödünç alan ve içteki veriye referans döndüren deref adlı tek bir yöntem ister. Liste 15-10 bu uygulamayı gösterir.
Filename: src/main.rs
use std::ops::Deref;

impl<T> Deref for BenimKutu<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

struct BenimKutu<T>(T);

impl<T> BenimKutu<T> {
    fn yeni(x: T) -> BenimKutu<T> {
        BenimKutu(x)
    }
}

fn main() {
    let sayi = 5;
    let kutu = BenimKutu::yeni(sayi);

    assert_eq!(5, sayi);
    assert_eq!(5, *kutu);
}
Listing 15-10: BenimKutu<T> uzerinde Deref uygulamak

type Target = T; sözdizimi, Deref trait’inin kullanacağı ilişkili türü tanımlar. İlişkili türler jenerik parametre bildirmeye benzer ama biraz farklı bir mekanizmadır; ayrıntısını 20. bölümde göreceğiz.

deref yönteminin gövdesine &self.0 yazıyoruz; böylece * ile erişmek istediğimiz değere referans dönüyor. 5. bölümdeki demet struct’larında olduğu gibi .0, ilk alanı seçer. Artık Liste 15-9’daki *kutu kullanan main derlenir ve doğrulamalar geçer.

Deref olmadan derleyici yalnızca & referanslarını çözebilir. deref yöntemi, Deref uygulayan herhangi bir türü alıp referansa çevirebilme yeteneği kazandırır.

Liste 15-9’da *kutu yazdığımızda, arka planda Rust şu kodu çalıştırır:

*(kutu.deref())

Rust, * operatörünü deref çağrısıyla ve ardından normal başvuru çözmeyle değiştirir; böylece bunu her seferinde elle düşünmemiz gerekmez.

derefin doğrudan değeri değil de referans döndürmesinin sebebi sahiplik sistemidir. Değeri doğrudan döndürseydi, değer selfin içinden taşınmış olurdu. Biz çoğu durumda akıllı işaretçinin içindeki değerin sahipliğini almak istemeyiz.

Fonksiyon ve Metotlarda Deref Zorlaması Kullanmak

Deref zorlama, Deref trait’ini uygulayan bir türe ait referansı başka bir türe ait referansa dönüştürür. Örneğin String, Deref uygulayıp &str döndürdüğü için &String, &stre dönüştürülebilir. Bu özellik fonksiyon ve metot çağrılarında, verdiğiniz argüman türü parametre türüyle tam eşleşmediğinde otomatik çalışır.

Bu özellik, fonksiyon ve metot çağrısı yazarken & ve * ile açıkça referans alma ve çözme ihtiyacını azaltır. Ayrıca hem referanslarla hem akıllı işaretçilerle çalışabilen daha esnek kod yazmanızı sağlar.

Bunu görmek için, Liste 15-8’de tanımladığımız BenimKutu<T> ve Liste 15-10’da eklediğimiz Deref uygulamasını kullanalım. Liste 15-11, parametresi &str olan merhaba fonksiyonunu gösterir.

Filename: src/main.rs
fn merhaba(isim: &str) {
    println!("Merhaba, {isim}!");
}

fn main() {}
Listing 15-11: Parametresi &str olan merhaba fonksiyonu

merhaba("Rust"); gibi bir çağrı zaten çalışır. Ama deref zorlama sayesinde BenimKutu<String> referansı da geçebiliriz; Liste 15-12 bunu gösterir.

Filename: src/main.rs
use std::ops::Deref;

impl<T> Deref for BenimKutu<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

struct BenimKutu<T>(T);

impl<T> BenimKutu<T> {
    fn yeni(x: T) -> BenimKutu<T> {
        BenimKutu(x)
    }
}

fn merhaba(isim: &str) {
    println!("Merhaba, {isim}!");
}

fn main() {
    let kutu = BenimKutu::yeni(String::from("Rust"));
    merhaba(&kutu);
}
Listing 15-12: Deref zorlama sayesinde merhabayi BenimKutu<String> referansiyla cagirmak

Burada merhabaya &kutu geçiriyoruz; bu bir BenimKutu<String> referansıdır. BenimKutu<T> üzerinde Deref uyguladığımız için Rust bunu önce &Stringe çevirir. Standart kütüphane de String için Deref uygulayıp &str döndürdüğünden, ikinci kez deref çağrısı yapılarak &str elde edilir. Sonuçta merhabanın beklediği türle eşleşmiş oluruz.

Rust’ta deref zorlama olmasaydı, Liste 15-12 yerine Liste 15-13’teki gibi açık kod yazmamız gerekirdi:

Filename: src/main.rs
use std::ops::Deref;

impl<T> Deref for BenimKutu<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

struct BenimKutu<T>(T);

impl<T> BenimKutu<T> {
    fn yeni(x: T) -> BenimKutu<T> {
        BenimKutu(x)
    }
}

fn merhaba(isim: &str) {
    println!("Merhaba, {isim}!");
}

fn main() {
    let kutu = BenimKutu::yeni(String::from("Rust"));
    merhaba(&(*kutu)[..]);
}
Listing 15-13: Rust’ta deref zorlama olmasaydi yazmamiz gerekecek kod

(*kutu)[..] ifadesi hem BenimKutu<String>yi Stringe çözer hem de tam dizgiyi kapsayan bir dizgi dilimi üretir. Deref zorlama bu işi bizim yerimize otomatik yaptığı için kod daha kısa ve okunur olur.

Rust üç durumda deref zorlama yapar:

  • T: Deref<Target=U> ise &T, &Uye çevrilir.
  • T: DerefMut<Target=U> ise &mut T, &mut Uye çevrilir.
  • T: Deref<Target=U> ise &mut T, &Uye de çevrilebilir.

Üçüncü durum, değiştirilebilir referansın değiştirilemez referans da olabilmesi nedeniyle mümkündür. Tersi doğru değildir: değiştirilemez referansla değiştirilebilir referans elde edilemez.