Drop Trait-এর সাহায্যে Cleanup-এর সময় কোড চালানো

স্মার্ট পয়েন্টার প্যাটার্নের জন্য গুরুত্বপূর্ণ দ্বিতীয় trait টি হল Drop, যা আপনাকে কাস্টমাইজ করতে দেয় যখন একটি value scope-এর বাইরে চলে যেতে চলেছে তখন কী ঘটবে। আপনি যেকোনো টাইপের জন্য Drop trait-এর একটি implementation provide করতে পারেন এবং সেই কোডটি ফাইল বা নেটওয়ার্ক কানেকশনের মতো রিসোর্স release করতে ব্যবহার করা যেতে পারে।

আমরা স্মার্ট পয়েন্টারগুলোর context-এ Drop introduce করছি কারণ Drop trait-এর functionality প্রায় সব সময় একটি স্মার্ট পয়েন্টার implement করার সময় ব্যবহার করা হয়। উদাহরণস্বরূপ, যখন একটি Box<T> ড্রপ করা হয়, তখন এটি heap-এর সেই space-টি deallocate করবে যেখানে box টি point করছে।

কিছু language-এ, কিছু type-এর জন্য, প্রোগ্রামারকে প্রতিবার সেই type-গুলোর একটি instance ব্যবহার করা শেষ হলে মেমরি বা রিসোর্স free করার জন্য কোড কল করতে হয়। উদাহরণের মধ্যে রয়েছে ফাইল হ্যান্ডেল, সকেট বা লক। যদি তারা ভুলে যায়, তাহলে সিস্টেম ওভারলোড হয়ে যেতে পারে এবং ক্র্যাশ করতে পারে। Rust-এ, আপনি specify করতে পারেন যে একটি value scope-এর বাইরে চলে গেলে একটি particular code-এর অংশ run হবে এবং compiler স্বয়ংক্রিয়ভাবে এই কোডটি insert করবে। ফলস্বরূপ, আপনাকে একটি প্রোগ্রামের সর্বত্র cleanup কোড রাখার বিষয়ে সতর্ক থাকতে হবে না যেখানে একটি particular type-এর instance-এর কাজ শেষ হয়েছে—তবুও আপনি রিসোর্স লিক করবেন না!

আপনি Drop trait implement করে একটি value scope-এর বাইরে চলে গেলে যে কোডটি run হবে তা specify করেন। Drop trait-এর জন্য আপনাকে drop নামক একটি method implement করতে হবে যা self-এর একটি mutable reference নেয়। Rust কখন drop কল করে তা দেখতে, আসুন আপাতত println! স্টেটমেন্ট দিয়ে drop implement করি।

Listing 15-14 একটি CustomSmartPointer struct দেখায় যার একমাত্র কাস্টম functionality হল যে instance টি scope-এর বাইরে চলে গেলে এটি Dropping CustomSmartPointer! প্রিন্ট করবে, এটা দেখানোর জন্য যে Rust কখন drop ফাংশনটি চালায়।

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created.");
}

Drop trait টি prelude-এ অন্তর্ভুক্ত, তাই আমাদের এটিকে scope-এ আনার প্রয়োজন নেই। আমরা CustomSmartPointer-এ Drop trait implement করি এবং drop method-এর জন্য একটি implementation provide করি যা println! কল করে। Drop ফাংশনের body হল সেই জায়গা যেখানে আপনি আপনার type-এর একটি instance scope-এর বাইরে চলে গেলে আপনি যে লজিকটি চালাতে চান সেটি রাখবেন। Rust কখন drop কল করবে তা visually প্রদর্শন করার জন্য আমরা এখানে কিছু text প্রিন্ট করছি।

Main-এ, আমরা CustomSmartPointer-এর দুটি instance তৈরি করি এবং তারপর CustomSmartPointers created প্রিন্ট করি। Main-এর শেষে, CustomSmartPointer-এর আমাদের instance গুলো scope-এর বাইরে চলে যাবে এবং Rust আমাদের drop method-এ রাখা কোডটিকে কল করবে, আমাদের final message প্রিন্ট করবে। মনে রাখবেন যে আমাদের explicit ভাবে drop method কল করার প্রয়োজন ছিল না।

যখন আমরা এই প্রোগ্রামটি চালাই, তখন আমরা নিম্নলিখিত আউটপুট দেখতে পাব:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running `target/debug/drop-example`
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

Rust স্বয়ংক্রিয়ভাবে আমাদের instance গুলো scope-এর বাইরে চলে গেলে আমাদের জন্য drop কল করেছে, আমরা যে কোড specify করেছি সেটি কল করে। Variable গুলো তাদের তৈরির বিপরীত ক্রমে ড্রপ করা হয়, তাই d-কে c-এর আগে ড্রপ করা হয়েছিল। এই উদাহরণের উদ্দেশ্য হল আপনাকে drop method কীভাবে কাজ করে তার একটি visual guide দেওয়া; সাধারণত আপনি একটি print message-এর পরিবর্তে আপনার type-এর যে cleanup কোড চালানো দরকার তা specify করবেন।

std::mem::drop দিয়ে একটি Value-কে তাড়াতাড়ি Drop করা

