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> va RefCell<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, agar RefCell<T> o'zgarmas bo'lsada RefCell<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 hamdaselfga 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_valueni 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, LimitTrackerda 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

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!"); } } } #[cfg(test)] mod tests { use super::*; struct MockMessenger { sent_messages: Vec<String>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: vec![], } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100); limit_tracker.set_value(80); assert_eq!(mock_messenger.sent_messages.len(), 1); } }

15-21-ro'yxat: MockMessengerning 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 Vecga 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 MockMessengerni berishimiz uchun MockMessenger uchun “Messenger” xususiyatini amalga oshiramiz. send usulining taʼrifida biz uzatilgan xabarni parametr sifatida qabul qilamiz va uni sent_messagesning MockMessenger roʻyxatida saqlaymiz.

Sinovda biz LimitTrackerga valueni 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 LimitTrackerdagi 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 MockMessengerni xabarlarni kuzatib borish uchun o‘zgartira olmaymiz, chunki send metodi selfga 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_messagesni joylyamiz, va keyin biz ko'rgan xabarlarni joylashtirish uchun send metodi sent_messagesni o'zgartira oladi. 15-22-ro'yxat bu qanday ko'rinishda bo'lishini ko'rsatadi:

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!"); } } } #[cfg(test)] mod tests { use super::*; use std::cell::RefCell; struct MockMessenger { sent_messages: RefCell<Vec<String>>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: RefCell::new(vec![]), } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.borrow_mut().push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { // --snip-- let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100); limit_tracker.set_value(80); assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); } }

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 selfning o'zgarmas borrowi bo'lib qolaveradi. Vvektor hisoblangan RefCell<Vec<String>>ninig ichidagi qiymatga o'zgaruvchan reference olish uchun self.sent_messagesning RefCell<Vec<String>>dagi borrow_mutni 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 borrowni 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

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!"); } } } #[cfg(test)] mod tests { use super::*; use std::cell::RefCell; struct MockMessenger { sent_messages: RefCell<Vec<String>>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: RefCell::new(vec![]), } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { let mut one_borrow = self.sent_messages.borrow_mut(); let mut two_borrow = self.sent_messages.borrow_mut(); one_borrow.push(String::from(message)); two_borrow.push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100); limit_tracker.set_value(80); assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); } }

Listing 15-23: Creating two mutable references in the same scope to see that RefCell<T> will panic

borrow_mutdan 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 malumotlarning 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 valueni o'z ichiga olgan Cons variantida List yaratamiz. valueni klonlashimiz kerak bo'ladi, shunda valuedan aga ownershipni (egalik) berishdan yoki valuedan a borrowga ega bo;lishdan ko'ra a va valuening ikkalasi ham ichki 5ga 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 valueda 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.