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

Referans Sayımlı Akıllı İşaretçi Rc<T>

Çoğu durumda sahiplik açıktır: hangi değişkenin hangi değere sahip olduğunu bilirsiniz. Ama bazen tek bir değerin birden çok sahibi olabilir. Örneğin graf veri yapılarında birden fazla kenar aynı düğümü gösterebilir; bu durumda o düğüm kavramsal olarak tüm bu kenarlar tarafından sahiplenilir. Düğüm, ona işaret eden sahip kalmayana kadar temizlenmemelidir.

Birden çok sahipliği açıkça etkinleştirmek için Rust’taki Rc<T> türünü kullanırız. Bu ad reference counting’in kısaltmasıdır. Rc<T>, bir değere kaç referans olduğunu takip eder; referans sayısı sıfıra indiğinde, geçersiz referans üretmeden veri temizlenebilir.

Rc<T>yi aile oturma odasındaki televizyon gibi düşünebilirsiniz: Odaya gelen ilk kişi televizyonu açar. Başkaları da gelip izleyebilir. Son kişi çıkınca televizyon kapatılır. İçeride biri hâlâ televizyon izlerken kapatılırsa kavga çıkar.

Rc<T>yi, programın birden çok kısmının okuyacağı veriyi öbekte ayırmak istediğimiz ama veriyi en son kimin kullanmayı bırakacağını derleme zamanında bilmediğimiz durumlarda kullanırız. Bilseydik, verinin sahibini o yapar ve normal sahiplik kurallarına uyardık.

Rc<T> yalnızca tek iş parçacıklı senaryolar içindir. 16. bölümde çok iş parçacıklı programlarda referans sayımının nasıl yapılacağını göreceğiz.

Veri Paylaşmak

Liste 15-5’teki cons liste örneğine geri dönelim. Orada Box<T> kullanmıştık. Bu kez üçüncü bir listeyi birlikte sahiplenen iki liste kuracağız.

'a' etiketli bir liste 5 ve 10'u iceriyor; 'b' listesi 3 ile baslayip 'a'ya baglaniyor, 'c' listesi 4 ile baslayip yine 'a'ya baglaniyor.

Şekil 15-3: b ve c listeleri, a listesinin sahipliğini paylaşıyor

Önce 5 ve 10u içeren a listesini, sonra 3 ile başlayan b ve 4 ile başlayan c listelerini kuracağız. Hem b hem c, ardından anın devamını kullanacak.

Bunu Box<T>li Liste tanımımızla yapmak işe yaramaz; Liste 15-17 bunu gösterir.

Filename: src/main.rs
enum Liste {
    Dugum(i32, Box<Liste>),
    Bos,
}

use crate::Liste::{Bos, Dugum};

fn main() {
    let a = Dugum(5, Box::new(Dugum(10, Box::new(Bos))));
    let b = Dugum(3, Box::new(a));
    let c = Dugum(4, Box::new(a));
}
Listing 15-17: Ucuncu listenin sahipligini paylasmaya calisan iki Box<T>li listeye izin verilmedigini gostermek

Derleyince şu hatayı alırız:

