Rc<T>
, reference hisoblangan Smart Pointer
Ko‘p hollarda, ownership ochiq, ya’ni tushunarli holda bo’ladi: sizga qaysi o‘zgaruvchi qaysi berilgan qiymatga egalik qilishini aniq bilasiz. Ammo, ayrim hollarda bitta qiymat ko‘p egalarga ega bo‘lishi mumkin. Masalan, grafik ma’lumotlar tuzilmasida (data structures), bir nechta edgelar bitta nodeni point qilishi mumkin, vas shu node unga point qilingan barcha edgelar tomonidan egalik qilinadi. Nodega edgelar point qilinmagungacha, shuningdek egalari bo‘lmagungacha tozalanishi mumkin emas.
Rustning refenceni hisoblovchi Rc
Rc<T>
ni zaldagi televizor sifatida tasavvur qiling. Agar bir kishi televizor ko‘rish uchun xonaga kirsa, u televizorni yoqadi. Boshqalar esa shunchaki xonaga kirib tomosha qilsalar bo‘ladi. Xonadan oxirgi odam chiqib ketayotganda, ular televizorni o‘chirib ketishadi chunki televizor boshqa ishlatilmaydi. Agar bir kishi boshqalar televizorni tomosha qilib o‘tirganida o‘chirsa boshqalar uchun g‘alati bo‘lishi mumkin.
Biz Rc<T>
ni ma’lumotni heapda dasturning ko‘p qismlarini o‘qishi uchun ajratishni hohlasaganimizda foydalanamiz va biz kompilyatsiya vaqti qaysi qism ma’lumotini oxirgi foydalanishni yakunlaganini bila olmaymiz. Agar biz ma’lumotni qaysi qismi oxirida to‘xtashini bilganimizda, biz shu qismni ma’lumotni egasi sifatida tayinlar edik va kompilyatsiya vaqtida qo‘llaniladigan oddiy egalik (ownership) qoidalari kuchga kirar edi.
Shuni yodda tutish kerakki Rc<T>
yakka-thread holatlardagina ishlatiladi. Biz 16-bo‘limda parralellik haqida suhlashganimizda, biz ko‘p threadli dasturlarda referenceni hisoblashni qanday qilishni o‘rganamiz.
Rc<T>
ni Ma'lumotni Ulashish uchun ishlatish
Keling kamchiligi bor bo‘lgan 15-5 ro‘yxat misolimizga qaytaylik. Esingizda bo‘lsa biz Box<T>
ni ishlatishni ko‘rsatib o‘tgan edik. Bu safar, biz 2ta ro‘yxat ham egalikni (ownership) 3-ro‘yxat bilan ulashadigan ro‘yxat yaratamiz. Aniq qilib aytadigan bo‘lsak, 15-3 shaklga o‘xshashdir:
Biz 5 va 10 dan iborat bo‘lgan a
ro‘yxatni yaratamiz. Keyin yana ikkita ro‘yxatni ham yaratamiz, ya’ni 3dan boshlanadigan b
va 4dan boshlanadigan c
. b
va c
ro‘yxatlari 5 va 10dan iborat bo‘lgan a
ro‘yxatda davom etadi. Boshqacha qilib aytganda, ro‘yxatlar birinchi 5 va 10dan iborat bo‘lgan birinchi ro‘yxat bilan ulashishadi.
15-17-ro‘yxatda ko‘rsatilgandek, bizning ssenariy bo‘yicha Box<T>
bilan List
dagi ta’rifimiz yordamida implement qilishga urunsak ishga tushmaydi:
Fayl-nomi: 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));
}
Ushbu kodni kompilyatsiya qilsak, biz yuqoridagi xatolikni ko'ramiz:
$ 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
variantlari o'zlariga tegishli bo'lgan ma'lumotlargagina egalik (own) qila oladi, shuninguchun biz b
ro'yxatini yaratganimizda, a
b
ga o'tadi va b
a
ga egalik qiladi.Undan keyin, c
ni yaratish uchun a
dan foydalanmoqchi bo'lganizmizda bizga ruxsat bermaydi chunki a
ko'chib ketganligi uchun.
Buning o'rniga havolalarni ushlab turish uchun Cons
ta'rifini o'zgartirishimiz mumkin, lekin keyin biz layvtaym parametrlarini ko'rsatishimiz kerak bo'ladi. Layvtaym parametrlarini belgilash orqali, biz ro'yxatdagi har bir elementning yashashini ko'rsatamiz. 15-17 ro'yxatda ko'rsatilganidek bu elementlar va ro'yxatlarga tegishli, lekin har doim gam emas.
15-18-ro'yxatda ko'rsatilganidek, Box<T>
ning o'rnigaRc<T>
ni ishlatish uchun biz bizning Ro'yxat
imizning mazmunini o'zgartiramiz. Har bir Cons
varianti qiymatni o'zida ushlab turadi va Rc<T>
Ro'yxat
ni ko'rsatadi. a
ning egaligini olishning o'rniga b
ni yaratganimizda, a
ni ushlab turgan Rc<List>
ni klonlaymiz, shu bilan birga referenslar sonini birdan ikkiga ko'paytiramiz va Rc<List>
dagi ma'lumotlarning egaligini ulashish uchun a
va b
ga ruxsat beramiz. referenslar sonini ikkidan uchga ko'paytirgan holda, c
ni yaratayotganimizda a
ni ham klonlaymiz. Rc::clone
ni har safar chaqirganimizda, Rc<List>
tarkibidagi ma'lumotlarining referenslari soni oshiriladi, zero referenslar paydo bo'lmagungacha ma'lumotlar tozalanmaydi.
Filename: 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)); }
Biz scopeni ichiga Rc<T>
ni kiritsh uchun use
statementini qo'shishimiz kerak
chunki u muqaddimani ichida bo'lmagani uchun. main
ni ichida 5 va 10ni saqlovch ro'yxatni
yaratamiz va uni a
ga tegishli yangi Rc<List>
ga joylashtiramiz. Keyin esa b
va c
yaratganimizda, Rc::clone
funksiyasini chaqiramiz va argument sifatida a
ga tegishli bo'lgan Rc<List>
ga o'tkazib yuboramiz.
Rc::clone(&a)
ning o'rniga biz a.clone()
chaqirishimiz mumkin edi, lekin Rust
qoidalariga muofiq ushbu holatda Rc::clone
ishlatgan ma'qul. Rc::clone
ning implementatsiyasi clone
ning ko'p implementatsiya turiga o'xshab ma'lumotlarni to'liq
nusxa olmaydi. Rc::clone
ning chaqirilishi referenslarning sonini ko'paytiradi, va shuning uchun ham ko'p vaqt olmaydi. Ma'lumotlarni to'liq nusxalash esa ko'p vaqt talab etadi. Referenslarni hisoblash uchun Rc::clone
ni ishlatsak, biz to'liq nusxalangan bilan referenslar soni ortgan nusxalar orasidagi farqni tasavvur qilishimiz mumkin. Kodning unumdorlik muammolarini qidirayotganimizda, bizga faqat to'liq nusxalangan nusxalarga e'tibor qaratib, Rc::clone
ning chaqirilishini e'tiborga olmasak ham bo'ladi.
Rc<T>
ni nusxalash Referenslar hisobini orttiradi
a
da referenslarni yaratib va uni Rc<List>
ga drop qilib referenslar sonini ortayotganligini keling bizning ishlab turgan 15-18-ro'yxatimizdagi misolga o'zgartirish kiritib ko'raylik,
15-19-ro'yxatda, main
ni o'zgartirib ko'raylik chunki uning ichki scope(doirasi) c
ro'yxatining atrofida shundan so'ng biz c
scope(doirasida) chiqqanda qanday qilib referenslar soni ortayotganini ko'ramiz.
Fayl-nomi: 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)); }
Dasturda referens soni o'zgargan har bir nuqtada, Rc::strong_count
funksiyasini chaqirish yordamida biz referens sonini print qilamiz. Ushbu funksiyani nomicount
deb emas strong_count
deb nomlanadi, chunki Rc<T>
turida weak_count
ham bor; biz weak_count
ni "Referenslar siklini oldini olish: Rc<T>
ni Weak<T>
ga aylantirish" bo'limida ko'ramiz.
Ushbu kod quyidagini print qiladi:
$ 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
da Rc<List>
ning referens soni 1 ekanligini ko'rishimiz mumkin; keyinchalik har safar biz clone
ni chaqirganimizda, son 1ga ortib boraveradi. c
o'z scope(doirasi)dan chiqib ketganida,referens soni 1ga kamayadi. Rc::clone
funksiyasi yordamida referens sonini ortirish uchun chaqirganimizdek referens soni kamaytirish uchun hech qanday funksiyasini chaqirishimiz kerak bo'lmaydi, chunki Drop
traitining implementatsiyasi Rc<T>
qiymati o'z scope(doirasi)dan chiqqanda avtomatik ravishda referens sonini kamaytiradi.
main
oxirida b
va keyin a
larning scope(doirasidan) chiqib, son/hisob 0ga tenglashib, Rc<List>
to'liq tozalanganligini biz ushbu misolda ko'ra olmaymiz. Rc<T>
ni ishlatish yordamida bitta qiymat ko'p egalarga ega bo'lishi mumkin, hamda son/hisob qiymat egalaridan biri bor bo'lgunga qadar yaroqliligini tekshirib turadi.
O'zgarmas o'zgaruvchilar yordamida, Rc<T>
o'qish uchun ma'lumotlarni dasturning ko'p joylari orasida ulashish imkonini beradi. Agar Rc<T>
ko'p o'zgaruvchan referenslarga ega bo'lish imkonini bergan bo'lsa, siz 4-Bo'limda ko'rsatilganidek borrowing qoidalarini birini buzishingiz mumkin: ko'p o'zgaruchilar bir xil joyga borrow qilib data race va nomuvofiqliklarga sabab bo'lishi mumkin. Lekin ma'lumotni o'zrgatira olish foydalidir! keyingi bo'limda, biz ichki o'zgaruvchanlik shakli(pattern) va RefCell<T>
turi bilan Rc<T>
yordamida o'zgarmaslik cheklovi bilan ishlash ko'rib chiqamiz.