Heapdagi ma'lumotlarni ko'rsatish uchun Box<T>
dan foydalanish
Eng sodda smart pointer bu box bo'lib, uning turi Box<T>
deb yoziladi.
Boxlar sizga ma'lumotlarni stackda emas, balki heapda saqlashga imkon beradi.
Stackda esa heapdagi ma'lumotlariga pointer qoladi. Stack va heap o'rtasidagi
farqni ko'rib chiqish uchun 4-bobga qarang.
Boxlar o'z ma'lumotlarini stackda emas, balki heapda saqlashdan tashqari, ishlash bo'yicha qo'shimcha xarajatlarga ega emas. Lekin ularda ko'p qo'shimcha imkoniyatlar ham yo'q. Siz ulardan ko'pincha quyidagi holatlarda foydalanasiz:
- Agar sizda kompilyatsiya vaqtida o'lchami noma'lum bo'lgan tur mavjud bo'lsa va siz aniq o'lchamni talab qiladigan kontekstda ushbu turdagi qiymatdan foydalanmoqchi bo'lsangiz
- Agar sizda katta hajmdagi maʼlumotlar mavjud boʻlsa va siz egalik huquqini oʻtkazganingizda maʼlumotlardan nusxa olinmasligiga ishonch hosil qilmoqchi bo'lsangiz
- Agar siz biror qiymatga egalik qilmoqchi bo'lsangiz va siz uni ma'lum bir turda bo'lishiga emas, balki ma'lum bir traitni implement qiluvchi tur bo'lishi haqida qayg'ursangiz
Birinchi holatni “Rekursiv turlarni Boxlar bilan qo'llash” bo‘limida ko‘rsatamiz. Ikkinchi holatda, katta hajmdagi ma'lumotlarga egalik huquqini o'tkazish uzoq vaqt talab qilishi mumkin, chunki ma'lumotlar stackdan ko'chiriladi. Bunday vaziyatda ishlashni yaxshilash uchun biz katta hajmdagi ma'lumotlarni heapda box ichida saqlashimiz mumkin. Shundan so'ng, pointer ma'lumotlarining faqat kichik miqdori stackdan ko'chiriladi, heapdagi u reference qilingan ma'lumotlar esa bir joyda qoladi. Uchinchi holat trait object sifatida tanilgan va butun 17-bob shu mavzuga bag'ishlangan, “Turli turdagi qiymatlarga ruxsat beruvchi Trait Objectlaridan foydalanish” o'sha mavzu. Shunday qilib, bu erda o'rgangan narsalaringizni 17-bobda yana qo'llaysiz!
Heapda ma'lumotlarni saqlash uchun Box<T>
dan foydalanish
Box<T>
uchun heap xotiradan foydalanish holatini muhokama qilishdan oldin, biz
sintaksisni va Box<T>
ichida saqlangan qiymatlar bilan qanday o'zaro aloqa
qilishni ko'rib chiqamiz.
15-1 ro'yxatda i32
qiymatini heapda saqlash uchun boxdan qanday foydalanish
ko'rsatilgan:
Fayl nomi: src/main.rs
fn main() { let b = Box::new(5); println!("b = {}", b); }
Biz b
o'zgaruvchini heapda joylashgan 5
ga point qiluvchi Box
qiymatiga ega
bo'lishi uchun e'lon qilamiz. Ushbu dastur b = 5
ni chop etadi; bu holda biz
boxdagi ma'lumotlarga, stackda bo'lgani kabi kirishimiz mumkin. main
ning
oxiridagi b
kabi boxlar scopedan chiqib ketganda, xuddi egalik qilingan
qiymatlarga o'xshab, u ham xotiradan o'chiriladi. O'chirilish ham box (stackda
saqlanuvchi) uchun va u point qiluvchi ma'lumotlar (heapda saqlanuvchi) uchun
ham sodir bo'ladi.
Heapda bitta qiymat saqlash unchalik foydali emas, shuning uchun boxlarni o'zini
bu tarzda ko'pincha ishlatmaysiz. Ko'pincha holatlarda bitta i32
kabi
qiymatlarni stackda saqlash maqsadga muvofiq bo'ladi. Keling, boxlar, agar bizda
boxlar bo'lmasa, ruxsat berilmaydigan turlarni e'lon qilishga imkon beradigan
holatni ko'rib chiqaylik.
Rekursiv turlarni Boxlar bilan qo'llash
Rekursiv turning qiymati o'zining bir qismi sifatida bir xil turdagi boshqa qiymatga ega bo'lishi mumkin. Rekursiv turlar muammo tug'diradi, chunki kompilyatsiya vaqtida Rust tur qancha joy egallashini bilishi kerak. Biroq, rekursiv turdagi qiymatlarni joylashtirish nazariy jihatdan cheksiz davom etishi mumkin, shuning uchun Rust qiymat uchun qancha joy kerakligini bilmaydi. Boxlar ma'lum o'lchamga ega bo'lganligi sababli, biz rekursiv tur ta'rifiga box kiritish orqali rekursiv turlarni qo'llashimiz mumkin.
Rekursiv turga misol sifatida keling, cons listni o'rganamiz. Bu funktsional dasturlash tillarida keng tarqalgan ma'lumotlar turi hisoblanadi. Biz e'lon qiladigan cons list turi rekursiyadan tashqari sodda; shuning uchun biz ishlaydigan misoldagi tushunchalar rekursiv turlarni o'z ichiga olgan murakkab vaziyatlarga tushganingizda foydali bo'ladi.
Cons List haqida batafsil ma'lumot
Cons list Lisp dasturlash tili va uning dialektlaridan kelib chiqqan va
ichma-ich juftliklardan tashkil topgan maʼlumotlar strukturasi boʻlib, linked
listning Lispdagi versiyasi hisoblanadi. Uning nomi Lispdagi cons
funktsiyasidan (“construct function” uchun qisqartma) kelib chiqqan bo'lib,
uning ikkita argumentidan yangi juftlik yaratadi. Qiymat va boshqa juftlikdan
iborat bo'lgan juftlikda cons
ni chaqirish orqali biz rekursiv juftliklardan
iborat bo'lgan cons list tuzishimiz mumkin.
Misol uchun, bu yerda 1, 2, 3 ro'yxatini o'z ichiga olgan cons listining psevdokod ko'rinishi, har bir juft qavs ichida:
(1, (2, (3, Nil)))
Cons listdagi har bir element ikkita elementni o'z ichiga oladi: shu elementning
qiymati va keyingi element. Ro'yxatning oxirgi elementida keyingi elementsiz
faqat Nil
deb nomlangan qiymatdan iborat bo'ladi. Cons list cons
funksiyasini rekursiv chaqirish orqali hosil qilinadi. Rekursiyaning tubidagi
holatini bildiruvchi qoidaga aylangan nom Nil
hisoblanadi. E'tibor bering, bu
6-bobdagi “null” yoki “nil” tushunchasi bilan bir xil emas, ya'ni noto'g'ri yoki
yo'q qiymat.
Cons list ma'lumotlar tuzilmasi Rust-da tez-tez ishlatilmaydi. Ko'pincha Rust-da
sizga elementlar ro'yxati kerak bo'lsa, Vec<T>
foydalanish uchun yaxshiroq
tanlovdir. Boshqa, murakkabroq rekursiv ma'lumot turlaridan foyladanish bir
qancha vaziyatlarda foydalidir, ammo ushbu bobdagi cons listdan boshlab, boxlar
qanday qilib, rekursiv ma'lumot turini e'lon qilishga imkon berishini
o'rganishimiz mumkin.
Ro'yxat 15-2 cons list uchun enum ko'rinishini o'z ichiga oladi. E'tibor bering,
ushbu kod hali kompilyatsiya qilinmaydi, chunki List
turi ma'lum hajmga ega
emas, biz buni tushuntiramiz.
Fayl nomi: src/main.rs
enum List {
Cons(i32, List),
Nil,
}
fn main() {}
Eslatma: Biz ushbu misol maqsadlari uchun faqat
i32
qiymatlarini o'z ichiga olgan cons listni amalga oshirmoqdamiz. Biz 10-bobda muhokama qilganimizdek, har qanday turdagi qiymatlarni saqlashi mumkin bo'lgan cons list turini generiklar yordamida e'lon qilishimiz mumkin edi.
List
turidan foydalanib 1, 2, 3
roʻyxatini saqlash 15-3 ro'yxat kabi
bo'ladi:
Fayl nomi: src/main.rs
enum List {
Cons(i32, List),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil)));
}
Birinchi Cons
qiymati 1
va boshqa List
qiymatiga ega. Bu List
qiymati
2
va boshqa List
qiymatiga ega bo'lgan boshqa Cons
qiymatidir. Ushbu
List
qiymati 3
ni o'z ichiga olgan yana bitta Cons
qiymati va List
qiymati, nihoyat Nil
, ro'yxat oxirini bildiruvchi rekursiv bo'lmagan variant.
Agar biz 15-3 ro'yxatdagi kodni kompilyatsiya qilishga harakat qilsak, biz 15-4 ro'yxatda ko'rsatilgan xatoni olamiz:
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
error[E0072]: recursive type `List` has infinite size
--> src/main.rs:1:1
|
1 | enum List {
| ^^^^^^^^^
2 | Cons(i32, List),
| ---- recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
2 | Cons(i32, Box<List>),
| ++++ +
For more information about this error, try `rustc --explain E0072`.
error: could not compile `cons-list` due to previous error
Xato ushbu tur “cheksiz o'lchamga ega” ekanligini ko'rsatadi. Buning sababi
shundaki, biz List
ni rekursiv variant bilan e'lon qildik: u bevosita o'zining
boshqa qiymatini saqlaydi. Natijada, Rust List
qiymatini saqlash uchun qancha
joy kerakligini aniqlay olmaydi. Keling, nima uchun bu xatoga duch kelganimizni
qismlarga ajratamiz. Birinchidan, Rust rekursiv bo'lmagan turdagi qiymatni
saqlash uchun qancha joy kerakligini qanday hal qilishini ko'rib chiqamiz.
Rekursiv bo'lmagan turning o'lchamini hisoblash
6-bobda enum ta'riflarini muhokama qilganimizdagi 6-2 ro'yxatda e'lon qilingan
Xabar
enumini eslang:
enum Xabar { Chiqish, Kochirish { x: i32, y: i32 }, Yozish(String), RangTanlash(i32, i32, i32), } fn main() {}
Xabar
qiymati uchun qancha joy ajratish kerakligini aniqlash uchun Rust har
bir variantni ko'rib chiqadi va qaysi variantga ko'proq joy kerakligini
aniqlaydi. Rust Xabar::Chiqish
uchun hech qanday joy kerak emasligini,
Xabar::Kochirish
ikkita i32
qiymatini saqlash uchun yetarli joy kerakligini
aniqlaydi va hokazo. Faqat bitta variant qo'llanilishi sababli, Xabar
qiymatiga kerak bo'ladigan eng ko'p joy-bu uning eng katta variantini saqlash
uchun zarur bo'lgan joy hisoblanadi.
Rust 15-2 roʻyxatdagi List
enum kabi rekursiv turga qancha boʻsh joy
kerakligini aniqlashga harakat qilganda nima sodir boʻlishini bu bilan
taqqoslang. Kompilyator i32
turidagi qiymat va List
turidagi qiymatga ega
bo'lgan Cons
variantini ko'rib chiqishdan boshlaydi. Shuning uchun, Cons
uchun i32
va List
o'lchamiga teng bo'sh joy kerak bo'ladi. List
turiga
qancha xotira kerakligini aniqlash uchun kompilyator Cons
variantidan boshlab
variantlarni ko'rib chiqadi. Cons
variantida i32
turidagi qiymat va List
turidagi qiymat mavjud va bu jarayon 15-1-rasmda ko'rsatilganidek, cheksiz davom
etadi.
O'lchami ma'lum bo'lgan rekursiv turni e'lon qilish uchun Box<T>
dan foydalanish
Rust rekursiv e'lon qilingan turlar uchun qancha joy ajratish kerakligini aniqlay olmaganligi sababli, kompilyator ushbu foydali taklif bilan xatolik beradi:
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable
|
2 | Cons(i32, Box<List>),
| ++++ +
Ushbu taklifda "indirection" qiymatni to'g'ridan-to'g'ri saqlash o'rniga, biz pointerni qiymatga saqlash orqali qiymatni bilvosita saqlash uchun ma'lumotlar strukturasini o'zgartirishimiz kerakligini anglatadi.
Box<T>
pointer bo'lgani uchun Rust har doim Box<T>
uchun qancha joy
kerakligini biladi: pointerning o'lchami u ko'rsatayotgan ma'lumotlar miqdoriga
qarab o'zgarmaydi. Bu shuni anglatadiki, biz to'g'ridan-to'g'ri boshqa List
qiymati o'rniga Cons
variantiga Box<T>
qo'yishimiz mumkin. Box<T>
keyingi
List
qiymatiga ishora qiladi, bu qiymat Cons
varianti ichida emas, balki
heapda bo'ladi. G'oyaga ko'ra, bizda hali ham boshqa ro'yxatlarni o'z ichiga
olgan ro'yxatlar bilan yaratilgan ro'yxat mavjud, ammo bu amalga oshirish endi
elementlarni bir-birining ichiga emas, balki bir-birining yoniga joylashtirishga
o'xshaydi.
We can change the definition of the List
enum in Listing 15-2 and the usage of
the List
in Listing 15-3 to the code in Listing 15-5, which will compile:
Biz 15-2 ro'yxatidagi List
enumni va 15-3 ro'yxatidagi List
ning
qo'llanishini 15-5 ro'yxatidagi kodga o'zgartirishimiz mumkin, bu kompilyatsiya
bo'ladi:
Fayl nomi: src/main.rs
enum List { Cons(i32, Box<List>), Nil, } use crate::List::{Cons, Nil}; fn main() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }
Cons
variantiga i32
o'lchamiga va boxdagi pointer ma'lumotlarini saqlash
uchun bo'sh joy kerak. Nil
varianti hech qanday qiymatlarni saqlamaydi,
shuning uchun u Cons
variantiga qaraganda kamroq joy talab qiladi. Endi
bilamizki, har qanday List
qiymati i32
o‘lchamini va boxdagi pointer
ma’lumotlari hajmini egallaydi. Boxdan foydalanib, biz cheksiz, rekursiv
zanjirni buzdik, shuning uchun kompilyator List
qiymatini saqlash uchun
kerakli hajmni aniqlay oladi. 15-2-rasmda Cons
varianti hozir qanday
ko'rinishi ko'rsatilgan.
Boxlar faqat bilvosita va heapda joylashuvni ta'minlaydi; ularda biz boshqa smart pointerlarda ko'radigan boshqa maxsus imkoniyatlar yo'q. Ular, shuningdek, ushbu maxsus imkoniyatlarga ega bo'lgan ishlash xarajatlariga ega emaslar, shuning uchun ular bizga kerak bo'lgan yagona xususiyat bo'lgan cons list kabi holatlarda foydali bo'lishi mumkin. Biz 17-bobda boxlar uchun ko'proq foydalanish holatlarini ko'rib chiqamiz.
Box<T>
turi smart pointerdir, chunki u Deref
xususiyatini amalga oshiradi,
bu esa Box<T>
qiymatlariga reference kabi qarashga imkonini beradi. Box<T>
qiymati scopedan chiqib ketganda, box ko'rsatayotgan heapdagi ma'lumotlar ham
Drop
xususiyatini amalga oshirish tufayli tozalanadi. Ushbu ikki xususiyat biz
ushbu bobning qolgan qismida muhokama qiladigan boshqa smart pointer turlari
tomonidan taqdim etilgan funksionallik uchun yanada muhimroq bo'ladi. Keling,
ushbu ikki xususiyatni batafsil ko'rib chiqaylik.