রেফারেন্স সাইকেল মেমোরি লিক করতে পারে (Reference Cycles Can Leak Memory)
রাস্টের মেমোরি সেফটি গ্যারান্টি ভুলবশত এমন মেমোরি তৈরি করা কঠিন করে তোলে যা কখনও পরিষ্কার হয় না (যা মেমোরি লিক নামে পরিচিত), কিন্তু অসম্ভব নয়। সম্পূর্ণভাবে মেমোরি লিক প্রতিরোধ করা রাস্টের গ্যারান্টির মধ্যে পড়ে না, যার অর্থ হলো রাস্ট-এ মেমোরি লিক মেমোরি সেফ (memory safe)। আমরা দেখতে পারি যে রাস্ট Rc<T>
এবং RefCell<T>
ব্যবহার করে মেমোরি লিকের অনুমতি দেয়: এমন রেফারেন্স তৈরি করা সম্ভব যেখানে আইটেমগুলো একে অপরকে একটি সাইকেলে (cycle) নির্দেশ করে। এটি মেমোরি লিক তৈরি করে কারণ সাইকেলের প্রতিটি আইটেমের রেফারেন্স কাউন্ট কখনও ০-তে পৌঁছাবে না, এবং ভ্যালুগুলো কখনও ড্রপ হবে না।
একটি রেফারেন্স সাইকেল তৈরি করা
চলুন দেখি কীভাবে একটি রেফারেন্স সাইকেল ঘটতে পারে এবং কীভাবে এটি প্রতিরোধ করা যায়। এর জন্য, আমরা Listing 15-25-এ List
enum-এর সংজ্ঞা এবং একটি tail
মেথড দিয়ে শুরু করব।
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() {}
আমরা Listing 15-5 থেকে List
সংজ্ঞার আরেকটি ভিন্ন সংস্করণ ব্যবহার করছি। Cons
ভ্যারিয়েন্টের দ্বিতীয় উপাদানটি এখন RefCell<Rc<List>>
, যার মানে হলো, Listing 15-24-এর মতো i32
ভ্যালু পরিবর্তন করার ক্ষমতার পরিবর্তে, আমরা একটি Cons
ভ্যারিয়েন্ট যে List
ভ্যালুকে নির্দেশ করছে তা পরিবর্তন করতে চাই। আমরা একটি tail
মেথডও যোগ করছি যাতে আমাদের কাছে Cons
ভ্যারিয়েন্ট থাকলে দ্বিতীয় আইটেমটি অ্যাক্সেস করা সুবিধাজনক হয়।
Listing 15-26-এ, আমরা একটি main
ফাংশন যোগ করছি যা Listing 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()); }
আমরা a
ভ্যারিয়েবলে একটি List
ভ্যালু ধারণকারী একটি Rc<List>
ইনস্ট্যান্স তৈরি করি যার প্রাথমিক লিস্ট হলো 5, Nil
। তারপর আমরা b
ভ্যারিয়েবলে আরেকটি List
ভ্যালু ধারণকারী একটি Rc<List>
ইনস্ট্যান্স তৈরি করি যা 10
ভ্যালুটি ধারণ করে এবং a
-এর লিস্টকে নির্দেশ করে।
আমরা a
-কে পরিবর্তন করি যাতে এটি Nil
-এর পরিবর্তে b
-কে নির্দেশ করে, যার ফলে একটি সাইকেল তৈরি হয়। আমরা এটি tail
মেথড ব্যবহার করে a
-এর RefCell<Rc<List>>
-এর একটি রেফারেন্স পেয়ে করি, যা আমরা link
ভ্যারিয়েবলে রাখি। তারপর আমরা RefCell<Rc<List>>
-এর উপর borrow_mut
মেথড ব্যবহার করে ভেতরের ভ্যালুটি Nil
ভ্যালু ধারণকারী একটি Rc<List>
থেকে b
-এর Rc<List>
-এ পরিবর্তন করি।
যখন আমরা এই কোডটি রান করি, শেষ 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
a
এবং b
উভয়ের Rc<List>
ইনস্ট্যান্সের রেফারেন্স কাউন্ট ২ হয়ে যায় যখন আমরা a
-এর লিস্টকে b
-কে নির্দেশ করার জন্য পরিবর্তন করি। main
-এর শেষে, রাস্ট b
ভ্যারিয়েবলটি ড্রপ করে, যা b
Rc<List>
ইনস্ট্যান্সের রেফারেন্স কাউন্ট ২ থেকে ১-এ কমিয়ে দেয়। এই মুহূর্তে Rc<List>
-এর হিপে থাকা মেমোরি ড্রপ হবে না কারণ এর রেফারেন্স কাউন্ট ১, ০ নয়। তারপর রাস্ট a
-কে ড্রপ করে, যা a
Rc<List>
ইনস্ট্যান্সের রেফারেন্স কাউন্টও ২ থেকে ১-এ কমিয়ে দেয়। এই ইনস্ট্যান্সের মেমোরিও ড্রপ করা যাবে না, কারণ অন্য Rc<List>
ইনস্ট্যান্সটি এখনও এটিকে নির্দেশ করছে। লিস্টের জন্য বরাদ্দ করা মেমোরি চিরকালের জন্য সংগ্রহ করা হবে না। এই রেফারেন্স সাইকেলটি কল্পনা করার জন্য, আমরা Figure 15-4-এ একটি ডায়াগ্রাম তৈরি করেছি।
Figure 15-4: লিস্ট a
এবং b
-এর একটি রেফারেন্স সাইকেল যা একে অপরকে নির্দেশ করছে
আপনি যদি শেষ println!
-টি আনকমেন্ট করে প্রোগ্রামটি রান করেন, রাস্ট এই সাইকেলটি প্রিন্ট করার চেষ্টা করবে যেখানে a
b
-কে নির্দেশ করে, b
a
-কে নির্দেশ করে এবং এভাবে চলতে থাকবে যতক্ষণ না এটি স্ট্যাক ওভারফ্লো (stack overflow) করে।
বাস্তব জগতের একটি প্রোগ্রামের তুলনায়, এই উদাহরণে একটি রেফারেন্স সাইকেল তৈরি করার পরিণতি খুব ভয়াবহ নয়: আমরা রেফারেন্স সাইকেল তৈরি করার পরেই প্রোগ্রামটি শেষ হয়ে যায়। তবে, যদি একটি আরও জটিল প্রোগ্রাম একটি সাইকেলে প্রচুর মেমোরি বরাদ্দ করে এবং এটি দীর্ঘ সময়ের জন্য ধরে রাখে, প্রোগ্রামটি প্রয়োজনের চেয়ে বেশি মেমোরি ব্যবহার করবে এবং সিস্টেমকে অভিভূত করতে পারে, যার ফলে উপলব্ধ মেমোরি শেষ হয়ে যেতে পারে।
রেফারেন্স সাইকেল তৈরি করা সহজ নয়, কিন্তু এটি অসম্ভবও নয়। যদি আপনার কাছে Rc<T>
ভ্যালু ধারণকারী RefCell<T>
ভ্যালু বা ইন্টেরিয়র মিউটেবিলিটি এবং রেফারেন্স কাউন্টিং সহ টাইপের অনুরূপ নেস্টেড সংমিশ্রণ থাকে, আপনাকে নিশ্চিত করতে হবে যে আপনি সাইকেল তৈরি করছেন না; আপনি রাস্টের উপর নির্ভর করতে পারবেন না যে এটি সেগুলো ধরবে। একটি রেফারেন্স সাইকেল তৈরি করা আপনার প্রোগ্রামে একটি লজিক বাগ হবে যা আপনার উচিত অটোমেটেড টেস্ট, কোড রিভিউ এবং অন্যান্য সফটওয়্যার ডেভেলপমেন্ট অনুশীলন ব্যবহার করে কমিয়ে আনা।
রেফারেন্স সাইকেল এড়ানোর আরেকটি সমাধান হলো আপনার ডেটা স্ট্রাকচারগুলো এমনভাবে পুনর্গঠিত করা যাতে কিছু রেফারেন্স মালিকানা প্রকাশ করে এবং কিছু রেফারেন্স করে না। ফলস্বরূপ, আপনি কিছু মালিকানা সম্পর্ক এবং কিছু অ-মালিকানা সম্পর্ক দিয়ে গঠিত সাইকেল রাখতে পারেন, এবং শুধুমাত্র মালিকানা সম্পর্কগুলোই একটি ভ্যালু ড্রপ করা যাবে কিনা তা প্রভাবিত করে। Listing 15-25-এ, আমরা সবসময় চাই Cons
ভ্যারিয়েন্টগুলো তাদের লিস্টের মালিক হোক, তাই ডেটা স্ট্রাকচার পুনর্গঠন করা সম্ভব নয়। চলুন প্যারেন্ট নোড এবং চাইল্ড নোড দিয়ে গঠিত গ্রাফ ব্যবহার করে একটি উদাহরণ দেখি কখন অ-মালিকানা সম্পর্ক রেফারেন্স সাইকেল প্রতিরোধের একটি উপযুক্ত উপায়।
Weak<T>
ব্যবহার করে রেফারেন্স সাইকেল প্রতিরোধ করা
এখন পর্যন্ত, আমরা দেখিয়েছি যে Rc::clone
কল করা একটি Rc<T>
ইনস্ট্যান্সের strong_count
বাড়ায়, এবং একটি Rc<T>
ইনস্ট্যান্স শুধুমাত্র তখনই পরিষ্কার করা হয় যদি এর strong_count
০ হয়। আপনি একটি Rc<T>
ইনস্ট্যান্সের ভেতরের ভ্যালুর একটি দুর্বল রেফারেন্সও (weak reference) তৈরি করতে পারেন Rc::downgrade
কল করে এবং Rc<T>
-এর একটি রেফারেন্স পাস করে। Strong references হলো যেভাবে আপনি একটি Rc<T>
ইনস্ট্যান্সের মালিকানা শেয়ার করতে পারেন। Weak references কোনো মালিকানা সম্পর্ক প্রকাশ করে না, এবং তাদের কাউন্ট একটি Rc<T>
ইনস্ট্যান্স কখন পরিষ্কার করা হবে তা প্রভাবিত করে না। তারা রেফারেন্স সাইকেল তৈরি করবে না কারণ কিছু দুর্বল রেফারেন্স জড়িত কোনো সাইকেল ভেঙে যাবে যখন জড়িত ভ্যালুগুলোর strong reference count ০ হবে।
যখন আপনি Rc::downgrade
কল করেন, আপনি Weak<T>
টাইপের একটি স্মার্ট পয়েন্টার পান। Rc<T>
ইনস্ট্যান্সের strong_count
১ বাড়ানোর পরিবর্তে, Rc::downgrade
কল করা weak_count
১ বাড়ায়। Rc<T>
টাইপ weak_count
ব্যবহার করে ট্র্যাক রাখে কতগুলো Weak<T>
রেফারেন্স বিদ্যমান, strong_count
-এর মতো। পার্থক্য হলো Rc<T>
ইনস্ট্যান্সটি পরিষ্কার করার জন্য weak_count
-এর ০ হওয়ার প্রয়োজন নেই।
যেহেতু Weak<T>
যে ভ্যালুটিকে রেফারেন্স করে তা ড্রপ হয়ে যেতে পারে, তাই Weak<T>
যে ভ্যালুটিকে নির্দেশ করছে তার সাথে কিছু করার জন্য আপনাকে নিশ্চিত করতে হবে যে ভ্যালুটি এখনও বিদ্যমান আছে। এটি Weak<T>
ইনস্ট্যান্সের উপর upgrade
মেথড কল করে করুন, যা একটি Option<Rc<T>>
রিটার্ন করবে। আপনি Some
ফলাফল পাবেন যদি Rc<T>
ভ্যালুটি এখনও ড্রপ না হয়ে থাকে এবং None
ফলাফল পাবেন যদি Rc<T>
ভ্যালুটি ড্রপ হয়ে গিয়ে থাকে। যেহেতু upgrade
একটি Option<Rc<T>>
রিটার্ন করে, রাস্ট নিশ্চিত করবে যে Some
কেস এবং None
কেস উভয়ই হ্যান্ডেল করা হয়েছে, এবং কোনো অবৈধ পয়েন্টার থাকবে না।
একটি উদাহরণ হিসাবে, এমন একটি লিস্ট ব্যবহার করার পরিবর্তে যার আইটেমগুলো কেবল পরবর্তী আইটেম সম্পর্কে জানে, আমরা একটি ট্রি (tree) তৈরি করব যার আইটেমগুলো তাদের চাইল্ড আইটেম এবং তাদের প্যারেন্ট আইটেম সম্পর্কে জানে।
একটি ট্রি ডেটা স্ট্রাকচার তৈরি করা: চাইল্ড নোড সহ একটি Node
শুরুতে, আমরা এমন একটি ট্রি তৈরি করব যার নোডগুলো তাদের চাইল্ড নোড সম্পর্কে জানে। আমরা Node
নামে একটি স্ট্রাকট তৈরি করব যা তার নিজস্ব i32
ভ্যালু এবং তার চাইল্ড Node
ভ্যালুগুলোর রেফারেন্স ধারণ করে:
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
-এ Vec<Rc<Node>>
-এর চারপাশে একটি RefCell<T>
আছে।
এরপরে, আমরা আমাদের স্ট্রাকট সংজ্ঞা ব্যবহার করব এবং 3
মান সহ এবং কোনো চাইল্ড ছাড়া leaf
নামে একটি Node
ইনস্ট্যান্স এবং 5
মান সহ এবং leaf
-কে তার একটি চাইল্ড হিসাবে branch
নামে আরেকটি ইনস্ট্যান্স তৈরি করব, যেমনটি Listing 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
-এর Rc<Node>
-কে ক্লোন করি এবং তা branch
-এ সংরক্ষণ করি, যার মানে হলো leaf
-এর Node
-এর এখন দুটি মালিক: leaf
এবং branch
। আমরা branch.children
-এর মাধ্যমে branch
থেকে leaf
-এ যেতে পারি, কিন্তু leaf
থেকে branch
-এ যাওয়ার কোনো উপায় নেই। কারণ হলো leaf
-এর branch
-এর কোনো রেফারেন্স নেই এবং তারা যে সম্পর্কিত তা জানে না। আমরা চাই leaf
জানুক যে branch
তার প্যারেন্ট। আমরাต่อไป এটি করব।
একটি চাইল্ড থেকে তার প্যারেন্টের একটি রেফারেন্স যোগ করা
চাইল্ড নোডকে তার প্যারেন্ট সম্পর্কে সচেতন করতে, আমাদের Node
স্ট্রাকট সংজ্ঞায় একটি parent
ফিল্ড যোগ করতে হবে। সমস্যা হলো parent
-এর টাইপ কী হবে তা নির্ধারণ করা। আমরা জানি এটি একটি Rc<T>
ধারণ করতে পারে না, কারণ এটি leaf.parent
-কে branch
-কে নির্দেশ করে এবং branch.children
-কে leaf
-কে নির্দেশ করে একটি রেফারেন্স সাইকেল তৈরি করবে, যা তাদের strong_count
মানগুলোকে কখনও ০ হতে দেবে না।
সম্পর্কগুলো অন্যভাবে চিন্তা করলে, একটি প্যারেন্ট নোডের উচিত তার চাইল্ডদের মালিক হওয়া: যদি একটি প্যারেন্ট নোড ড্রপ করা হয়, তার চাইল্ড নোডগুলোও ড্রপ করা উচিত। তবে, একটি চাইল্ডের উচিত নয় তার প্যারেন্টের মালিক হওয়া: যদি আমরা একটি চাইল্ড নোড ড্রপ করি, প্যারেন্টটি তখনও বিদ্যমান থাকা উচিত। এটি 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()); }
একটি নোড তার প্যারেন্ট নোডকে নির্দেশ করতে সক্ষম হবে কিন্তু তার প্যারেন্টের মালিক হবে না। Listing 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
নোড তৈরি করা Listing 15-27-এর মতোই দেখাচ্ছে, parent
ফিল্ডটি ছাড়া: leaf
শুরুতে কোনো প্যারেন্ট ছাড়া থাকে, তাই আমরা একটি নতুন, খালি Weak<Node>
রেফারেন্স ইনস্ট্যান্স তৈরি করি।
এই মুহূর্তে, যখন আমরা upgrade
মেথড ব্যবহার করে leaf
-এর প্যারেন্টের একটি রেফারেন্স পাওয়ার চেষ্টা করি, আমরা একটি None
ভ্যালু পাই। আমরা এটি প্রথম println!
স্টেটমেন্টের আউটপুটে দেখতে পাই:
leaf parent = None
যখন আমরা branch
নোড তৈরি করি, তখন এটির parent
ফিল্ডে একটি নতুন Weak<Node>
রেফারেন্সও থাকবে কারণ branch
-এর কোনো প্যারেন্ট নোড নেই। আমাদের এখনও leaf
branch
-এর একটি চাইল্ড হিসাবে আছে। একবার আমাদের branch
-এ Node
ইনস্ট্যান্সটি থাকলে, আমরা leaf
-কে পরিবর্তন করে তার প্যারেন্টের একটি Weak<Node>
রেফারেন্স দিতে পারি। আমরা leaf
-এর parent
ফিল্ডের RefCell<Weak<Node>>
-এর উপর borrow_mut
মেথড ব্যবহার করি, এবং তারপর আমরা branch
-এর Rc<Node>
থেকে branch
-এর একটি Weak<Node>
রেফারেন্স তৈরি করতে Rc::downgrade
ফাংশন ব্যবহার করি।
যখন আমরা leaf
-এর প্যারেন্ট আবার প্রিন্ট করি, এবার আমরা branch
ধারণকারী একটি Some
ভ্যারিয়েন্ট পাব: এখন leaf
তার প্যারেন্ট অ্যাক্সেস করতে পারে! যখন আমরা leaf
প্রিন্ট করি, আমরা সেই সাইকেলটিও এড়িয়ে যাই যা অবশেষে Listing 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
-এর পরিবর্তনগুলো কল্পনা করা
চলুন দেখি Rc<Node>
ইনস্ট্যান্সগুলোর strong_count
এবং weak_count
মানগুলো কীভাবে পরিবর্তিত হয় একটি নতুন অভ্যন্তরীণ স্কোপ তৈরি করে এবং branch
-এর তৈরিকে সেই স্কোপে সরিয়ে নিয়ে। এটি করার মাধ্যমে, আমরা দেখতে পারি branch
তৈরি হলে এবং তারপর স্কোপের বাইরে চলে গেলে কী হয়। পরিবর্তনগুলো Listing 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), ); }
leaf
তৈরি করার পরে, তার Rc<Node>
-এর একটি strong count of 1 এবং একটি weak count of 0 থাকে। অভ্যন্তরীণ স্কোপে, আমরা branch
তৈরি করি এবং এটিকে leaf
-এর সাথে যুক্ত করি, সেই সময়ে যখন আমরা কাউন্টগুলো প্রিন্ট করি, branch
-এর Rc<Node>
-এর একটি strong count of 1 এবং একটি weak count of 1 থাকবে (leaf.parent
branch
-কে একটি Weak<Node>
দিয়ে নির্দেশ করার জন্য)। যখন আমরা leaf
-এর কাউন্টগুলো প্রিন্ট করি, আমরা দেখব এটির একটি strong count of 2 থাকবে কারণ branch
-এর এখন branch.children
-এ সংরক্ষিত leaf
-এর Rc<Node>
-এর একটি ক্লোন আছে, কিন্তু weak count of 0 থাকবে।
যখন অভ্যন্তরীণ স্কোপ শেষ হয়, branch
স্কোপের বাইরে চলে যায় এবং Rc<Node>
-এর strong count ০-তে কমে যায়, তাই তার Node
ড্রপ হয়ে যায়। leaf.parent
থেকে weak count of 1 Node
ড্রপ হবে কিনা তার উপর কোনো প্রভাব ফেলে না, তাই আমরা কোনো মেমোরি লিক পাই না!
যদি আমরা স্কোপের শেষের পরে leaf
-এর প্যারেন্ট অ্যাক্সেস করার চেষ্টা করি, আমরা আবার None
পাব। প্রোগ্রামের শেষে, leaf
-এর Rc<Node>
-এর একটি strong count of 1 এবং একটি weak count of 0 থাকে কারণ leaf
ভ্যারিয়েবলটি এখন Rc<Node>
-এর একমাত্র রেফারেন্স।
কাউন্ট এবং ভ্যালু ড্রপিং পরিচালনা করার সমস্ত লজিক Rc<T>
এবং Weak<T>
এবং তাদের Drop
ট্রেইটের ইমপ্লিমেন্টেশনে নির্মিত। Node
-এর সংজ্ঞায় একটি চাইল্ড থেকে তার প্যারেন্টের সম্পর্ক একটি Weak<T>
রেফারেন্স হওয়া উচিত তা নির্দিষ্ট করার মাধ্যমে, আপনি রেফারেন্স সাইকেল এবং মেমোরি লিক তৈরি না করে প্যারেন্ট নোডগুলোকে চাইল্ড নোডগুলোকে নির্দেশ করতে এবং এর বিপরীতটি করতে সক্ষম হন।
সারসংক্ষেপ (Summary)
এই অধ্যায়ে আলোচনা করা হয়েছে কীভাবে স্মার্ট পয়েন্টার ব্যবহার করে রাস্টের ডিফল্ট রেফারেন্সের থেকে ভিন্ন গ্যারান্টি এবং ট্রেড-অফ তৈরি করা যায়। Box<T>
টাইপের একটি নির্দিষ্ট সাইজ আছে এবং এটি হিপ-এ বরাদ্দ করা ডেটাকে নির্দেশ করে। Rc<T>
টাইপ হিপের ডেটার রেফারেন্স সংখ্যা ট্র্যাক করে যাতে ডেটার একাধিক মালিক থাকতে পারে। RefCell<T>
টাইপ তার ইন্টেরিয়র মিউটেবিলিটি সহ আমাদের এমন একটি টাইপ দেয় যা আমরা ব্যবহার করতে পারি যখন আমাদের একটি immutable টাইপ প্রয়োজন কিন্তু সেই টাইপের একটি ভেতরের ভ্যালু পরিবর্তন করতে হবে; এটি কম্পাইল টাইমের পরিবর্তে রানটাইমে borrowing-এর নিয়ম প্রয়োগ করে।
এছাড়াও Deref
এবং Drop
ট্রেইট নিয়ে আলোচনা করা হয়েছে, যা স্মার্ট পয়েন্টারগুলোর অনেক কার্যকারিতা সক্ষম করে। আমরা রেফারেন্স সাইকেল যা মেমোরি লিক ঘটাতে পারে এবং Weak<T>
ব্যবহার করে কীভাবে তা প্রতিরোধ করা যায় তা অন্বেষণ করেছি।
যদি এই অধ্যায়টি আপনার আগ্রহ জাগিয়ে তোলে এবং আপনি আপনার নিজস্ব স্মার্ট পয়েন্টার ইমপ্লিমেন্ট করতে চান, আরও দরকারী তথ্যের জন্য ["The Rustonomicon"][nomicon] দেখুন।
এরপরে, আমরা রাস্ট-এ কনকারেন্সি (concurrency) নিয়ে কথা বলব। আপনি এমনকি কয়েকটি নতুন স্মার্ট পয়েন্টার সম্পর্কেও শিখবেন।