چرخههای ارجاعی میتوانند منجر به نشت حافظه شوند
تضمینهای ایمنی حافظه راست ایجاد حافظهای که هرگز پاک نمیشود (که به عنوان نشت حافظه
شناخته میشود) را دشوار میکنند، اما غیرممکن نمیکنند. جلوگیری کامل از نشت حافظه
یکی از تضمینهای راست نیست، به این معنی که نشت حافظه در راست ایمن است. ما میتوانیم
ببینیم که راست اجازه نشت حافظه را میدهد با استفاده از Rc<T>
و RefCell<T>
:
امکان ایجاد ارجاعهایی وجود دارد که آیتمها در آن به یکدیگر در یک چرخه ارجاع میدهند.
این باعث نشت حافظه میشود، زیرا شمارش ارجاع هر آیتم در چرخه هرگز به 0 نمیرسد و
مقادیر هرگز حذف نمیشوند.
ایجاد یک چرخه ارجاعی
بیایید بررسی کنیم که چگونه ممکن است یک چرخهی رفرنس (reference cycle) بهوجود بیاید و چگونه میتوان از آن جلوگیری کرد. این بررسی را با تعریف enum
ی به نام List
و متدی به نام tail
در لیستینگ 15-25 آغاز میکنیم.
use crate::List::{Cons, Nil}; use std::cell::RefCell; use std::rc::Rc; #[derive(Debug)] enum List { Cons(i32, RefCell<Rc<List>>), Nil, } impl List { fn tail(&self) -> Option<&RefCell<Rc<List>>> { match self { Cons(_, item) => Some(item), Nil => None, } } } fn main() {}
RefCell<T>
نگه میدارد تا بتوانیم آنچه که یک متغیر Cons
به آن اشاره میکند را تغییر دهیمما از یک نسخه دیگر از تعریف List
که در فهرست 15-5 آمده بود استفاده میکنیم.
عنصر دوم در متغیر Cons
اکنون RefCell<Rc<List>>
است، به این معنی که به جای
توانایی تغییر مقدار i32
که در فهرست 15-24 داشتیم، میخواهیم مقدار List
را که
یک متغیر Cons
به آن اشاره میکند، تغییر دهیم. همچنین، یک متد tail
اضافه
میکنیم تا دسترسی به آیتم دوم را در صورتی که یک متغیر Cons
داریم، راحتتر کنیم.
در فهرست 15-26، یک تابع main
اضافه میکنیم که از تعاریف فهرست 15-25 استفاده میکند.
این کد لیستی در a
و لیستی در b
ایجاد میکند که به لیست a
اشاره میکند.
سپس لیست در a
را تغییر میدهد تا به b
اشاره کند و یک چرخه ارجاعی ایجاد کند.
در طول این فرآیند، اظهارات println!
وجود دارند که نشان میدهند شمارش ارجاع
در نقاط مختلف چه مقدار است.
use crate::List::{Cons, Nil}; use std::cell::RefCell; use std::rc::Rc; #[derive(Debug)] enum List { Cons(i32, RefCell<Rc<List>>), Nil, } impl List { fn tail(&self) -> Option<&RefCell<Rc<List>>> { match self { Cons(_, item) => Some(item), Nil => None, } } } fn main() { let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); println!("a initial rc count = {}", Rc::strong_count(&a)); println!("a next item = {:?}", a.tail()); let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); println!("a rc count after b creation = {}", Rc::strong_count(&a)); println!("b initial rc count = {}", Rc::strong_count(&b)); println!("b next item = {:?}", b.tail()); if let Some(link) = a.tail() { *link.borrow_mut() = Rc::clone(&b); } println!("b rc count after changing a = {}", Rc::strong_count(&b)); println!("a rc count after changing a = {}", Rc::strong_count(&a)); // Uncomment the next line to see that we have a cycle; // it will overflow the stack. // println!("a next item = {:?}", a.tail()); }
List
که به یکدیگر اشاره میکنندما یک نمونهی Rc<List>
ایجاد میکنیم که یک مقدار از نوع List
را در متغیر a
نگه میدارد و لیست اولیهای به شکل 5, Nil
دارد. سپس یک نمونهی دیگر از Rc<List>
در متغیر b
میسازیم که مقداری برابر با 10
دارد و به لیست موجود در a
اشاره میکند.
ما a
را تغییر میدهیم تا به جای Nil
به b
اشاره کند، و یک چرخه ایجاد میکنیم.
این کار را با استفاده از متد tail
انجام میدهیم تا یک ارجاع به RefCell<Rc<List>>
در a
بگیریم، که آن را در متغیر link
قرار میدهیم. سپس از متد borrow_mut
روی RefCell<Rc<List>>
استفاده میکنیم تا مقدار داخلی را از یک Rc<List>
که
مقدار Nil
را نگه میدارد به Rc<List>
در b
تغییر دهیم.
وقتی این کد را اجرا میکنیم و println!
آخر را به طور موقت کامنت میکنیم، خروجی زیر را دریافت میکنیم:
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.53s
Running `target/debug/cons-list`
a initial rc count = 1
a next item = Some(RefCell { value: Nil })
a rc count after b creation = 2
b initial rc count = 1
b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
b rc count after changing a = 2
a rc count after changing a = 2
تعداد رفرنسهای نمونههای Rc<List>
در هر دو متغیر a
و b
پس از آنکه لیست در a
را طوری تغییر میدهیم که به b
اشاره کند، برابر با ۲ خواهد شد. در پایان تابع main
، Rust متغیر b
را حذف میکند، که شمارندهی رفرنس نمونهی Rc<List>
مربوط به b
را از ۲ به ۱ کاهش میدهد. حافظهای که Rc<List>
در heap نگه میدارد در این لحظه آزاد نخواهد شد، چون شمارندهی رفرنس آن هنوز ۱ است، نه صفر. سپس Rust متغیر a
را حذف میکند، که شمارندهی رفرنس نمونهی Rc<List>
مربوط به a
را نیز از ۲ به ۱ کاهش میدهد. حافظهی این نمونه نیز نمیتواند آزاد شود، زیرا نمونهی دیگر از Rc<List>
هنوز به آن اشاره دارد. در نتیجه، حافظهای که به این لیست اختصاص داده شده است، برای همیشه آزاد نخواهد شد.
برای تصویرسازی این چرخهی رفرنس، دیاگرام زیر را در شکل 15-4 ایجاد کردهایم:
شکل 15-4: یک چرخه ارجاعی از لیستهای a
و b
که به یکدیگر اشاره میکنند
اگر آخرین println!
را از حالت کامنت خارج کرده و برنامه را اجرا کنید، Rust تلاش خواهد کرد این چرخه را چاپ کند؛ چرخهای که در آن a
به b
اشاره میکند، b
به a
و دوباره a
به b
و همینطور ادامه پیدا میکند تا جایی که پشته (stack) پر شده و stack overflow رخ میدهد.
در مقایسه با یک برنامه واقعی، عواقب ایجاد چرخه ارجاعی در این مثال چندان وخیم نیست: درست بعد از اینکه چرخه ارجاعی ایجاد میشود، برنامه پایان مییابد. با این حال، اگر یک برنامه پیچیدهتر مقدار زیادی حافظه در یک چرخه تخصیص دهد و برای مدت طولانی آن را نگه دارد، برنامه بیشتر از حافظهای که نیاز دارد استفاده خواهد کرد و ممکن است سیستم را از حافظه موجود تخلیه کند.
ایجاد چرخههای ارجاعی کار آسانی نیست، اما غیرممکن هم نیست. اگر مقادیر RefCell<T>
داشته باشید که مقادیر Rc<T>
یا ترکیبات مشابهی از انواع با تغییرپذیری داخلی و شمارش ارجاع را در خود جای دهند، باید مطمئن شوید که چرخهای ایجاد نمیکنید؛ نمیتوانید به راست اعتماد کنید که آنها را شناسایی کند. ایجاد چرخه ارجاعی یک اشکال منطقی در برنامه شما خواهد بود که باید با استفاده از تستهای خودکار، بررسی کد، و دیگر شیوههای توسعه نرمافزار، آن را به حداقل برسانید.
یک راهحل دیگر برای جلوگیری از چرخههای ارجاعی، بازسازی ساختار دادههایتان است بهطوری که برخی ارجاعات بیانگر مالکیت باشند و برخی نباشند. به این ترتیب، میتوانید چرخههایی داشته باشید که شامل برخی روابط مالکیت و برخی روابط غیرمالکیت هستند، و تنها روابط مالکیت تعیین میکنند که آیا یک مقدار میتواند حذف شود یا خیر. در فهرست 15-25، ما همیشه میخواهیم که متغیرهای Cons
مالک لیستهایشان باشند، بنابراین بازسازی ساختار داده امکانپذیر نیست. بیایید به یک مثال با استفاده از گرافها که شامل گرههای والد و فرزند هستند نگاه کنیم تا ببینیم چه زمانی روابط غیرمالکیت یک راه مناسب برای جلوگیری از چرخههای ارجاعی هستند.
جلوگیری از چرخههای رفرنس با استفاده از Weak<T>
تا اینجا نشان دادیم که فراخوانی Rc::clone
شمارندهی strong_count
یک نمونهی Rc<T>
را افزایش میدهد، و یک نمونهی Rc<T>
تنها زمانی پاکسازی میشود که مقدار strong_count
آن برابر با صفر باشد. همچنین میتوانید با فراخوانی Rc::downgrade
و ارسال یک رفرنس به Rc<T>
، یک رفرنس ضعیف (weak reference) به مقدار درون یک نمونهی Rc<T>
ایجاد کنید.
رفرنسهای قوی (strong references) روشی برای بهاشتراکگذاری مالکیت یک نمونهی Rc<T>
هستند. در مقابل، رفرنسهای ضعیف رابطهی مالکیتی ایجاد نمیکنند، و شمارش آنها (weak count) هیچ تأثیری در زمان پاکسازی یک نمونهی Rc<T>
ندارد. آنها باعث ایجاد چرخهی رفرنس نمیشوند، زیرا هر چرخهای که شامل برخی weak reference
باشد، زمانی شکسته میشود که شمارندهی قوی (strong_count
) مقادیر درگیر در آن چرخه به صفر برسد.
وقتی Rc::downgrade
را فراخوانی میکنید، یک اسمارت پوینتر از نوع Weak<T>
دریافت میکنید. به جای افزایش شمارش strong_count
در نمونه Rc<T>
به مقدار 1، فراخوانی Rc::downgrade
شمارش weak_count
را به مقدار 1 افزایش میدهد. نوع Rc<T>
از weak_count
برای پیگیری تعداد ارجاعات Weak<T>
موجود استفاده میکند، مشابه strong_count
. تفاوت این است که شمارش weak_count
نیازی به 0 بودن برای پاکسازی نمونه Rc<T>
ندارد.
از آنجا که مقداری که یک Weak<T>
به آن اشاره میکند ممکن است پیش از این پاک شده باشد، برای انجام هر کاری با آن مقدار ابتدا باید مطمئن شوید که هنوز وجود دارد. برای این کار، متد upgrade
را روی یک نمونهی Weak<T>
فراخوانی میکنید؛ این متد یک Option<Rc<T>>
بازمیگرداند. اگر مقدار Rc<T>
هنوز پاک نشده باشد، نتیجهی Some
دریافت خواهید کرد؛ و اگر مقدار Rc<T>
قبلاً پاک شده باشد، نتیجهی None
خواهد بود.
از آنجا که upgrade
یک Option<Rc<T>>
برمیگرداند، Rust شما را ملزم میکند که هر دو حالت Some
و None
را مدیریت کنید، و در نتیجه از بروز اشارهگر نامعتبر جلوگیری میشود.
برای مثال، به جای استفاده از یک لیست که آیتمهای آن فقط درباره آیتم بعدی اطلاع دارند، ما یک درخت ایجاد خواهیم کرد که آیتمهای آن درباره آیتمهای فرزند و والد خود اطلاع دارند.
ایجاد یک ساختار دادهی درختی: یک Node
با گرههای فرزند (Child Nodes)
برای شروع، ما یک درخت با گرههایی ایجاد خواهیم کرد که درباره گرههای فرزند خود اطلاع
دارند. ما یک ساختار به نام Node
ایجاد خواهیم کرد که مقدار i32
خود را نگه میدارد
و همچنین به گرههای فرزند خود ارجاع میدهد:
Filename: src/main.rs
use std::cell::RefCell; use std::rc::Rc; #[derive(Debug)] struct Node { value: i32, children: RefCell<Vec<Rc<Node>>>, } fn main() { let leaf = Rc::new(Node { value: 3, children: RefCell::new(vec![]), }); let branch = Rc::new(Node { value: 5, children: RefCell::new(vec![Rc::clone(&leaf)]), }); }
ما میخواهیم که یک Node
مالک فرزندان خود باشد و همچنین میخواهیم که این مالکیت با
متغیرها به اشتراک گذاشته شود تا بتوانیم مستقیماً به هر Node
در درخت دسترسی داشته
باشیم. برای انجام این کار، آیتمهای Vec<T>
را به عنوان مقادیری از نوع Rc<Node>
تعریف میکنیم. همچنین میخواهیم تغییر دهیم که کدام گرهها فرزندان یک گره دیگر باشند،
بنابراین در children
یک RefCell<T>
در اطراف Vec<Rc<Node>>
قرار میدهیم.
در ادامه، از تعریف struct
خود استفاده میکنیم و یک نمونه از Node
با نام leaf
ایجاد میکنیم که مقدار آن 3
است و هیچ فرزندی ندارد، و یک نمونهی دیگر با نام branch
میسازیم که مقدار آن 5
است و leaf
را بهعنوان یکی از فرزندان خود دارد؛ همانطور که در لیستینگ 15-27 نشان داده شده است.
use std::cell::RefCell; use std::rc::Rc; #[derive(Debug)] struct Node { value: i32, children: RefCell<Vec<Rc<Node>>>, } fn main() { let leaf = Rc::new(Node { value: 3, children: RefCell::new(vec![]), }); let branch = Rc::new(Node { value: 5, children: RefCell::new(vec![Rc::clone(&leaf)]), }); }
leaf
بدون فرزند و یک گره branch
با leaf
به عنوان یکی از فرزندان آنما Rc<Node>
را در leaf
کلون میکنیم و آن را در branch
ذخیره میکنیم، به این
معنی که Node
در leaf
اکنون دو مالک دارد: leaf
و branch
. ما میتوانیم از
branch
به leaf
از طریق branch.children
برسیم، اما هیچ راهی برای رفتن از
leaf
به branch
وجود ندارد. دلیل این است که leaf
هیچ ارجاعی به branch
ندارد و نمیداند که آنها مرتبط هستند. ما میخواهیم که leaf
بداند که branch
والد آن است. این کار را در مرحله بعد انجام خواهیم داد.
افزودن یک ارجاع از فرزند به والد
برای آگاه کردن گره فرزند از والدش، باید یک فیلد parent
به تعریف ساختار Node
خود اضافه کنیم. مشکل در تصمیمگیری در مورد نوع parent
است. میدانیم که نمیتواند شامل یک Rc<T>
باشد، زیرا این امر باعث ایجاد چرخه ارجاعی میشود که در آن leaf.parent
به branch
اشاره میکند و branch.children
به leaf
، که باعث میشود مقادیر strong_count
آنها هرگز به 0 نرسد.
با در نظر گرفتن روابط از دیدگاهی دیگر، یک گره والد باید مالک فرزندان خود باشد: اگر یک گره والد حذف شود، گرههای فرزند آن نیز باید حذف شوند. اما، یک فرزند نباید مالک والدش باشد: اگر یک گره فرزند حذف شود، والد باید همچنان وجود داشته باشد. این مورد برای استفاده از ارجاعات ضعیف (weak references) مناسب است!
بنابراین، به جای Rc<T>
، نوع parent
را از نوع Weak<T>
انتخاب میکنیم، به طور خاص یک RefCell<Weak<Node>>
. اکنون تعریف ساختار Node
ما به این شکل است:
Filename: src/main.rs
use std::cell::RefCell; use std::rc::{Rc, Weak}; #[derive(Debug)] struct Node { value: i32, parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>>, } fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); }
یک گره باید بتواند به گره والد خود اشاره کند، اما مالک آن نباشد.
در لیستینگ 15-28، تابع main
را بهروزرسانی میکنیم تا از این تعریف جدید استفاده کند و گره leaf
بتواند به گره والد خود، یعنی branch
، اشاره کند.
use std::cell::RefCell; use std::rc::{Rc, Weak}; #[derive(Debug)] struct Node { value: i32, parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>>, } fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); }
leaf
با یک رفرنس ضعیف به گره والدش، branch
ایجاد گره leaf
مشابه فهرست 15-27 است با این تفاوت که فیلد parent
: leaf
ابتدا بدون والد شروع میشود، بنابراین یک نمونه جدید و خالی از ارجاع Weak<Node>
ایجاد میکنیم.
در این مرحله، وقتی سعی میکنیم با استفاده از متد upgrade
به والد گره leaf
دسترسی پیدا کنیم، یک مقدار None
دریافت میکنیم. این مورد را در خروجی اولین دستور println!
مشاهده میکنیم:
leaf parent = None
زمانی که گره branch
را ایجاد میکنیم، این گره در فیلد parent
خود دارای یک رفرنس جدید از نوع Weak<Node>
خواهد بود، زیرا branch
هیچ گره والد ندارد. همچنان leaf
یکی از فرزندان branch
است. پس از آنکه نمونهی Node
را در branch
ساختیم، میتوانیم گره leaf
را طوری تغییر دهیم که یک رفرنس Weak<Node>
به والد خود داشته باشد.
برای این کار، از متد borrow_mut
روی RefCell<Weak<Node>>
موجود در فیلد parent
در leaf
استفاده میکنیم، و سپس با استفاده از تابع Rc::downgrade
یک رفرنس Weak<Node>
به branch
میسازیم که از Rc<Node>
موجود در branch
ساخته شده است.
وقتی والد گره leaf
را دوباره چاپ میکنیم، این بار یک متغیر Some
که branch
را
نگه میدارد دریافت میکنیم: اکنون leaf
میتواند به والد خود دسترسی پیدا کند!
هنگامی که leaf
را چاپ میکنیم، همچنین از چرخهای که نهایتاً به سرریز شدن استک
مانند فهرست 15-26 منجر میشد اجتناب میکنیم؛ ارجاعات Weak<Node>
بهصورت (Weak)
چاپ میشوند:
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },
children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },
children: RefCell { value: [] } }] } })
نبود خروجی بینهایت نشان میدهد که این کد چرخه ارجاعی ایجاد نکرده است. همچنین میتوانیم
این را با مشاهده مقادیری که از فراخوانی Rc::strong_count
و Rc::weak_count
دریافت میکنیم، تأیید کنیم.
تجسم تغییرات در strong_count
و weak_count
بیایید بررسی کنیم که چگونه مقادیر strong_count
و weak_count
در نمونههای Rc<Node>
تغییر میکنند؛ برای این کار، یک بلاک داخلی جدید ایجاد میکنیم و ساخت گره branch
را به درون این بلاک منتقل میکنیم. با این کار میتوانیم مشاهده کنیم که چه اتفاقی میافتد زمانی که branch
ایجاد میشود و سپس پس از خارج شدن از حوزهی دید (scope) حذف میگردد. این تغییرات در لیستینگ 15-29 نشان داده شدهاند.
use std::cell::RefCell; use std::rc::{Rc, Weak}; #[derive(Debug)] struct Node { value: i32, parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>>, } fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); { let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!( "branch strong = {}, weak = {}", Rc::strong_count(&branch), Rc::weak_count(&branch), ); println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); } println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); }
branch
در یک دامنه داخلی و بررسی شمارش ارجاعات قوی و ضعیفپس از ایجاد leaf
، مقدار strong_count
برای Rc<Node>
آن برابر با ۱ و مقدار weak_count
برابر با ۰ است. در بلاک داخلی، گرهی branch
را ایجاد میکنیم و آن را به leaf
مرتبط میسازیم؛ در این مرحله وقتی شمارندهها را چاپ کنیم، Rc<Node>
مربوط به branch
دارای strong_count
برابر با ۱ و weak_count
برابر با ۱ خواهد بود (بهخاطر اینکه leaf.parent
به branch
با یک Weak<Node>
اشاره میکند). همچنین وقتی شمارندهها را در leaf
چاپ کنیم، مشاهده خواهیم کرد که strong_count
آن برابر با ۲ است، زیرا branch
اکنون یک کلون از Rc<Node>
مربوط به leaf
را در branch.children
نگه میدارد، اما مقدار weak_count
آن همچنان ۰ باقی میماند.
وقتی دامنه داخلی به پایان میرسد، branch
از دامنه خارج میشود و شمارش قوی
Rc<Node>
به 0 کاهش مییابد، بنابراین Node
آن حذف میشود. شمارش ضعیف 1 از
leaf.parent
تأثیری بر اینکه آیا Node
حذف میشود ندارد، بنابراین هیچ نشت
حافظهای نخواهیم داشت!
اگر بعد از پایان بلاک (scope) تلاش کنیم به والد leaf
دسترسی پیدا کنیم، دوباره مقدار None
دریافت خواهیم کرد. در انتهای برنامه، مقدار strong_count
برای Rc<Node>
در leaf
برابر با ۱ و مقدار weak_count
برابر با ۰ خواهد بود، زیرا متغیر leaf
اکنون تنها رفرنس به آن Rc<Node>
است.
تمام منطق مدیریت شمارشها و حذف مقدار درون Rc<T>
و Weak<T>
و پیادهسازیهای
ویژگی Drop
آنها تعبیه شده است. با مشخص کردن اینکه رابطه از یک فرزند به والد
آن باید یک ارجاع Weak<T>
باشد در تعریف Node
، میتوانید گرههای والد را
به گرههای فرزند و بالعکس ارجاع دهید بدون ایجاد یک چرخه ارجاعی و نشت حافظه.
خلاصه
این فصل نحوه استفاده از اسمارت پوینترها برای ارائه تضمینها و مبادلات متفاوت
از آنچه که راست به طور پیشفرض با ارجاعهای معمولی ارائه میدهد را پوشش داد.
نوع Box<T>
دارای اندازه مشخصی است و به دادههای تخصیصیافته در heap اشاره میکند.
نوع Rc<T>
تعداد ارجاعها به دادهها در heap را پیگیری میکند تا دادهها بتوانند
چندین مالک داشته باشند. نوع RefCell<T>
با تغییرپذیری داخلی خود به ما نوعی
میدهد که میتوانیم زمانی که به یک نوع غیرقابلتغییر نیاز داریم اما باید مقدار
درونی آن نوع را تغییر دهیم، استفاده کنیم؛ همچنین قوانین وامدهی را در زمان اجرا
به جای زمان کامپایل اعمال میکند.
همچنین، ویژگیهای Deref
و Drop
که بسیاری از قابلیتهای اسمارت پوینترها را
ممکن میسازند، مورد بحث قرار گرفتند. ما چرخههای ارجاعی که میتوانند باعث
نشت حافظه شوند و نحوه جلوگیری از آنها با استفاده از Weak<T>
را بررسی کردیم.
اگر این فصل علاقه شما را برانگیخته و میخواهید اسمارت پوینترهای خود را پیادهسازی کنید، به “The Rustonomicon” برای اطلاعات مفید بیشتر مراجعه کنید.
در فصل بعدی، درباره همزمانی (concurrency) در راست صحبت خواهیم کرد. حتی با چند اسمارت پوینتر جدید نیز آشنا خواهید شد.