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.
Ş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.
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));
}
Box<T>li listeye izin verilmedigini gostermekDerleyince ş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.
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));
}
Rc<T> kullanan Liste tanimiRc<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.
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));
}
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.