দুর্ভাগ্যবশত, স্বয়ংক্রিয় drop functionality disable করা straightforward নয়। Drop disable করা সাধারণত প্রয়োজন হয় না; Drop trait-এর মূল বিষয় হল এটি স্বয়ংক্রিয়ভাবে handle করা হয়। যাইহোক, মাঝে মাঝে, আপনি একটি value তাড়াতাড়ি clean up করতে চাইতে পারেন। একটি উদাহরণ হল যখন স্মার্ট পয়েন্টার ব্যবহার করা হয় যা লক manage করে: আপনি হয়তো drop method-কে force করতে চাইতে পারেন যা লক release করে যাতে একই scope-এর অন্যান্য কোড লকটি acquire করতে পারে। Rust আপনাকে Drop trait-এর drop method ম্যানুয়ালি কল করতে দেয় না; পরিবর্তে আপনাকে standard library দ্বারা provide করা std::mem::drop ফাংশনটি কল করতে হবে যদি আপনি একটি value-কে তার scope-এর শেষের আগে ড্রপ করতে বাধ্য করতে চান।

যদি আমরা Listing 15-14 থেকে main ফাংশনটিকে modify করে Drop trait-এর drop method ম্যানুয়ালি কল করার চেষ্টা করি, যেমনটি Listing 15-15-এ দেখানো হয়েছে, তাহলে আমরা একটি compiler error পাব:

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    c.drop();
    println!("CustomSmartPointer dropped before the end of main.");
}

যখন আমরা এই কোডটি compile করার চেষ্টা করি, তখন আমরা এই error টি পাব:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
error[E0040]: explicit use of destructor method
  --> src/main.rs:16:7
   |
16 |     c.drop();
   |       ^^^^ explicit destructor calls not allowed
   |
help: consider using `drop` function
   |
16 |     drop(c);
   |     +++++ ~

For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example` (bin "drop-example") due to 1 previous error

এই error message টি বলে যে আমাদের explicit ভাবে drop কল করার অনুমতি নেই। Error message টি destructor শব্দটি ব্যবহার করে, যেটি একটি instance clean up করে এমন একটি ফাংশনের general programming term। একটি destructor একটি constructor-এর অনুরূপ, যা একটি instance তৈরি করে। Rust-এর drop ফাংশনটি হল একটি particular destructor।

Rust আমাদের explicit ভাবে drop কল করতে দেয় না কারণ Rust এখনও স্বয়ংক্রিয়ভাবে main-এর শেষে value-টিতে drop কল করবে। এটি একটি double free error-এর কারণ হবে কারণ Rust একই value দুবার clean up করার চেষ্টা করবে।

আমরা যখন একটি value scope-এর বাইরে চলে যায় তখন drop-এর স্বয়ংক্রিয় insertion disable করতে পারি না এবং আমরা explicit ভাবে drop method কল করতে পারি না। সুতরাং, যদি আমাদের একটি value-কে তাড়াতাড়ি clean up করতে বাধ্য করতে হয়, তাহলে আমরা std::mem::drop ফাংশনটি ব্যবহার করি।

Std::mem::drop ফাংশনটি Drop trait-এর drop method থেকে আলাদা। আমরা এটিকে argument হিসেবে যে value-টিকে force drop করতে চাই সেটি pass করে কল করি। ফাংশনটি prelude-এ রয়েছে, তাই আমরা Listing 15-15-এর main-কে modify করে drop ফাংশনটিকে কল করতে পারি, যেমনটি Listing 15-16-এ দেখানো হয়েছে:

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    drop(c);
    println!("CustomSmartPointer dropped before the end of main.");
}

এই কোডটি run করলে নিম্নলিখিতগুলো প্রিন্ট হবে:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
     Running `target/debug/drop-example`
CustomSmartPointer created.
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main.

Dropping CustomSmartPointer with data `some data`! text টি CustomSmartPointer created. এবং CustomSmartPointer dropped before the end of main. text-এর মধ্যে প্রিন্ট করা হয়েছে, এটি দেখায় যে drop method code-টি সেই সময়ে c-কে drop করার জন্য কল করা হয়েছে।

আপনি cleanup-কে সুবিধাজনক এবং নিরাপদ করতে Drop trait implementation-এ specify করা কোডটি বিভিন্ন উপায়ে ব্যবহার করতে পারেন: উদাহরণস্বরূপ, আপনি এটি ব্যবহার করে আপনার নিজের মেমরি অ্যালোকেটর তৈরি করতে পারেন! Drop trait এবং Rust-এর ownership system-এর সাহায্যে, আপনাকে clean up করার কথা মনে রাখতে হবে না কারণ Rust এটি স্বয়ংক্রিয়ভাবে করে।

ভুলবশত এখনও ব্যবহৃত value গুলো clean up করার ফলে ഉണ്ടാ হওয়া সমস্যাগুলো নিয়ে আপনাকে চিন্তা করতে হবে না: ownership system যা নিশ্চিত করে যে reference গুলো সব সময় valid, তাও নিশ্চিত করে যে drop শুধুমাত্র একবার কল করা হবে যখন value টি আর ব্যবহার করা হবে না।

এখন যেহেতু আমরা Box<T> এবং স্মার্ট পয়েন্টারগুলোর কিছু বৈশিষ্ট্য পরীক্ষা করেছি, আসুন standard library-তে define করা আরও কয়েকটি স্মার্ট পয়েন্টার দেখি।