Rc<T>
, Referans Sayaçlı Akıllı İşaretçi
Çoğu durumda, sahiplik açıktır: Belirli bir değere hangi değişkenin sahip olduğunu tam olarak bilirsiniz. Ancak, tek bir değerin birden çok sahibi olabileceği durumlar vardır. Örneğin, grafik veri yapılarında, birden çok kenar aynı düğüme işaret edebilir ve bu düğüm kavramsal olarak ona işaret eden tüm kenarlara aittir. Bir düğüm, kendisine işaret eden herhangi bir kenarı olmadığı ve dolayısıyla sahibi olmadığı sürece temizlenmemelidir.
Referans sayımının kısaltması olan Rust türü Rc<T>
'yi kullanarak birden çok sahipliği açıkça etkinleştirmeniz gerekir.
Rc<T>
türü, değerin hala kullanımda olup olmadığını belirlemek için bir değere yapılan başvuruların sayısını takip eder.
Bir değere sıfır referans varsa, hiçbir referans geçersiz hale gelmeden değer temizlenebilir.
Rc<T>
'yi bir aile odasındaki bir TV olarak hayal edin. Bir kişi TV izlemek için girdiğinde onu açar.
Diğerleri odaya gelip televizyon izleyebilir. Son kişi odadan ayrıldığında, artık kullanılmadığı için televizyonu kapatırlar.
Biri televizyonu kapatırsa, diğerleri hala onu seyrediyorsa, kalan televizyon izleyicilerinden büyük bir kargaşa çıkar!
Rc<T>
türünü, programımızın birden fazla bölümünün okuması için yığın üzerinde bazı verileri tahsis etmek istediğimizde kullanırız ve
derleme zamanında verileri en son hangi bölümün bitireceğini belirleyemeyiz. Hangi bölümün en son biteceğini bilseydik,
o bölümü verinin sahibi yapabilirdik ve derleme zamanında uygulanan normal sahiplik kuralları yürürlüğe girecekti.
Rc<T>
öğesinin yalnızca tek iş parçacıklı senaryolarda kullanım için olduğunu unutmayın.
Bölüm 16'da eşzamanlılığı tartıştığımızda, çok iş parçacıklı programlarda referans sayımının nasıl yapıldığını ele alacağız.
Veri Paylaşmak için Rc<T>
Kullanmak
Liste 15-5'teki liste örneğimize dönelim. Box<T>
kullanarak tanımladığımızı hatırlayın.
Bu sefer, her ikisi de üçüncü bir listenin sahipliğini paylaşan iki liste oluşturacağız.
Kavramsal olarak, bu Şekil 15-3'e benziyor:
5
ve ardından 10
'u içeren bir a
listesi oluşturacağız.
Ardından, 3
ile başlayan b
ve 4
ile başlayan c
olmak üzere iki liste daha yapacağız.
Daha sonra hem b
hem de c
listeleri, 5
'i ve 10
'u içeren ilk listeye devam edecek.
Başka bir deyişle, her iki liste de 5
ve 10
'u içeren ilk listeyi paylaşacaktır.
Liste 15-17'de gösterildiği gibi, Box<T>
'li List
tanımımızı kullanarak bu senaryoyu uygulamaya
çalışmak işe yaramaz:
Dosya adı: src/main.rs
enum List {
Cons(i32, Box<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a));
}
Kodu derlediğimizde, şu hatayı alırız:
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
error[E0382]: use of moved value: `a`
--> src/main.rs:11:30
|
9 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
| - move occurs because `a` has type `List`, which does not implement the `Copy` trait
10 | let b = Cons(3, Box::new(a));
| - value moved here
11 | let c = Cons(4, Box::new(a));
| ^ value used here after move
For more information about this error, try `rustc --explain E0382`.
error: could not compile `cons-list` due to previous error
Cons
varyantları, tuttukları verilere sahiptir, bu nedenle b
listesini oluşturduğumuzda a
, b
'ye taşınır ve b
, a
'ya sahiptir.
Ardından, c
'yi oluştururken a
'yı tekrar kullanmaya çalıştığımızda, a
taşınmış olduğu için buna izin verilmiyor.
Bunun yerine referansları tutmak için Cons
'un tanımını değiştirebilirdik, ancak daha sonra ömür boyu parametreleri belirtmemiz gerekecekti.
Yaşam süresi parametreleri belirterek, listedeki her öğenin en az tüm liste kadar yaşayacağını belirtmiş oluruz.
Bu, Liste 15-17'deki öğeler ve listeler için geçerlidir, ancak her senaryoda geçerli değildir.
Bunun yerine List
tanımımızı, Liste 15-18'de gösterildiği gibi Box<T>
yerine Rc<T>
kullanacak şekilde değiştireceğiz.
Her Cons
varyantı artık bir değere ve bir Listeye işaret eden bir Rc<T>
'ye sahip olacaktır. b
'yi yarattığımızda, a
'nın sahipliğini almak yerine, a
'nın sahip olduğu Rc<List>
'i klonlayacağız, böylece referans sayısını birden ikiye çıkaracağız ve a
ve b
'nin o
Rc<List>
'deki verilerin sahipliğini paylaşmasına izin vereceğiz. Ayrıca c
'yi oluştururken a
'yı klonlayarak referans sayısını
ikiden üçe çıkaracağız. Rc::clone
'u her çağırdığımızda, Rc<List>
içindeki verilere referans sayısı artacak ve sıfır referans
olmadıkça veriler temizlenmeyecektir.
Dosya adı: src/main.rs
enum List { Cons(i32, Rc<List>), Nil, } use crate::List::{Cons, Nil}; use std::rc::Rc; fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); let b = Cons(3, Rc::clone(&a)); let c = Cons(4, Rc::clone(&a)); }
Rc<T>
'yi kapsam içine almak için bir use
ifadesi eklememiz gerekiyor çünkü bu başlangıç kısmında değil.
main
'de, 5 ve 10'u tutan listeyi oluşturuyoruz ve onu a
'da yeni bir Rc<List>
içinde saklıyoruz.
Daha sonra b
ve c
'yi oluşturduğumuzda, Rc::clone
fonksiyonunu çağırırız ve argüman olarak a
'daki Rc<List>
'e bir referansını
iletiriz.
Rc::clone(&a)
yerine a.clone()
'u çağırabilirdik, ancak Rust'ın kuralı bu durumda Rc::clone
kullanmaktır.
Rc::clone
'un süreklenmesi, çoğu türde clone
süreklemesinin yaptığı gibi tüm verilerin derin bir kopyasını oluşturmaz.
Rc::clone
çağrısı yalnızca referans sayısını artırır, bu da fazla zaman almaz. Verilerin derin kopyaları çok zaman alabilir.
Referans sayımı için Rc::clone
kullanarak, derin kopyalı klon türleri ile referans sayısını artıran klon türleri arasında görsel olarak ayrım yapabiliriz. Kodda performans sorunları ararken, yalnızca derin kopya klonlarını dikkate almamız gerekir ve Rc::clone
'a yapılan çağrıları göz ardı edebiliriz.
Rc<T>
'yi Klonlamak Referans Sayısını Artırır
Liste 15-18'deki çalışma örneğimizi değiştirelim, böylece a
içindeki Rc<List>
e referansları oluşturup bıraktıkça
referans sayılarının değiştiğini görebilelim.
Liste 15-19'da, main
'i, c
listesi etrafında bir iç kapsama sahip olacak şekilde değiştireceğiz;
o zaman c
kapsam dışına çıktığında referans sayısının nasıl değiştiğini görebiliriz.
Dosya adı: src/main.rs
enum List { Cons(i32, Rc<List>), Nil, } use crate::List::{Cons, Nil}; use std::rc::Rc; fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!("count after creating a = {}", Rc::strong_count(&a)); let b = Cons(3, Rc::clone(&a)); println!("count after creating b = {}", Rc::strong_count(&a)); { let c = Cons(4, Rc::clone(&a)); println!("count after creating c = {}", Rc::strong_count(&a)); } println!("count after c goes out of scope = {}", Rc::strong_count(&a)); }
Referans sayısının değiştiği programdaki her noktada, Rc::strong_count
fonksiyonunu çağırarak elde ettiğimiz referans
sayısını yazdırırız. Bu fonksiyon, count
yerine strong_count
olarak adlandırılır,
çünkü Rc<T>
türünde bir weak_count
da vardır; “Referans Döngülerini Önleme: Bir Rc<T>
'yi
Weak<T>
'ye Çevirme” bölümünde weak_count
'ın ne için kullanıldığını göreceğiz.
Bu kod şunları yazdırır:
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
Finished dev [unoptimized + debuginfo] target(s) in 0.45s
Running `target/debug/cons-list`
count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2
a
içindeki Rc<List>
öğesinin ilk referans sayısının 1
olduğunu görebiliriz; sonra clone
'u her çağırdığımızda,
sayı 1 artar. c
kapsam dışına çıktığında, sayı 1 azalır. Rc
'yi çağırmamız gerektiği gibi referans sayısını azaltmak için
bir fonksiyon çağırmamız gerekmez: referans sayısını artırmak için klonlayın: Drop
tanımının süreklenmesi,
bir Rc<T>
değeri kapsam dışına çıktığında referans sayısını otomatik olarak azaltır.
Bu örnekte göremediğimiz şey, main
sonunda b
ve ardından a
kapsam dışına çıktığında, sayının 0
olduğu ve Rc<List>
'in tamamen temizlendiğidir.
Rc<T>
kullanmak, tek bir değerin birden çok sahibine sahip olmasına izin verir ve sayı,
sahiplerden herhangi biri var olduğu sürece değerin geçerli kalmasını sağlar.
Değişmez referanslar aracılığıyla, Rc<T>
, programınızın birden çok bölümü arasında salt okuma için veri paylaşmanıza olanak tanır.
Eğer Rc<T>
birden çok değişken referansa sahip olmanıza da izin veriyorsa, Bölüm 4'te tartışılan ödünç alma kurallarından
birini ihlal etmiş olabilirsiniz: aynı yere birden fazla değişken ödünç alma veri yarışlarına ve tutarsızlıklara neden olabilir.
Ancak verileri değiştirebilmek çok faydalıdır! Bir sonraki bölümde, bu değişmezlik kısıtlamasıyla çalışmak için bir Rc<T>
ile birlikte kullanabileceğiniz iç değişkenlik modelini ve RefCell<T>
türünü tartışacağız.