$ cargo run
   Compiling kons-liste v0.1.0 (file:///projects/kons-liste)
error[E0382]: use of moved value: `a`
  --> src/main.rs:11:30
   |
 9 |     let a = Dugum(5, Box::new(Dugum(10, Box::new(Bos))));
   |         - move occurs because `a` has type `Liste`, which does not implement the `Copy` trait
10 |     let b = Dugum(3, Box::new(a));
   |                              - value moved here
11 |     let c = Dugum(4, Box::new(a));
   |                              ^ value used here after move
   |
note: if `List` implemented `Clone`, you could clone the value
  --> src/main.rs:1:1
   |
 1 | enum Liste {
   | ^^^^^^^^^^ consider implementing `Clone` for this type
...
10 |     let b = Dugum(3, Box::new(a));
   |                              - you could clone this value

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

Dugum varyantları tuttukları verinin sahibidir. byi oluşturduğumuz anda a bye taşınır. Ardından cyi oluştururken ayı yeniden kullanamayız.

Dugumü referans tutacak şekilde değiştirsek bu kez ömür parametreleri tanımlamamız gerekirdi. Bu da listedeki her öğenin tüm listenin ömrü kadar yaşamasını gerektirirdi. Liste 15-17’deki örnek için geçerli olsa da her zaman böyle değildir.

Bu yüzden Liste tanımında Box<T> yerine Rc<T> kullanacağız; Liste 15-18 bunu gösterir. Her Dugum artık bir değer ve Listeyi gösteren Rc<T> taşır. byi oluştururken ayı sahiplenmek yerine anın tuttuğu Rc<Liste>yi klonlarız; böylece referans sayısı artar ve veri paylaşılır. c için de aynısı yapılır.

Filename: src/main.rs
enum Liste {
    Dugum(i32, Rc<Liste>),
    Bos,
}

use crate::Liste::{Bos, Dugum};
use std::rc::Rc;

fn main() {
    let a = Rc::new(Dugum(5, Rc::new(Dugum(10, Rc::new(Bos)))));
    let b = Dugum(3, Rc::clone(&a));
    let c = Dugum(4, Rc::clone(&a));
}
Listing 15-18: Rc<T> kullanan Liste tanimi

Rc<T> prelude içinde olmadığı için önce kapsamaya almamız gerekir. main içinde 5 ve 10u barındıran listeyi a adlı Rc<Liste>ye koyuyoruz. Ardından b ve c oluşturulurken Rc::clone(&a) çağırıyoruz.

İstersek a.clone() da yazabilirdik; ama Rust geleneği burada Rc::clone kullanmaktır. Çünkü Rc::clone, çoğu clone uygulaması gibi tüm veriyi derin kopyalamaz; yalnızca referans sayısını artırır. Görsel olarak da bu iki klon türünü birbirinden ayırmak performans analizi yaparken işimizi kolaylaştırır.

Klonlayarak Referans Sayısını Artırmak

Liste 15-18’deki çalışan örneği biraz değiştirip adaki Rc<Liste>nin referans sayısının nasıl değiştiğini görelim.

Liste 15-19’da c listesini iç kapsam içine alıyoruz; böylece c kapsam dışına çıkınca sayının nasıl düştüğünü görebiliriz.

Filename: src/main.rs
enum Liste {
    Dugum(i32, Rc<Liste>),
    Bos,
}

use crate::Liste::{Bos, Dugum};
use std::rc::Rc;

// --snip--

fn main() {
    let a = Rc::new(Dugum(5, Rc::new(Dugum(10, Rc::new(Bos)))));
    println!("a oluşturulduktan sonraki sayı = {}", Rc::strong_count(&a));
    let b = Dugum(3, Rc::clone(&a));
    println!("b oluşturulduktan sonraki sayı = {}", Rc::strong_count(&a));
    {
        let c = Dugum(4, Rc::clone(&a));
        println!("c oluşturulduktan sonraki sayı = {}", Rc::strong_count(&a));
    }
    println!("c kapsamdan çıkınca sayı = {}", Rc::strong_count(&a));
}
Listing 15-19: Referans sayisini yazdirmak

Referans sayısı her değiştiğinde Rc::strong_count çağırıp sonucu yazdırıyoruz. Bu fonksiyona yalnızca count değil strong_count denmesinin nedeni, Rc<T>nin weak_count adlı başka bir sayaç daha tutmasıdır; onu Weak<T> Kullanarak Referans Döngülerini Önlemek” bölümünde göreceğiz.

Çıktı şöyledir:

$ cargo run
   Compiling kons-liste v0.1.0 (file:///projects/kons-liste)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.45s
     Running `target/debug/kons-liste`
a oluşturulduktan sonraki sayı = 1
b oluşturulduktan sonraki sayı = 2
c oluşturulduktan sonraki sayı = 3
c kapsamdan çıkınca sayı = 2

anın başlangıçta referans sayısı 1dir. Her clone çağrısı sayıyı bir artırır. c kapsam dışına çıktığında sayı bir azalır. Azaltmak için ayrıca fonksiyon çağırmamız gerekmez; Rc<T> kapsam dışına çıktığında Drop uygulaması sayacı otomatik düşürür.

Bu örnekte görmediğimiz şey, main sonunda b ve sonra a kapsam dışına çıkınca sayının 0 olması ve Rc<Liste>nin tamamen temizlenmesidir. Rc<T> tek bir değerin birden çok sahibi olmasını sağlar; sayaç da herhangi bir sahip yaşadığı sürece değerin geçerli kalmasını güvence altına alır.

Rc<T>, değiştirilemez referanslar üzerinden veriyi birden çok yerde yalnızca okuma amaçlı paylaşmanızı sağlar. Değiştirilebilir erişim de verseydi, 4. bölümde gördüğümüz ödünç alma kurallarını ihlal etmek kolaylaşırdı. Yine de veriyi değiştirebilmek çok faydalı. Sonraki bölümde, bu kısıtı esnetmek için RefCell<T> ile içsel değiştirilebilirlik desenini göreceğiz.