অ্যাডভান্সড ফাংশন এবং ক্লোজার (Advanced Functions and Closures)

এই বিভাগে ফাংশন এবং ক্লোজার সম্পর্কিত কিছু উন্নত বৈশিষ্ট্য অন্বেষণ করা হয়েছে, যার মধ্যে রয়েছে ফাংশন পয়েন্টার এবং রিটার্নিং ক্লোজার।

ফাংশন পয়েন্টার (Function Pointers)

আমরা ফাংশনে ক্লোজার পাস করার বিষয়ে কথা বলেছি; আপনি ফাংশনে রেগুলার ফাংশনও পাস করতে পারেন! এই কৌশলটি দরকারী যখন আপনি একটি নতুন ক্লোজার সংজ্ঞায়িত করার পরিবর্তে ইতিমধ্যে সংজ্ঞায়িত একটি ফাংশন পাস করতে চান। ফাংশনগুলি fn টাইপে কোয়ার্স করে (ছোট হাতের f দিয়ে), Fn ক্লোজার ট্রেইটের সাথে বিভ্রান্ত হবেন না। fn টাইপকে ফাংশন পয়েন্টার বলা হয়। ফাংশন পয়েন্টার সহ ফাংশন পাস করা আপনাকে অন্য ফাংশনের আর্গুমেন্ট হিসাবে ফাংশন ব্যবহার করার অনুমতি দেবে।

একটি প্যারামিটার যে একটি ফাংশন পয়েন্টার, তা নির্দিষ্ট করার সিনট্যাক্স ক্লোজারের মতোই, যেমনটি Listing 20-28-এ দেখানো হয়েছে, যেখানে আমরা add_one নামে একটি ফাংশন সংজ্ঞায়িত করেছি যা তার প্যারামিটারে এক যোগ করে। do_twice ফাংশনটি দুটি প্যারামিটার নেয়: একটি ফাংশন পয়েন্টার যে কোনও ফাংশনে যা একটি i32 প্যারামিটার নেয় এবং একটি i32 রিটার্ন করে এবং একটি i32 মান। do_twice ফাংশনটি f ফাংশনটিকে দুবার কল করে, এটিকে arg মান পাস করে, তারপর দুটি ফাংশন কলের ফলাফল একসাথে যোগ করে। main ফাংশনটি add_one এবং 5 আর্গুমেন্ট সহ do_twice কল করে।

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {answer}");
}

এই কোডটি The answer is: 12 প্রিন্ট করে। আমরা নির্দিষ্ট করি যে do_twice-এ f প্যারামিটারটি হল একটি fn যা i32 টাইপের একটি প্যারামিটার নেয় এবং একটি i32 রিটার্ন করে। তারপর আমরা do_twice-এর বডিতে f কল করতে পারি। main-এ, আমরা ফাংশনের নাম add_one-কে do_twice-এর প্রথম আর্গুমেন্ট হিসাবে পাস করতে পারি।

ক্লোজারের বিপরীতে, fn হল একটি টাইপ, ট্রেইট নয়, তাই আমরা সরাসরি প্যারামিটার টাইপ হিসাবে fn নির্দিষ্ট করি, ট্রেইট বাউন্ড হিসাবে Fn ট্রেইটগুলির মধ্যে একটি সহ একটি জেনেরিক টাইপ প্যারামিটার ঘোষণা করার পরিবর্তে।

ফাংশন পয়েন্টারগুলি ক্লোজারের তিনটি ট্রেইটই (Fn, FnMut এবং FnOnce) ইমপ্লিমেন্ট করে, যার মানে আপনি সবসময় একটি ফাংশন পয়েন্টারকে একটি ফাংশনের আর্গুমেন্ট হিসাবে পাস করতে পারেন যা একটি ক্লোজার আশা করে। জেনেরিক টাইপ এবং ক্লোজার ট্রেইটগুলির মধ্যে একটি ব্যবহার করে ফাংশন লেখা ভাল যাতে আপনার ফাংশনগুলি ফাংশন বা ক্লোজার উভয়ই গ্রহণ করতে পারে।

বলা বাহুল্য, আপনি যেখানে শুধুমাত্র fn গ্রহণ করতে চান এবং ক্লোজার নয়, তার একটি উদাহরণ হল বাহ্যিক কোডের সাথে ইন্টারফেস করার সময় যেখানে ক্লোজার নেই: C ফাংশনগুলি আর্গুমেন্ট হিসাবে ফাংশন গ্রহণ করতে পারে, কিন্তু C-তে ক্লোজার নেই।

আপনি কোথায় ইনলাইনে সংজ্ঞায়িত একটি ক্লোজার বা একটি নামযুক্ত ফাংশন ব্যবহার করতে পারেন তার একটি উদাহরণ হিসাবে, আসুন স্ট্যান্ডার্ড লাইব্রেরিতে Iterator ট্রেইট দ্বারা সরবরাহ করা map মেথডের একটি ব্যবহার দেখি। সংখ্যার একটি ভেক্টরকে স্ট্রিং-এর ভেক্টরে পরিণত করতে map ফাংশনটি ব্যবহার করার জন্য, আমরা একটি ক্লোজার ব্যবহার করতে পারি, এইভাবে:

fn main() {
    let list_of_numbers = vec![1, 2, 3];
    let list_of_strings: Vec<String> =
        list_of_numbers.iter().map(|i| i.to_string()).collect();
}

