RefCell<T>
va Ichki O'zgaruvchanlik Shakli(pattern)
Ichki o'zgaruvchanlik bu Rustda ma'lumotlarga o'zgarmas referenslar mavjud bo'lganda ham ma'lumotni o'zgartirish imkonini beruvchi (dizayn) shakli/patternidir: odatda bu borrowing qoidalari bo'yicha esa taqiqlangan. Ma'lumotni o'zgaruvchan qilish uchun shakl/pattern Rustning o'zgaruvchanlik va borrowingni boshqaruchi oddiy qoidalarini chetlab o'tish uchun ma'lumotlar strukturasi ichiga unsafe
kod ishlatiladi. Xavfsiz bo'lmagan kod bizning o'rnimizga kompilyatorga qoidalarni kompliyator yordamisiz tekshirayotganimizni ko'rsatadi; xavfsiz bo'lmagan kod haqida 19-bo'limda o'rganib chiqamiz.
Biz ichki o'zgaruvchanlik shakli/patterni ishlatadigan turlardan faqatgina borrowing qoidalari runtimeda amal qilingaligi paytida ishlatishimiz mumkin, kompilyator bunga kafolat bera olmaydi. Keyin unsafe
kod xavfsiz APIga ulanadi va tashqi tur o'zgarmasligicha qoladi.
Keling ushbu tushunchani ichki o'zgaruvchanlik shakliga amal qiluvchi quyidagi RefCell<T>
turiga qarab ko'rib chiqaylik.
Enforcing Borrowing Rules at Runtime with RefCell<T>
yordamida Borrowing qoidalarini Runtime vaqtida kuch bilan ishlatish
Rc<T>
lidan faqrli o'laroq, RefCell<T>
turi o'zi egalik qilib turgan ma'lumotda yagona egalikni namoyish etadi. Xo'sh, RefCell<T>
turi Box<T>
turidan nimasi bilan farq qiladi? 4-bo'limda o'tilgan borrowing qoidalarini esga olaylik:
- Xohlagan belgilangan vaqtda, siz yoki (ikkalasini bir vaqtda ega bo'lish mumkin emas)bitta o'zgaruvchan referens yoki xohlagan sondagi o'zgarmas referenslarga ega bo'lishingiz mumkin.
- Referenslar har doim yaroqli bo'lishi shart
Referenslar va Box<T>
bilan, borrowing qoidalarining kompilyatsiya vaqtida o'zgarmaslar kuchga kiradi. RefCell<T>
bilan esa ushbu o'zgarmaslar runtime paytida kuchga kiradi. Referenslar bilan, agar siz ushbu qoidalarni buzsangiz, sizda kompilyator xatoligi yuzaga keladi. RefCell<T>
bilan suhbu qoidalarni buzganingizda, sizning dasturingizda panic vujudga kelib, dastur chiqib ketadi.
The advantages of checking the borrowing rules at compile time are that errors will be caught sooner in the development process, and there is no impact on runtime performance because all the analysis is completed beforehand. For those reasons, checking the borrowing rules at compile time is the best choice in the majority of cases, which is why this is Rust’s default.
Borrowing qoidalarini kompilyatsiay vaqtida tekshirishning yaxshi tarafi xatolarni development vaqtida tezroq topishdir, va runtime unumdorligiga ta'sir ko'rsatmaydi chunki hamma analizlar oldindan qilingan bo'ladi. Ko'p hollarda borrowing qoidalarini kompilyatsiya vaqtida tekshirish eng yaxshi tanlovdir, sababi ushbu xususiyat Rustda odatiy xususiyatidir.
Borrowing qoidalarini runtime vaqtida tekshrishning afzalligi shundaki, kompilyatsiya vaqtidagi tekshiruvlar tomonidan ruxsat etilmaganda ba'zi xotira uchun xavfsizlik ssenariylarga ruxsat beriladi. Rust kompilyatoriga o'xshagan statik analizlar o'z-o'zidan konservativdir. Kodni tahlil qilayotganda kodning ba'zi bir xususiyatlarini aniqlash qiyindir: bunga Halting Problem mashxur misol bo'la oladi, bu kitob doirasidan tashqarida bo'lsada lekin izlanib o'rganish uchun qiziq mavzu
Agar Rust kompilyatori egalik (ownership) qoidalari asosida kompilyatsiya qilayotganini aqiqlay olmasa, bu to'g'ri, ya'ni ishlab turgan dasturni rad etishi mumkin, shuning uchun ham konservativ hisoblanadi va bu ba'zi tahlillar uchun qiyindir. Agar Rust xatolikka ega bo'lgan dasturni qabul qilsa, foydalanuvchilar Rust beradigan kafolatlarga ishona olmaydilar. Agarda, Rust ishlab turgan dasturni rad etsa, dasturchi uchun noqulaylik tug'diradi, lekin hech qanday qo'rqinchli narsa bo'lmaydi. RefCell<T>
turi sizning kodingiz borrowing qoidlariga amal qilayotganiga ishonchingiz komil bo'lganda lekin kompilyator buni tushuna olmayotganda va kafolat bera olmaganda foydalidir.
RefCell<T>
Rc<T>
ga o'xshab bitta potokli (oqimli) ssenariylarda ishlatilinadi va agar siz ko'p potokli (oqimli) holatda ishlatsangiz kompilyatsiya vaqtidagi xatolikni yuzaga keltiradi. Biz RefCell<T>
ni ko'p potokli (oqimli) dasturda qanday qilib funksionalligini olishni 16-bo'limda ko'rib chiqamiz.
Quyida takrorlash uchun Box<T>
, Rc<T>
, yoki RefCell<T>
ni tanlash sabablari:
Rc<T>
bitta ma'lumotga ko'p egalarga ega bo'lish imkonini beradi;Box<T>
vaRefCell<T>
esa yagona egaga egadirlar;Box<T>
kompilyatsiya vaqtida o'zgaruvchan va o'zgarmas borrowlarni tekshrilishini ta'minlaydi;Rc<T>
kompilyatsiya vaqtida faqat o'zgarmas borrowlarni tekshrilishini ta'minlaydi;RefCell<T>
runtimeda o'zgaruvchan va o'zgarmas borrowlarni tekshrilishini ta'minlaydi.- Because
RefCell<T>
runtimeda o'zgaruvchan borrowlar tekshirilishi ta'minlaydi, agarRefCell<T>
o'zgarmas bo'lsadaRefCell<T>
ichida qiymatni o'zgaruvchan qilishingiz mumkin.
Qiymatni o'zgarmas qiymat ichida o'zgaruvchan qilish ichki o'zgaruvchanlik shaklidir (pattern). Keling ichki o'zgaruvchanlikni foydali ekanligini va bu qanday sodir bo'lishini misollarda ko'rib chiqaylik.
Ichki o'zgaruvchanlik: O'zgaruvchan Borrowdan O'zgarmas Qiymatga
Borrowing qoilari natijasida, o'zgarmas qiyamtga ega bo'lganingizda, siz o'zgaruvchan borrow qila olmaysiz. Masalan, ushbu kod kompilyatsiya qilinmaydi:
fn main() {
let x = 5;
let y = &mut x;
}
Agar siz ushbu kodni kompilyatsiya qilishga harakat qilsangiz, quyidagi xatolik kelib chiqadi:
$ cargo run
Compiling borrowing v0.1.0 (file:///projects/borrowing)
error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
--> src/main.rs:3:13
|
2 | let x = 5;
| - help: consider changing this to be mutable: `mut x`
3 | let y = &mut x;
| ^^^^^^ cannot borrow as mutable
For more information about this error, try `rustc --explain E0596`.
error: could not compile `borrowing` due to previous error
Shunday vaziyatlar borki qiymat o'zini-o'zi o'zining metodlarida o'zgarivchan qilishi
foydali hisoblanadi, lekin boshqa kodda o'zgarmas shaklda bo'ladi. Kodning doirasidan
tashqaridagi qiymat metodi qiymatni o'zgaruvchan qila olmaydi. RefCell<T>
ni ishlatish ichki
o'zgaruvchanlikga ega bo'lishning bir yo'li hisoblanadi, lekin RefCell<T>
borrowing
qoidalarini to'liq aylanib o'tmaydi: kompilyatorda borrow tekshiruvchisi shu ichki o'zgaruvchanlikka
ruxsat beradi, va borrowing qoidalari runtimes tekshiriladi. Agar qoidalarni rad etsangiz,
kompilyator xatoligini o'rniga panic!
ko'rasiz.
RefCell<T>
ni o'zgarmas qiymatni o'zgaruvchanga aylantirishni, hamda nimaga RefCell<T>
ni
ishlatish foydali ekanligini amaliy misollarda ko'rib chiqaylik.
Ichki o'zgaruvchanlik uchun foydalanish holati/misoli: Soxta Obyektlar
Ayrim hollarda test vaqti dasturchi boshqa turni o'rniga kerakli hatti-harakatni kuzatish uchun va to'g'ri kompilyatsiya amalga oshirilganligini tasdiqlash uchun boshqa bir turni ishlatib ko'radi. Ushbu to'ldiruvchi tur test double deb ataladi. Qiyin bo'lgan sahna ko'rinishida aktyorning o'rniga chiqib, sahna ko'rinishi amalga oshirib beruvchi, ya'nikino yaratishda "kaskadyor" misolida ko'rib chiqaylik. Test doublelari boshqa turlarda test o'tkazayotganimizda xizmat qiladi. Soxta obyektlar test paytida nimalar sodir bo'lishini qayd etuvchi test doublelar o'ziga xos turlardan biri bo'lib, siz to'g'ri amallar amalga oshirilayotganini ko'zdan kechirishingiz mumkin.
Rustda boshqa dasturlash tillari kabi bir xil ma'noli obyektlarga ega emas, va soxta obyekt funksionalligini olgan standart kutubxonasi yo'q. Aksincha, soxta obyektlar kabi ish bajaruvchi struct yaratishingiz mumkin.
Ushbu ssenariyni ko'rib chiqaylik: qiymatni maksimal qiymatga nisbatan kuzatuvchi kutubxona yaratamiz va joriy qiymat maksimal qiymatga qanchalik yaqinligiga qarab bizga xabar jo'natib turadi. Ushbu kutubxona foydalanuvchi uchun ruxsat etilgan API 'call'lar sonini kuzatib borish uchun ishlatilishi mumkin, bu ishlatish mumkin bo'lgan bir misol.
Bizning kutubxonamiz faqatgina qiymatni maksimal qiymatga qanchalik yaqin ekanligi
va qaysi vaqtda qaysi xabar jo'natilishini kuzatib turish imkonini beredi. Bizning
kutubxonamizdan foydalanadigan ilovalar xabar jo'natish mexanizmini ta'minlashini
talab qiladi, ya'ni ilova xabarni ilova ichida, email orqali, matnli xabar ko'rinishida
yoki boshqa ko'rinishda yuborishi mumkin. Kutubxona ushbu tafsilotlarni bilishi talab etilmaydi.
Kutubxona uchun biz tomonimizdan qo'llaniladigan Messenger
traitini implementatsiya qiladigan
narsa kerak xolos.
Faylnomi: src/lib.rs
pub trait Messenger {
fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}
15-20-ro'yxat: Qiymatni qanchalik maksimal qiymatga yaqinligini kuzatish va kerakli darajaga yetganda ogohlantiruvchi kutubxona
Ushbu kodning e'tiborli tomoni shundaki Messenger
traitining send
nomli metodi
xabarning matni hamdaself
ga o'zgarmas referensni oladi. Ushbu trait bizning soxta
obyektimizning implementatisiyasi uchun kerak bo'lgan interfeys hisoblanadi, shu
holatda soxta obyekt haqiqiy obyektga o'xshab ishlatilishi mumkin. Yana bir muhim
tomoni shundaki, biz set_value
ni ko'rinishini LimitTracker
orqali ko'rishimiz mumkin.
Biz xohlaganimizcha o'tkazayotganimizni value
parametri uchun o'zgartirishimiz mumkin,
lekin set_value
biz da'vo qilishimiz mumkin bo'lgan narsani return qilmaydi. Agar biz
Messenger
traitini implementatisiya qiladigan va ma'lum bir qiymatga ega bo'lgan LimitTracker
yaratsak, value
uchun turli raqamlar berganimizda, xabar kerakli xabar ko'rinishida jo'natildi
deya olishni xohlaymiz.
Pochta orqali yoki matn xabar orqali xabar jo'natish o'rniga biz send
ni ishga tushurib yuborilishi kerak bo'lgan xabarni
kuzatish uchun soxta obyekt kerak bo'ladi. Obyektning yangi namunasini yaratishimiz mumkin, soxta obyektdan foydalanadigan
LimitTracker
yaratib, LimitTracker
da set_value
metodini qo'llashimiz va soxta obyekt biz kutgan xabar bor yoki yo'qligin
tekshirib ko'ramiz. 15-21-ro'yxat soxta obyekt implementatsiya qilishga urinishi, lekin borrow tekshiruvchi ruxsat bermasligi ko'rsatilgan:
Faylnomi: src/lib.rs
15-21-ro'yxat: MockMessenger
ning implementatsiya qilishga urinishi, ammo borrow checker bunga ruxsat bermayotgaligi ko'rsatilgan
Ushbu test kodimiz Sent_messages
maydoniga ega boʻlgan MockMessenger
strukturasini belgilaydi va u Vec
ga ega bo'lgan String
qiymatlari bilan joʻnatilishi kerak boʻlgan xabarlarni kuzatib boradi. Shuningdek, biz bo'sh xabarlar ro'yxati bilan boshlanadigan yangi MockMessenger
qiymatlarini yaratishni qulay qilish uchun yangi
funksiyasini aniqlaymiz. Biz bo'sh xabarlar ro'yxati bilan boshlanadigan yangi MockMessenger qiymatlarini yaratamiz. Keyin biz LimitTracker
ga MockMessenger
ni berishimiz uchun MockMessenger
uchun “Messenger” xususiyatini amalga oshiramiz. send
usulining taʼrifida biz uzatilgan xabarni parametr sifatida qabul qilamiz va uni sent_messages
ning MockMessenger
roʻyxatida saqlaymiz.
Sinovda biz LimitTracker
ga value
ni max
qiymatining 75 foizidan ko‘prog‘iga o‘rnatish buyurilganda nima sodir bo‘lishini sinab ko‘ramiz. Birinchidan, biz yangi MockMessenger
ni yaratamiz, u xabarlarning bo'sh ro'yxati bilan boshlanadi. Keyin biz yangi LimitTracker
yaratamiz va unga yangi MockMessenger
va max
qiymati 100 ga teng reference beramiz. 100dan 75dan katta bo'lgan, 80ga teng bo'lgan qiymatli LimitTracker
dagi set_value
metodini ishga tushiramiz. Keyin biz MockMessenger
kuzatayotgan xabarlar roʻyxatida bitta xabar boʻlishi kerakligini taʼkidlaymiz.
Biroq, bu testda ko'rsatilganidek, bitta muammo bor:
$ cargo test
Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference
--> src/lib.rs:58:13
|
2 | fn send(&self, msg: &str);
| ----- help: consider changing that to be a mutable reference: `&mut self`
...
58 | self.sent_messages.push(String::from(message));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
For more information about this error, try `rustc --explain E0596`.
error: could not compile `limit-tracker` due to previous error
warning: build failed, waiting for other jobs to finish...
Biz MockMessenger
ni xabarlarni kuzatib borish uchun o‘zgartira olmaymiz, chunki send
metodi self
ga o'zgarmas refernce oladi. Shuningdek, xato matnidagi &mut self
dan foydalanish taklifini ham qabul qila olmaymiz, chunki send
signaturasi Messenger
traiti taʼrifidagi imzoga toʻgʻri kelmas edi (urunib ko'rganingizda qanaqa xatolik (error message) bo'lishini ko'rishingiz mumkin).
Ushbu holatda bizga ichgi o'zgaruvchanlik yordam berishi mumkin! Biz RefCell<T>
orqali sent_messages
ni joylyamiz, va keyin biz ko'rgan xabarlarni joylashtirish uchun send
metodi sent_messages
ni o'zgartira oladi. 15-22-ro'yxat bu qanday ko'rinishda bo'lishini ko'rsatadi:
Faylnomi: src/lib.rs
15-22-ro'yxat: tashqi qiymat o'zgarmas bo'lganida RefCell<T>
yordamida ichki qiymatni o'zgaruvchan qilish
sent_messages
maydoni endi Vec<String>
oʻrniga RefCell<Vec<String>>
turiga ega.
new
funksiyada biz yangisini yaratamiz RefCell<Vec<String>>
bo'sh vektor atrofidagi misol.
new
funksiyasida biz yangi RefCell<Vec<String>>
instancesini bo'sh vektor atrofida yaratib olamiz.
send
metodini ishga tushirishda, traitning ma'no/tarifiga o'xshash bo'lgan birinchi parametr self
ning o'zgarmas borrowi bo'lib qolaveradi. Vvektor hisoblangan RefCell<Vec<String>>
ninig ichidagi qiymatga o'zgaruvchan reference olish uchun self.sent_messages
ning RefCell<Vec<String>>
dagi borrow_mut
ni ishga tushuramiz. Keyin test paytida yuborilgan xabarlarni kuzatib borish uchun vektorga o'zgaruvchan referenceda push
ni ishga tushirishimiz mumkin.
Tasdiqlash uchun biz qilishimiz kerak bo'lgan oxirgi o'zgarish bu: ichki vektor ichida qancha itemlar borligini ko'rish uchun, vektorga o'zgarmas refrence olish uchunRefCell<Vec<String>>
dagi borrow
ni ishga tushiramiz.
RefCell<T>
dan qanday ishlatish mumkiniligi bilan tanishdik, keling qanday ishlashi haqida o'raganib chiqaylik.
Runtime vaqtidan RefCell<T>
yordamida Borrowlarni kuzatish
O'zgarmas va o'zgaruvchan referencelarni yaratishda biz mos ravishda &
va &mut
sintaksisidan foydalanamiz. RefCell<T>
bilan biz RefCell<T>
ga tegishli xavfsiz API tarkibiga kiruvchi borrow
va borrow_mut
usullaridan foydalanamiz. borrow
metodi Ref<T>
smart pointer turini, borrow_mut
esa RefMut<T>
smart pointer turini qaytaradi. Ikkala tur ham Deref
ni implementatsiya qiladi, shuning uchun biz ularni/doimiy oddiy reference kabi ko'rib chiqishimiz mumkin.
RefCell<T>
hozirda qancha Ref<T>
va RefMut<T>
smart pointerlari faol ekanligini kuzatib boradi. Har safar biz borrow
ishga tushirganimizda, RefCell<T>
qancha o'zgarmas borrowlar faolligini oshiradi. Agar Ref<T>
qiymati chegarasidan chiqib ketsa, o'zgarmas borrowlar soni bittaga kamayadi. Kompilyatsiya vaqtidagi borrowing qoidalari kabi, RefCell<T>
bizga istalgan vaqtda koʻp oʻzgarmas yoki bitta oʻzgaruvchan borrowga ega boʻlish imkonini beradi.
Agar biz referencelarda bo'lgani kabi kompilyator xatosini olishdan ko'ra, ushbu qoidalarni buzishga harakat qilsak, RefCell<T>
amalga oshirilishi runtime vaqtida panic qo'yadi. 15-23 ro'yxatda 15-22 ro'yxatda send
ning implementatsiyasining modifikatsiyasi ko'rsatilgan. RefCell<T>
runtimeda buni amalga oshirishga to'sqinlik qilishini ko‘rsatish uchun biz ataylab bir xil qamrov uchun faol ikkita o‘zgaruvchan borrowni yaratishga harakat qilapmiz.
Faylnomi: src/lib.rs
Listing 15-23: Creating two mutable references in the
same scope to see that RefCell<T>
will panic
borrow_mut
dan qaytarilgan RefMut<T>
smart pointer uchun one_borrow
o'zgaruvchisini yaratamiz. Keyin two_borrow
o'zgaruvchisida xuddi shu tarzda boshqa o'zgaruvchan borrow hosil qilamiz. Bu bir scopeda ikkita o'zgaruvchan reference qiladi, bu aslida mumkin emas. Kutubxonamiz uchun testlarni o'tkazganimizda, 15-23 ro'yxatdagi kod hech qanday xatosiz kompilyatsiya qilinadi, ammo test muvaffaqiyatsiz bo'ladi:
$ cargo test
Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
Finished test [unoptimized + debuginfo] target(s) in 0.91s
Running unittests src/lib.rs (target/debug/deps/limit_tracker-e599811fa246dbde)
running 1 test
test tests::it_sends_an_over_75_percent_warning_message ... FAILED
failures:
---- tests::it_sends_an_over_75_percent_warning_message stdout ----
thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at 'already borrowed: BorrowMutError', src/lib.rs:60:53
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::it_sends_an_over_75_percent_warning_message
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
E'tibor bering ushbu kod quyidagi panicni keltirib chiqardi already borrowed: BorrowMutError
. Bu RefCell<T>
runtime vaqtida borrowing qoidalari buzilishini boshqariishini ko'rsak bo'ladi
Ko'p marta qilganimizdek, ya'ni kompilyatsiya vaqtida emas, balki ish vaqtida borriwing
xatolarini ko'rish, ishlab chiqish jarayonida keyinchalik kodingizda
xatolar topishingiz mumkinligini anglatadi, ya'ni sizning kodingiz productionga
deploy qilinmagungacha. Bundan tashqari, sizning kodingiz kompilyatsiya vaqtida emas, aksincha runtime
vaqtida borrowlarni kuzatib borishi natijasida kichik runtime xatolik bo'lishi mumkin.
Lekin, RefCell<T>
dan foydalanish faqat o'zgaruvchan qiymatlar mumkin bo'lgan kontekstdan foydalanayotgan
vaqtingizdagi xabarlarni kuzatish uchun o'zini o'zgaritira oladigan soxta obyektni yaratish imkonini beradi. Doimiy referencelardan ko'ra ko'proq funksionallikni olishni xohlasangiz RefCell<T>
dan foydalanishingiz mumkin, uning farqli tomomnlariga qaramasdan ham.
Rc<T>
va RefCell<T>
ni birlashtirish orqali ozgaruvchan ma
lumotlarning bir nechta egalariga ega bo`lish
RefCell<T>
dan foydalanishning keng tarqalgan usuli Rc<T>
bilan kombinatsiyadir. Eslatib o'tamiz, Rc<T>
sizga ba'zi ma'lumotlarning bir nechta egalariga ega bo'lish imkonini beradi, lekin u faqat ushbu ma'lumotlarga o'zgarmas ruxsat beradi. Agar sizda RefCell<T>
ga ega Rc<T>
boʻlsa, siz bir nechta egalari boʻlishi mumkin boʻlgan va siz o'zgartira oladigan qiymatni olishingiz mumkin!
Misol uchun, 15-18-sonli ro'yxatdagi kamchiliklar ro'yxati misolini eslang, bu ro'yxatda bir nechta ro'yxatlarga boshqa ro'yxat egaligini ulashishga ruxsat berish uchun Rc<T>
dan foydalanganmiz. Rc<T>
faqat oʻzgarmas qiymatlarga ega boʻlgani uchun biz ularni yaratganimizdan soʻng biz roʻyxatdagi qiymatlarni oʻzgartira olmaymiz. Ro'yxatlardagi qiymatlarni o'zgartirish imkoniyatiga ega bo'lish uchun RefCell<T>
ni qo'shaylik. 15-24-ro'yxat shuni ko'rsatadiki, Cons
ta'rifida RefCell<T>
dan foydalanib, biz barcha ro'yxatlarda saqlangan qiymatni o'zgartirishimiz mumkin:
src/main.rs nomli fayl:
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));
*value.borrow_mut() += 10;
println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}
15-24-ro'yxat: Rc<RefCell<i32>>
yordamida o'zgaruvchan List
yaratish
Keyinchalik to'g'ridan-to'g'ri foydalana olishimiz mumkin bo'lgan Rc<RefCell<i32>>
namunasi (instance) bo'lgan qiymatni yaratamiz value
nomli o'zgaruvchiga joylashtiramiz. Keyin a
bilan birga value
ni o'z ichiga olgan Cons
variantida List
yaratamiz. value
ni klonlashimiz kerak bo'ladi, shunda value
dan a
ga ownershipni (egalik) berishdan yoki value
dan a
borrowga ega bo;lishdan ko'ra a
va value
ning ikkalasi ham ichki 5
ga ownershipga (egalikka) ega bo'ladilar.
15-18-ro'yxatda qilganimizdek, a
ro'yxatini Rc<T>
ichiga o'rab olamiz, shunda b
va c
ro'yxatlarini yaratganimizda, ular ikkalasi ham a
ga murojaat (refer) qilishlari mumkin bo'ladi
a
, b
va c
ro'yxatlarini yaratganimizdan so'ng, biz qiymat
qiymatiga 10 qo'shmoqchimiz. Biz buni 5-bobda muhokama qilgan avtomatik yoʻqotish xususiyatidan foydalanadigan value
da borrow_mut
ni ishga tushurishh orqali, ya'ni Rc<T>
ni ichki RefCell<T>
qiymatiga yo'naltirish orqali amalga oshiramiz ([->
Operatori Qayerda?”]ida ko'ring wheres-the---operator). borrow_mut
usuli RefMut<T>
smart pointerini qaytaradi va biz unda dereference operatoridan foydalanamiz va uning ichki qiymatni o`zgartiramiz.
a
, b
, va c
print qilganimizda, 5dan ko'ra 15lik qiymatni hammasi o'zgaritirish kiritganligini ko'rishimiz mumkin.
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
Finished dev [unoptimized + debuginfo] target(s) in 0.63s
Running `target/debug/cons-list`
a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))
Ushbu uslub juda jozibali! RefCell<T>
dan foydalanib, biz tashqi o'zgarmas List
qiymatiga ega bo'lamiz. Lekin biz RefCell<T>
da uning ichki o'zgaruvchanligiga kirish/boshqarishni ta'minlaydigan metoddan foydalanishimiz mumkin, shunda kerak bo'lganda ma'lumotlarimizni o'zgartirishimiz mumkin. Runtime qarz olish (borrowing) qoidalari bizni ma'lumotlar o'zgaruvchanligidan (data races) himoya qilishini tekshiradi, va ba'zida ma'lumotlar tuzilmalarimizdagi bu moslashuvchanlik uchun biroz tezlikni oshirishga/o'zgaritishga (trading) arziydi. RefCell<T>
ko'p oqimli (multithreaded) kod uchun ishlamaydi, shuni inobatga oling! Mutex<T>
bu RefCell<T>
ning xafsizroq versiyasidir va biz Mutex<T>
ni 16-bobda muhokama qilamiz.