অথবা আমরা ক্লোজারের পরিবর্তে map-এর আর্গুমেন্ট হিসাবে একটি ফাংশনের নাম দিতে পারি, এইভাবে:

fn main() {
    let list_of_numbers = vec![1, 2, 3];
    let list_of_strings: Vec<String> =
        list_of_numbers.iter().map(ToString::to_string).collect();
}

লক্ষ্য করুন যে আমাদের অবশ্যই সম্পূর্ণ যোগ্য সিনট্যাক্স ব্যবহার করতে হবে যা আমরা “Advanced Traits”-এ আলোচনা করেছি কারণ to_string নামে একাধিক ফাংশন উপলব্ধ রয়েছে। এখানে, আমরা ToString ট্রেইটে সংজ্ঞায়িত to_string ফাংশনটি ব্যবহার করছি, যা স্ট্যান্ডার্ড লাইব্রেরি যেকোনো টাইপের জন্য ইমপ্লিমেন্ট করেছে যা Display ইমপ্লিমেন্ট করে।

Chapter 6-এর “Enum values” থেকে স্মরণ করুন যে আমরা যে প্রতিটি এনাম ভেরিয়েন্টের নাম সংজ্ঞায়িত করি সেটিও একটি ইনিশিয়ালাইজার ফাংশন হয়ে ওঠে। আমরা এই ইনিশিয়ালাইজার ফাংশনগুলিকে ফাংশন পয়েন্টার হিসাবে ব্যবহার করতে পারি যা ক্লোজার ট্রেইটগুলি ইমপ্লিমেন্ট করে, যার অর্থ হল আমরা ইনিশিয়ালাইজার ফাংশনগুলিকে মেথডগুলির আর্গুমেন্ট হিসাবে নির্দিষ্ট করতে পারি যা ক্লোজার নেয়, এইভাবে:

fn main() {
    enum Status {
        Value(u32),
        Stop,
    }

    let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
}

এখানে আমরা Status::Value-এর ইনিশিয়ালাইজার ফাংশন ব্যবহার করে map-এ কল করা প্রতিটি u32 মানের জন্য Status::Value ইনস্ট্যান্স তৈরি করি। কিছু লোক এই স্টাইলটি পছন্দ করে এবং কিছু লোক ক্লোজার ব্যবহার করতে পছন্দ করে। এগুলি একই কোডে কম্পাইল হয়, তাই আপনার কাছে যেটি পরিষ্কার মনে হয় সেটি ব্যবহার করুন।

ক্লোজার রিটার্ন করা (Returning Closures)

ক্লোজারগুলি ট্রেইট দ্বারা উপস্থাপিত হয়, যার মানে আপনি সরাসরি ক্লোজার রিটার্ন করতে পারবেন না। বেশিরভাগ ক্ষেত্রে যেখানে আপনি একটি ট্রেইট রিটার্ন করতে চাইতে পারেন, আপনি পরিবর্তে ফাংশনের রিটার্ন মান হিসাবে ট্রেইটটি ইমপ্লিমেন্ট করে এমন কংক্রিট টাইপ ব্যবহার করতে পারেন। যাইহোক, আপনি সাধারণত ক্লোজারের সাথে এটি করতে পারবেন না কারণ তাদের সাধারণত একটি কংক্রিট টাইপ থাকে না যা রিটার্নযোগ্য। উদাহরণস্বরূপ, আপনি যদি ক্লোজারটি তার স্কোপ থেকে কোনও মান ক্যাপচার করে তবে ফাংশন পয়েন্টার fn-কে রিটার্ন টাইপ হিসাবে ব্যবহার করার অনুমতি নেই।

পরিবর্তে, আপনি সাধারণত impl Trait সিনট্যাক্স ব্যবহার করবেন যা আমরা Chapter 10-এ শিখেছি। আপনি Fn, FnOnce এবং FnMut ব্যবহার করে যেকোনো ফাংশন টাইপ রিটার্ন করতে পারেন। উদাহরণস্বরূপ, এই কোডটি ঠিকঠাক কাজ করবে:

#![allow(unused)]
fn main() {
fn returns_closure(init: i32) -> impl Fn(i32) -> i32 {
    move |x| x + init
}
}

যাইহোক, আমরা যেমন Chapter 13-এর “Closure Type Inference and Annotation” বিভাগে উল্লেখ করেছি, প্রতিটি ক্লোজারও তার নিজস্ব স্বতন্ত্র টাইপ। যদি আপনাকে একই স্বাক্ষর কিন্তু ভিন্ন ইমপ্লিমেন্টেশন সহ একাধিক ফাংশনের সাথে কাজ করতে হয়, তাহলে আপনাকে তাদের জন্য একটি ট্রেইট অবজেক্ট ব্যবহার করতে হবে:

fn main() {
    let handlers = vec![returns_closure(), returns_initialized_closure(123)];
    for handler in handlers {
        let output = handler(5);
        println!("{output}");
    }
}

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

fn returns_initialized_closure(init: i32) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |x| x + init)
}

এই কোডটি ঠিকঠাক কম্পাইল হবে—কিন্তু আমরা যদি impl Fn(i32) -> i32 নিয়ে কাজ করার চেষ্টা করতাম তাহলে হত না। ট্রেইট অবজেক্ট সম্পর্কে আরও জানতে, Chapter 18-এর “Using Trait Objects That Allow for Values of Different Types” বিভাগটি দেখুন।

এরপর, আসুন ম্যাক্রোগুলির দিকে তাকাই!