Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

লাইফটাইম দিয়ে রেফারেন্স ভ্যালিডেইট করা

লাইফটাইম (Lifetimes) হলো আরেক ধরনের জেনেরিক যা আমরা ইতিমধ্যে ব্যবহার করে আসছি। একটি টাইপের কাঙ্ক্ষিত আচরণ আছে কিনা তা নিশ্চিত করার পরিবর্তে, লাইফটাইম নিশ্চিত করে যে রেফারেন্সগুলো যতক্ষণ আমাদের প্রয়োজন ততক্ষণ ভ্যালিড থাকবে।

চ্যাপ্টার ৪-এর "রেফারেন্স এবং বরোয়িং" (References and Borrowing) বিভাগে আমরা একটি বিষয় আলোচনা করিনি, তা হলো Rust-এর প্রতিটি রেফারেন্সের একটি লাইফটাইম থাকে, যা হলো সেই স্কোপ যার জন্য রেফারেন্সটি ভ্যালিড। বেশিরভাগ সময়, লাইফটাইমগুলো উহ্য (implicit) এবং অনুমিত (inferred) থাকে, ঠিক যেমন বেশিরভাগ সময় টাইপগুলো অনুমিত থাকে। আমাদের কেবল তখনই টাইপ অ্যানোটেট করতে হয় যখন একাধিক টাইপ সম্ভব হয়। একইভাবে, আমাদের তখনই লাইফটাইম অ্যানোটেট করতে হয় যখন রেফারেন্সগুলোর লাইফটাইম কয়েকটি ভিন্ন উপায়ে সম্পর্কিত হতে পারে। Rust আমাদের এই সম্পর্কগুলো জেনেরিক লাইফটাইম প্যারামিটার ব্যবহার করে অ্যানোটেট করতে বলে, যাতে রানটাইমে ব্যবহৃত আসল রেফারেন্সগুলো অবশ্যই ভ্যালিড থাকে।

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

লাইফটাইম দিয়ে ড্যাংলিং রেফারেন্স প্রতিরোধ করা

লাইফটাইমের মূল উদ্দেশ্য হলো ড্যাংলিং রেফারেন্স (dangling references) প্রতিরোধ করা, যা একটি প্রোগ্রামকে এমন ডেটা রেফারেন্স করতে বাধ্য করে যা তার উদ্দিষ্ট ডেটা নয়। লিস্টিং ১০-১৬-এর প্রোগ্রামটি বিবেচনা করুন, যার একটি বাইরের স্কোপ এবং একটি ভেতরের স্কোপ রয়েছে।

fn main() {
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {r}");
}

দ্রষ্টব্য: লিস্টিং ১০-১৬, ১০-১৭, এবং ১০-২৩ এর উদাহরণগুলোতে ভ্যারিয়েবল ডিক্লেয়ার করা হয়েছে কোনো প্রাথমিক মান না দিয়েই, তাই ভ্যারিয়েবলের নামটি বাইরের স্কোপে বিদ্যমান থাকে। প্রথম নজরে, এটি Rust-এর কোনো null ভ্যালু না থাকার সাথে সাংঘর্ষিক মনে হতে পারে। তবে, যদি আমরা কোনো ভ্যারিয়েবলকে মান দেওয়ার আগে ব্যবহার করার চেষ্টা করি, তাহলে আমরা একটি কম্পাইল-টাইম এরর পাব, যা দেখায় যে Rust সত্যিই null ভ্যালু অনুমোদন করে না।

বাইরের স্কোপটি r নামে একটি ভ্যারিয়েবল ডিক্লেয়ার করে যার কোনো প্রাথমিক মান নেই, এবং ভেতরের স্কোপটি x নামে একটি ভ্যারিয়েবল ডিক্লেয়ার করে যার প্রাথমিক মান 5। ভেতরের স্কোপের মধ্যে, আমরা r-এর মানকে x-এর একটি রেফারেন্স হিসাবে সেট করার চেষ্টা করি। তারপর ভেতরের স্কোপ শেষ হয়ে যায়, এবং আমরা r-এর মান প্রিন্ট করার চেষ্টা করি। এই কোডটি কম্পাইল হবে না কারণ r যে মানটিকে রেফার করছে তা আমরা ব্যবহার করার চেষ্টা করার আগেই স্কোপের বাইরে চলে গেছে। এখানে এরর মেসেজটি হলো:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0597]: `x` does not live long enough
 --> src/main.rs:6:13
  |
5 |         let x = 5;
  |             - binding `x` declared here
6 |         r = &x;
  |             ^^ borrowed value does not live long enough
7 |     }
  |     - `x` dropped here while still borrowed
8 |
9 |     println!("r: {r}");
  |                  --- borrow later used here

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

এরর মেসেজটি বলছে যে ভ্যারিয়েবল x "যথেষ্ট দীর্ঘজীবী নয়" (does not live long enough)। কারণটি হলো লাইন ৭-এ ভেতরের স্কোপ শেষ হলে x স্কোপের বাইরে চলে যাবে। কিন্তু r বাইরের স্কোপের জন্য এখনও ভ্যালিড; যেহেতু এর স্কোপটি বড়, আমরা বলি যে এটি "দীর্ঘজীবী" (lives longer)। যদি Rust এই কোডটি কাজ করার অনুমতি দিত, r এমন মেমরি রেফারেন্স করত যা x স্কোপের বাইরে যাওয়ার সময় ডিঅ্যালোকেট হয়ে গিয়েছিল, এবং r দিয়ে আমরা যা করার চেষ্টা করতাম তা সঠিকভাবে কাজ করত না। তাহলে Rust কীভাবে নির্ধারণ করে যে এই কোডটি অবৈধ? এটি একটি borrow checker ব্যবহার করে।

দ্য বরো চেকার (The Borrow Checker)

Rust কম্পাইলারের একটি borrow checker আছে যা স্কোপগুলো তুলনা করে নির্ধারণ করে যে সমস্ত borrow ভ্যালিড কিনা। লিস্টিং ১০-১৭ লিস্টিং ১০-১৬-এর মতো একই কোড দেখায় কিন্তু ভ্যারিয়েবলগুলোর লাইফটাইম দেখানো অ্যানোটেশনসহ।

fn main() {
    let r;                // ---------+-- 'a
                          //          |
    {                     //          |
        let x = 5;        // -+-- 'b  |
        r = &x;           //  |       |
    }                     // -+       |
                          //          |
    println!("r: {r}");   //          |
}                         // ---------+

এখানে, আমরা r-এর লাইফটাইমকে 'a এবং x-এর লাইফটাইমকে 'b দিয়ে অ্যানোটেট করেছি। जैसा कि आप देख सकते हैं, ভেতরের 'b ব্লকটি বাইরের 'a লাইফটাইম ব্লকের চেয়ে অনেক ছোট। কম্পাইল টাইমে, Rust দুটি লাইফটাইমের আকার তুলনা করে এবং দেখে যে r-এর লাইফটাইম 'a কিন্তু এটি 'b লাইফটাইমযুক্ত মেমরিকে রেফার করে। প্রোগ্রামটি প্রত্যাখ্যান করা হয়েছে কারণ 'b 'a-এর চেয়ে ছোট: রেফারেন্সের বিষয়বস্তুটি রেফারেন্সের মতো দীর্ঘজীবী নয়।

লিস্টিং ১০-১৮ কোডটি ঠিক করে যাতে এটিতে কোনো ড্যাংলিং রেফারেন্স না থাকে এবং এটি কোনো এরর ছাড়াই কম্পাইল হয়।

fn main() {
    let x = 5;            // ----------+-- 'b
                          //           |
    let r = &x;           // --+-- 'a  |
                          //   |       |
    println!("r: {r}");   //   |       |
                          // --+       |
}                         // ----------+

এখানে, x-এর লাইফটাইম 'b, যা এই ক্ষেত্রে 'a-এর চেয়ে বড়। এর মানে হলো r, x-কে রেফারেন্স করতে পারে কারণ Rust জানে যে r-এর রেফারেন্সটি সবসময় ভ্যালিড থাকবে যতক্ষণ x ভ্যালিড থাকে।

এখন যেহেতু আপনি জানেন রেফারেন্সের লাইফটাইম কোথায় থাকে এবং Rust কীভাবে লাইফটাইম বিশ্লেষণ করে রেফারেন্সগুলো সবসময় ভ্যালিড থাকবে তা নিশ্চিত করে, চলুন ফাংশনের প্রেক্ষাপটে প্যারামিটার এবং রিটার্ন ভ্যালুর জেনেরিক লাইফটাইম অন্বেষণ করি।

ফাংশনে জেনেরিক লাইফটাইম

আমরা একটি ফাংশন লিখব যা দুটি স্ট্রিং স্লাইসের মধ্যে দীর্ঘতরটি রিটার্ন করে। এই ফাংশনটি দুটি স্ট্রিং স্লাইস নেবে এবং একটি একক স্ট্রিং স্লাইস রিটার্ন করবে। আমরা longest ফাংশনটি ইমপ্লিমেন্ট করার পরে, লিস্টিং ১০-১৯-এর কোডটি The longest string is abcd প্রিন্ট করা উচিত।

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {result}");
}

লক্ষ্য করুন যে আমরা চাই ফাংশনটি স্ট্রিং স্লাইস (যা রেফারেন্স) নিক, স্ট্রিং নয়, কারণ আমরা চাই না যে longest ফাংশন তার প্যারামিটারগুলোর মালিকানা (ownership) নিয়ে নিক। লিস্টিং ১০-১৯-এ আমরা যে প্যারামিটারগুলো ব্যবহার করি সেগুলো কেন আমরা চাই সে সম্পর্কে আরও আলোচনার জন্য চ্যাপ্টার ৪-এর "প্যারামিটার হিসাবে স্ট্রিং স্লাইস" (String Slices as Parameters) দেখুন।

যদি আমরা লিস্টিং ১০-২০-তে দেখানো হিসাবে longest ফাংশনটি ইমপ্লিমেন্ট করার চেষ্টা করি, তবে এটি কম্পাইল হবে না।

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {result}");
}

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() { x } else { y }
}

পরিবর্তে, আমরা নিম্নলিখিত এররটি পাই যা লাইফটাইম সম্পর্কে কথা বলে:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0106]: missing lifetime specifier
 --> src/main.rs:9:33
  |
9 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
  |
9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  |           ++++     ++          ++          ++

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

হেল্প টেক্সট প্রকাশ করে যে রিটার্ন টাইপের উপর একটি জেনেরিক লাইফটাইম প্যারামিটার প্রয়োজন কারণ Rust বলতে পারে না যে রিটার্ন করা রেফারেন্সটি x-কে নাকি y-কে রেফার করে। আসলে, আমরাও জানি না, কারণ এই ফাংশনের বডির if ব্লকটি x-এর একটি রেফারেন্স রিটার্ন করে এবং else ব্লকটি y-এর একটি রেফারেন্স রিটার্ন করে!

যখন আমরা এই ফাংশনটি ডিফাইন করছি, তখন আমরা জানি না কোন কংক্রিট (concrete) ভ্যালু এই ফাংশনে পাস করা হবে, তাই আমরা জানি না if কেস না else কেস এক্সিকিউট হবে। আমরা এটাও জানি না যে পাস করা রেফারেন্সগুলোর কংক্রিট লাইফটাইম কী হবে, তাই আমরা লিস্টিং ১০-১৭ এবং ১০-১৮ এর মতো স্কোপগুলো দেখে নির্ধারণ করতে পারি না যে আমাদের রিটার্ন করা রেফারেন্সটি সবসময় ভ্যালিড থাকবে কিনা। বরো চেকারও এটি নির্ধারণ করতে পারে না, কারণ এটি জানে না x এবং y-এর লাইফটাইম রিটার্ন ভ্যালুর লাইফটাইমের সাথে কীভাবে সম্পর্কিত। এই এররটি ঠিক করার জন্য, আমরা জেনেরিক লাইফটাইম প্যারামিটার যোগ করব যা রেফারেন্সগুলোর মধ্যে সম্পর্ক ডিফাইন করবে যাতে বরো চেকার তার বিশ্লেষণ করতে পারে।

লাইফটাইম অ্যানোটেশন সিনট্যাক্স

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

লাইফটাইম অ্যানোটেশনের একটি কিছুটা অস্বাভাবিক সিনট্যাক্স আছে: লাইফটাইম প্যারামিটারের নাম অবশ্যই একটি অ্যাপস্ট্রফি (') দিয়ে শুরু হতে হবে এবং সাধারণত সবগুলো ছোট হাতের এবং খুব ছোট হয়, জেনেরিক টাইপের মতো। বেশিরভাগ লোক প্রথম লাইফটাইম অ্যানোটেশনের জন্য 'a নামটি ব্যবহার করে। আমরা রেফারেন্সের &-এর পরে লাইফটাইম প্যারামিটার অ্যানোটেশন রাখি, অ্যানোটেশনটিকে রেফারেন্সের টাইপ থেকে আলাদা করার জন্য একটি স্পেস ব্যবহার করে।

এখানে কিছু উদাহরণ দেওয়া হলো: একটি i32-এর রেফারেন্স যাতে কোনো লাইফটাইম প্যারামিটার নেই, একটি i32-এর রেফারেন্স যার 'a নামের একটি লাইফটাইম প্যারামিটার আছে, এবং একটি i32-এর মিউটেবল রেফারেন্স যারও 'a লাইফটাইম আছে।

&i32        // একটি রেফারেন্স
&'a i32     // একটি সুস্পষ্ট লাইফটাইমসহ রেফারেন্স
&'a mut i32 // একটি সুস্পষ্ট লাইফটাইমসহ মিউটেবল রেফারেন্স

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

ফাংশন সিগনেচারে লাইফটাইম অ্যানোটেশন

ফাংশন সিগনেচারে লাইফটাইম অ্যানোটেশন ব্যবহার করার জন্য, আমাদের ফাংশনের নাম এবং প্যারামিটার তালিকার মধ্যে অ্যাঙ্গেল ব্র্যাকেটের ভিতরে জেনেরিক লাইফটাইম প্যারামিটার ডিক্লেয়ার করতে হবে, ঠিক যেমন আমরা জেনেরিক টাইপ প্যারামিটারের সাথে করেছিলাম।

আমরা চাই সিগনেচারটি নিম্নলিখিত সীমাবদ্ধতা প্রকাশ করুক: রিটার্ন করা রেফারেন্সটি ততক্ষণ ভ্যালিড থাকবে যতক্ষণ উভয় প্যারামিটার ভ্যালিড থাকবে। এটি প্যারামিটার এবং রিটার্ন ভ্যালুর লাইফটাইমের মধ্যেকার সম্পর্ক। আমরা লাইফটাইমটির নাম দেব 'a এবং তারপর প্রতিটি রেফারেন্সে এটি যোগ করব, যেমনটি লিস্টিং ১০-২১-এ দেখানো হয়েছে।

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {result}");
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

এই কোডটি কম্পাইল হওয়া উচিত এবং আমরা যখন লিস্টিং ১০-১৯-এর main ফাংশনের সাথে এটি ব্যবহার করব তখন কাঙ্ক্ষিত ফলাফল তৈরি করা উচিত।

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

মনে রাখবেন, যখন আমরা এই ফাংশন সিগনেচারে লাইফটাইম প্যারামিটারগুলো নির্দিষ্ট করি, তখন আমরা পাস করা বা রিটার্ন করা কোনো মানের লাইফটাইম পরিবর্তন করছি না। বরং, আমরা নির্দিষ্ট করছি যে বরো চেকার এমন কোনো মান প্রত্যাখ্যান করবে যা এই সীমাবদ্ধতাগুলো মেনে চলে না। লক্ষ্য করুন যে longest ফাংশনটিকে x এবং y ঠিক কতদিন বেঁচে থাকবে তা জানার প্রয়োজন নেই, কেবল এটি জানতে হবে যে 'a-এর জন্য এমন কোনো স্কোপ প্রতিস্থাপন করা যেতে পারে যা এই সিগনেচারটি সন্তুষ্ট করবে।

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

যখন আমরা longest-এ কংক্রিট রেফারেন্স পাস করি, তখন 'a-এর জন্য প্রতিস্থাপিত কংক্রিট লাইফটাইম হলো x-এর স্কোপের সেই অংশ যা y-এর স্কোপের সাথে ওভারল্যাপ করে। অন্য কথায়, জেনেরিক লাইফটাইম 'a সেই কংক্রিট লাইফটাইম পাবে যা x এবং y-এর লাইফটাইমের মধ্যে ছোটটির সমান। যেহেতু আমরা রিটার্ন করা রেফারেন্সটিকে একই লাইফটাইম প্যারামিটার 'a দিয়ে অ্যানোটেট করেছি, তাই রিটার্ন করা রেফারেন্সটিও x এবং y-এর লাইফটাইমের মধ্যে ছোটটির দৈর্ঘ্যের জন্য ভ্যালিড থাকবে।

আসুন দেখি কীভাবে লাইফটাইম অ্যানোটেশনগুলো longest ফাংশনটিকে সীমাবদ্ধ করে, ভিন্ন কংক্রিট লাইফটাইমযুক্ত রেফারেন্স পাস করে। লিস্টিং ১০-২২ একটি সহজ উদাহরণ।

fn main() {
    let string1 = String::from("long string is long");

    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("The longest string is {result}");
    }
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

এই উদাহরণে, string1 বাইরের স্কোপের শেষ পর্যন্ত ভ্যালিড, string2 ভেতরের স্কোপের শেষ পর্যন্ত ভ্যালিড, এবং result এমন কিছুকে রেফারেন্স করে যা ভেতরের স্কোপের শেষ পর্যন্ত ভ্যালিড। এই কোডটি চালান এবং আপনি দেখবেন যে বরো চেকার অনুমোদন করে; এটি কম্পাইল হবে এবং The longest string is long string is long প্রিন্ট করবে।

এরপরে, আসুন একটি উদাহরণ চেষ্টা করি যা দেখায় যে result-এর রেফারেন্সের লাইফটাইম অবশ্যই দুটি আর্গুমেন্টের ছোট লাইফটাইম হতে হবে। আমরা result ভ্যারিয়েবলের ডিক্লেয়ারেশন ভেতরের স্কোপের বাইরে নিয়ে যাব কিন্তু result ভ্যারিয়েবলে মানের অ্যাসাইনমেন্ট string2-এর সাথে স্কোপের ভিতরেই রাখব। তারপর আমরা println! যা result ব্যবহার করে তা ভেতরের স্কোপের বাইরে, ভেতরের স্কোপ শেষ হওয়ার পরে নিয়ে যাব। লিস্টিং ১০-২৩-এর কোডটি কম্পাইল হবে না।

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {result}");
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

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

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0597]: `string2` does not live long enough
 --> src/main.rs:6:44
  |
5 |         let string2 = String::from("xyz");
  |             ------- binding `string2` declared here
6 |         result = longest(string1.as_str(), string2.as_str());
  |                                            ^^^^^^^ borrowed value does not live long enough
7 |     }
  |     - `string2` dropped here while still borrowed
8 |     println!("The longest string is {result}");
  |                                     -------- borrow later used here

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

এররটি দেখায় যে println! স্টেটমেন্টের জন্য result ভ্যালিড হতে হলে, string2-কে বাইরের স্কোপের শেষ পর্যন্ত ভ্যালিড থাকতে হতো। Rust এটি জানে কারণ আমরা ফাংশন প্যারামিটার এবং রিটার্ন ভ্যালুর লাইফটাইম একই লাইফটাইম প্যারামিটার 'a ব্যবহার করে অ্যানোটেট করেছি।

মানুষ হিসেবে, আমরা এই কোডটি দেখে বুঝতে পারি যে string1 string2-এর চেয়ে দীর্ঘ, এবং তাই result string1-এর একটি রেফারেন্স ধারণ করবে। যেহেতু string1 এখনও স্কোপের বাইরে যায়নি, তাই string1-এর একটি রেফারেন্স println! স্টেটমেন্টের জন্য এখনও ভ্যালিড থাকবে। তবে, কম্পাইলার এই ক্ষেত্রে দেখতে পারে না যে রেফারেন্সটি ভ্যালিড। আমরা Rust-কে বলেছি যে longest ফাংশন দ্বারা রিটার্ন করা রেফারেন্সের লাইফটাইম পাস করা রেফারেন্সগুলোর লাইফটাইমের মধ্যে যেটি ছোট তার সমান। তাই, বরো চেকার লিস্টিং ১০-২৩-এর কোডটিকে সম্ভবত একটি অবৈধ রেফারেন্স থাকার কারণে অনুমোদন করে না।

longest ফাংশনে পাস করা রেফারেন্সগুলোর মান এবং লাইফটাইম এবং রিটার্ন করা রেফারেন্সটি কীভাবে ব্যবহৃত হয় তা পরিবর্তন করে আরও পরীক্ষা ডিজাইন করার চেষ্টা করুন। আপনার পরীক্ষাগুলো বরো চেকার পাস করবে কিনা সে সম্পর্কে অনুমান করুন কম্পাইল করার আগে; তারপর পরীক্ষা করে দেখুন আপনি সঠিক ছিলেন কিনা!

লাইফটাইমের দৃষ্টিকোণ থেকে চিন্তা করা

আপনার ফাংশন কী করছে তার উপর নির্ভর করে আপনাকে কীভাবে লাইফটাইম প্যারামিটার নির্দিষ্ট করতে হবে। উদাহরণস্বরূপ, যদি আমরা longest ফাংশনের ইমপ্লিমেন্টেশন পরিবর্তন করে সবসময় দীর্ঘতম স্ট্রিং স্লাইসের পরিবর্তে প্রথম প্যারামিটারটি রিটার্ন করতাম, তাহলে আমাদের y প্যারামিটারে একটি লাইফটাইম নির্দিষ্ট করার প্রয়োজন হতো না। নিম্নলিখিত কোডটি কম্পাইল হবে:

fn main() {
    let string1 = String::from("abcd");
    let string2 = "efghijklmnopqrstuvwxyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {result}");
}

fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    x
}

আমরা x প্যারামিটার এবং রিটার্ন টাইপের জন্য একটি লাইফটাইম প্যারামিটার 'a নির্দিষ্ট করেছি, কিন্তু y প্যারামিটারের জন্য নয়, কারণ y-এর লাইফটাইমের সাথে x-এর লাইফটাইম বা রিটার্ন ভ্যালুর কোনো সম্পর্ক নেই।

একটি ফাংশন থেকে একটি রেফারেন্স রিটার্ন করার সময়, রিটার্ন টাইপের জন্য লাইফটাইম প্যারামিটারটি প্যারামিটারগুলোর মধ্যে একটির লাইফটাইম প্যারামিটারের সাথে মিলতে হবে। যদি রিটার্ন করা রেফারেন্সটি প্যারামিটারগুলোর মধ্যে একটিকে রেফার না করে, তবে এটি অবশ্যই এই ফাংশনের মধ্যে তৈরি একটি মানকে রেফার করবে। তবে, এটি একটি ড্যাংলিং রেফারেন্স হবে কারণ মানটি ফাংশনের শেষে স্কোপের বাইরে চলে যাবে। longest ফাংশনের এই প্রচেষ্টাটি বিবেচনা করুন যা কম্পাইল হবে না:

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {result}");
}

fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str()
}

এখানে, যদিও আমরা রিটার্ন টাইপের জন্য একটি লাইফটাইম প্যারামিটার 'a নির্দিষ্ট করেছি, এই ইমপ্লিমেন্টেশনটি কম্পাইল করতে ব্যর্থ হবে কারণ রিটার্ন ভ্যালুর লাইফটাইম প্যারামিটারগুলোর লাইফটাইমের সাথে মোটেও সম্পর্কিত নয়। এখানে আমরা যে এরর মেসেজটি পাই:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0515]: cannot return value referencing local variable `result`
  --> src/main.rs:11:5
   |
11 |     result.as_str()
   |     ------^^^^^^^^^
   |     |
   |     returns a value referencing data owned by the current function
   |     `result` is borrowed here

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

সমস্যাটি হলো result longest ফাংশনের শেষে স্কোপের বাইরে চলে যায় এবং পরিষ্কার হয়ে যায়। আমরা ফাংশন থেকে result-এর একটি রেফারেন্সও রিটার্ন করার চেষ্টা করছি। এমন কোনো উপায় নেই যে আমরা লাইফটাইম প্যারামিটার নির্দিষ্ট করতে পারি যা ড্যাংলিং রেফারেন্স পরিবর্তন করবে, এবং Rust আমাদের একটি ড্যাংলিং রেফারেন্স তৈরি করতে দেবে না। এই ক্ষেত্রে, সেরা সমাধান হবে একটি ওনড (owned) ডেটা টাইপ রিটার্ন করা, রেফারেন্সের পরিবর্তে, যাতে কলিং ফাংশনটি মান পরিষ্কার করার জন্য দায়ী থাকে।

শেষ পর্যন্ত, লাইফটাইম সিনট্যাক্স বিভিন্ন প্যারামিটার এবং ফাংশনের রিটার্ন ভ্যালুর লাইফটাইম সংযোগ করার বিষয়। একবার সেগুলো সংযুক্ত হয়ে গেলে, Rust-এর কাছে মেমরি-সেফ অপারেশন অনুমোদন করার এবং ড্যাংলিং পয়েন্টার তৈরি বা অন্যথায় মেমরি সেফটি লঙ্ঘনকারী অপারেশনগুলো নিষিদ্ধ করার জন্য যথেষ্ট তথ্য থাকে।

স্ট্রাকট ডেফিনিশনে লাইফটাইম অ্যানোটেশন

এখন পর্যন্ত, আমরা যে struct গুলো ডিফাইন করেছি সেগুলো সবই owned টাইপ ধারণ করে। আমরা রেফারেন্স ধারণ করার জন্য struct ডিফাইন করতে পারি, কিন্তু সেক্ষেত্রে আমাদের struct-এর ডেফিনিশনে প্রতিটি রেফারেন্সের উপর একটি লাইফটাইম অ্যানোটেশন যোগ করতে হবে। লিস্টিং ১০-২৪-এ ImportantExcerpt নামে একটি struct আছে যা একটি স্ট্রিং স্লাইস ধারণ করে।

struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

এই struct-টির part নামে একটি মাত্র ফিল্ড আছে যা একটি স্ট্রিং স্লাইস ধারণ করে, যা একটি রেফারেন্স। জেনেরিক ডেটা টাইপের মতো, আমরা struct-এর নামের পরে অ্যাঙ্গেল ব্র্যাকেটের মধ্যে জেনেরিক লাইফটাইম প্যারামিটারের নাম ডিক্লেয়ার করি যাতে আমরা struct ডেফিনিশনের বডিতে লাইফটাইম প্যারামিটার ব্যবহার করতে পারি। এই অ্যানোটেশনের মানে হলো ImportantExcerpt-এর একটি ইনস্ট্যান্স তার part ফিল্ডে থাকা রেফারেন্সের চেয়ে বেশিদিন বাঁচতে পারে না।

এখানকার main ফাংশনটি ImportantExcerpt struct-এর একটি ইনস্ট্যান্স তৈরি করে যা novel ভ্যারিয়েবলের মালিকানাধীন String-এর প্রথম বাক্যের একটি রেফারেন্স ধারণ করে। ImportantExcerpt ইনস্ট্যান্স তৈরি হওয়ার আগে novel-এর ডেটা বিদ্যমান থাকে। উপরন্তু, ImportantExcerpt স্কোপের বাইরে যাওয়ার পরেও novel স্কোপের বাইরে যায় না, তাই ImportantExcerpt ইনস্ট্যান্সের রেফারেন্সটি ভ্যালিড।

লাইফটাইম এলিশন (Lifetime Elision)

আপনি শিখেছেন যে প্রতিটি রেফারেন্সের একটি লাইফটাইম আছে এবং আপনাকে রেফারেন্স ব্যবহার করে এমন ফাংশন বা struct-এর জন্য লাইফটাইম প্যারামিটার নির্দিষ্ট করতে হবে। তবে, আমাদের লিস্টিং ৪-৯-এ একটি ফাংশন ছিল, যা আবার লিস্টিং ১০-২৫-এ দেখানো হয়েছে, যা লাইফটাইম অ্যানোটেশন ছাড়াই কম্পাইল হয়েছে।

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

fn main() {
    let my_string = String::from("hello world");

    // first_word works on slices of `String`s
    let word = first_word(&my_string[..]);

    let my_string_literal = "hello world";

    // first_word works on slices of string literals
    let word = first_word(&my_string_literal[..]);

    // Because string literals *are* string slices already,
    // this works too, without the slice syntax!
    let word = first_word(my_string_literal);
}

এই ফাংশনটি লাইফটাইম অ্যানোটেশন ছাড়াই কম্পাইল হওয়ার কারণটি ঐতিহাসিক: Rust-এর প্রাথমিক সংস্করণগুলোতে (১.০-এর আগে), এই কোডটি কম্পাইল হতো না কারণ প্রতিটি রেফারেন্সের জন্য একটি সুস্পষ্ট লাইফটাইম প্রয়োজন ছিল। সেই সময়ে, ফাংশন সিগনেচারটি এভাবে লেখা হতো:

fn first_word<'a>(s: &'a str) -> &'a str {

অনেক Rust কোড লেখার পর, Rust টিম দেখতে পেল যে Rust প্রোগ্রামাররা নির্দিষ্ট পরিস্থিতিতে বারবার একই লাইফটাইম অ্যানোটেশন লিখছে। এই পরিস্থিতিগুলো অনুমানযোগ্য ছিল এবং কয়েকটি ডিটারমিনিস্টিক প্যাটার্ন অনুসরণ করত। ডেভেলপাররা এই প্যাটার্নগুলো কম্পাইলারের কোডে প্রোগ্রাম করে দিয়েছে যাতে বরো চেকার এই পরিস্থিতিতে লাইফটাইমগুলো অনুমান করতে পারে এবং সুস্পষ্ট অ্যানোটেশনের প্রয়োজন না হয়।

Rust ইতিহাসের এই অংশটি প্রাসঙ্গিক কারণ ভবিষ্যতে আরও ডিটারমিনিস্টিক প্যাটার্ন ortaya আসতে পারে এবং কম্পাইলারে যোগ করা হতে পারে। ভবিষ্যতে, আরও কম লাইফটাইম অ্যানোটেশনের প্রয়োজন হতে পারে।

Rust-এর রেফারেন্স বিশ্লেষণের মধ্যে প্রোগ্রাম করা প্যাটার্নগুলোকে লাইফটাইম এলিশন রুলস (lifetime elision rules) বলা হয়। এগুলো প্রোগ্রামারদের অনুসরণ করার নিয়ম নয়; এগুলো এমন কিছু বিশেষ কেসের সেট যা কম্পাইলার বিবেচনা করবে, এবং যদি আপনার কোড এই কেসগুলোর সাথে মিলে যায়, তাহলে আপনাকে সুস্পষ্টভাবে লাইফটাইম লেখার প্রয়োজন নেই।

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

ফাংশন বা মেথড প্যারামিটারে লাইফটাইমকে ইনপুট লাইফটাইম (input lifetimes) বলা হয়, এবং রিটার্ন ভ্যালুতে লাইফটাইমকে আউটপুট লাইফটাইম (output lifetimes) বলা হয়।

কম্পাইলার যখন সুস্পষ্ট অ্যানোটেশন না থাকে তখন রেফারেন্সের লাইফটাইম বের করার জন্য তিনটি নিয়ম ব্যবহার করে। প্রথম নিয়মটি ইনপুট লাইফটাইমের জন্য প্রযোজ্য, এবং দ্বিতীয় ও তৃতীয় নিয়ম আউটপুট লাইফটাইমের জন্য প্রযোজ্য। যদি কম্পাইলার তিনটি নিয়ম শেষে পৌঁছানোর পরেও এমন রেফারেন্স থাকে যার লাইফটাইম বের করতে পারে না, কম্পাইলার একটি এরর দিয়ে থেমে যাবে। এই নিয়মগুলো fn ডেফিনিশন এবং impl ব্লকের জন্য প্রযোজ্য।

প্রথম নিয়মটি হলো কম্পাইলার প্রতিটি প্যারামিটার যা একটি রেফারেন্স, তাকে একটি লাইফটাইম প্যারামিটার বরাদ্দ করে। অন্য কথায়, একটি প্যারামিটারযুক্ত একটি ফাংশন একটি লাইফটাইম প্যারামিটার পায়: fn foo<'a>(x: &'a i32); দুটি প্যারামিটারযুক্ত একটি ফাংশন দুটি পৃথক লাইফটাইম প্যারামিটার পায়: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); এবং এভাবেই চলতে থাকে।

দ্বিতীয় নিয়মটি হলো, যদি ঠিক একটি ইনপুট লাইফটাইম প্যারামিটার থাকে, তবে সেই লাইফটাইমটি সমস্ত আউটপুট লাইফটাইম প্যারামিটারে বরাদ্দ করা হয়: fn foo<'a>(x: &'a i32) -> &'a i32

তৃতীয় নিয়মটি হলো, যদি একাধিক ইনপুট লাইফটাইম প্যারামিটার থাকে, কিন্তু তাদের মধ্যে একটি &self বা &mut self হয় কারণ এটি একটি মেথড, তবে self-এর লাইফটাইম সমস্ত আউটপুট লাইফটাইম প্যারামিটারে বরাদ্দ করা হয়। এই তৃতীয় নিয়মটি মেথডগুলোকে পড়া এবং লেখা অনেক সুন্দর করে তোলে কারণ কম প্রতীকের প্রয়োজন হয়।

চলুন আমরা কম্পাইলারের মতো ভান করি। আমরা লিস্টিং ১০-২৫-এর first_word ফাংশনের সিগনেচারে রেফারেন্সের লাইফটাইম বের করার জন্য এই নিয়মগুলো প্রয়োগ করব। সিগনেচারটি রেফারেন্সের সাথে কোনো লাইফটাইম যুক্ত না করে শুরু হয়:

fn first_word(s: &str) -> &str {

তারপর কম্পাইলার প্রথম নিয়মটি প্রয়োগ করে, যা নির্দিষ্ট করে যে প্রতিটি প্যারামিটার তার নিজস্ব লাইফটাইম পায়। আমরা এটিকে যথারীতি 'a বলব, তাই এখন সিগনেচারটি হলো:

fn first_word<'a>(s: &'a str) -> &str {

দ্বিতীয় নিয়মটি প্রযোজ্য কারণ ঠিক একটি ইনপুট লাইফটাইম আছে। দ্বিতীয় নিয়মটি নির্দিষ্ট করে যে একটি ইনপুট প্যারামিটারের লাইফটাইম আউটপুট লাইফটাইমে বরাদ্দ করা হয়, তাই সিগনেচারটি এখন হলো:

fn first_word<'a>(s: &'a str) -> &'a str {

এখন এই ফাংশন সিগনেচারের সমস্ত রেফারেন্সের লাইফটাইম আছে, এবং কম্পাইলার প্রোগ্রামারকে এই ফাংশন সিগনেচারে লাইফটাইম অ্যানোটেট করার প্রয়োজন ছাড়াই তার বিশ্লেষণ চালিয়ে যেতে পারে।

আসুন আরেকটি উদাহরণ দেখি, এবার longest ফাংশনটি ব্যবহার করে যা আমরা লিস্টিং ১০-২০-এ কাজ শুরু করার সময় কোনো লাইফটাইম প্যারামিটার ছিল না:

fn longest(x: &str, y: &str) -> &str {

আসুন প্রথম নিয়মটি প্রয়োগ করি: প্রতিটি প্যারামিটার তার নিজস্ব লাইফটাইম পায়। এবার আমাদের একটির পরিবর্তে দুটি প্যারামিটার আছে, তাই আমাদের দুটি লাইফটাইম আছে:

fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {

আপনি দেখতে পাচ্ছেন যে দ্বিতীয় নিয়মটি প্রযোজ্য নয় কারণ একাধিক ইনপুট লাইফটাইম আছে। তৃতীয় নিয়মটিও প্রযোজ্য নয়, কারণ longest একটি ফাংশন, মেথড নয়, তাই কোনো প্যারামিটার self নয়। তিনটি নিয়ম কাজ করার পরেও, আমরা এখনও বের করতে পারিনি যে রিটার্ন টাইপের লাইফটাইম কী। এই কারণেই আমরা লিস্টিং ১০-২০-এর কোড কম্পাইল করার চেষ্টা করার সময় একটি এরর পেয়েছিলাম: কম্পাইলার লাইফটাইম এলিশন রুলস কাজ করেছে কিন্তু এখনও সিগনেচারের সমস্ত রেফারেন্সের লাইফটাইম বের করতে পারেনি।

যেহেতু তৃতীয় নিয়মটি সত্যিই কেবল মেথড সিগনেচারে প্রযোজ্য, আমরা পরবর্তী অংশে সেই প্রেক্ষাপটে লাইফটাইম দেখব কেন তৃতীয় নিয়মের মানে হলো আমাদের মেথড সিগনেচারে খুব কমই লাইফটাইম অ্যানোটেট করতে হয়।

মেথড ডেফিনিশনে লাইফটাইম অ্যানোটেশন

যখন আমরা লাইফটাইমসহ একটি struct-এ মেথড ইমপ্লিমেন্ট করি, তখন আমরা জেনেরিক টাইপ প্যারামিটারের মতো একই সিনট্যাক্স ব্যবহার করি, যেমনটি লিস্টিং ১০-১১-এ দেখানো হয়েছে। আমরা কোথায় লাইফটাইম প্যারামিটার ডিক্লেয়ার এবং ব্যবহার করি তা নির্ভর করে সেগুলো struct ফিল্ড বা মেথড প্যারামিটার এবং রিটার্ন ভ্যালুর সাথে সম্পর্কিত কিনা তার উপর।

struct ফিল্ডের জন্য লাইফটাইম নাম সবসময় impl কিওয়ার্ডের পরে ডিক্লেয়ার করতে হবে এবং তারপর struct-এর নামের পরে ব্যবহার করতে হবে কারণ সেই লাইফটাইমগুলো struct-এর টাইপের অংশ।

impl ব্লকের ভিতরে মেথড সিগনেচারে, রেফারেন্সগুলো struct-এর ফিল্ডে থাকা রেফারেন্সের লাইফটাইমের সাথে আবদ্ধ হতে পারে, অথবা সেগুলো স্বাধীন হতে পারে। উপরন্তু, লাইফটাইম এলিশন রুলস প্রায়শই মেথড সিগনেচারে লাইফটাইম অ্যানোটেশনের প্রয়োজন হয় না। আসুন আমরা লিস্টিং ১০-২৪-এ সংজ্ঞায়িত ImportantExcerpt নামক struct ব্যবহার করে কিছু উদাহরণ দেখি।

প্রথমে আমরা level নামের একটি মেথড ব্যবহার করব যার একমাত্র প্যারামিটার হলো self-এর একটি রেফারেন্স এবং যার রিটার্ন ভ্যালু একটি i32, যা কোনো কিছুর রেফারেন্স নয়:

struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {announcement}");
        self.part
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

impl-এর পরে লাইফটাইম প্যারামিটার ডিক্লেয়ারেশন এবং টাইপের নামের পরে এর ব্যবহার প্রয়োজন, কিন্তু প্রথম এলিশন রুলের কারণে আমাদের self-এর রেফারেন্সের লাইফটাইম অ্যানোটেট করার প্রয়োজন নেই।

এখানে একটি উদাহরণ যেখানে তৃতীয় লাইফটাইম এলিশন রুল প্রযোজ্য:

struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {announcement}");
        self.part
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

দুটি ইনপুট লাইফটাইম আছে, তাই Rust প্রথম লাইফটাইম এলিশন রুল প্রয়োগ করে এবং &self এবং announcement উভয়কেই তাদের নিজস্ব লাইফটাইম দেয়। তারপর, যেহেতু প্যারামিটারগুলোর মধ্যে একটি &self, রিটার্ন টাইপ &self-এর লাইফটাইম পায়, এবং সমস্ত লাইফটাইম হিসাব করা হয়েছে।

স্ট্যাটিক লাইফটাইম (The Static Lifetime)

একটি বিশেষ লাইফটাইম যা আমাদের আলোচনা করতে হবে তা হলো 'static, যা বোঝায় যে প্রভাবিত রেফারেন্সটি প্রোগ্রামের পুরো সময়কাল ধরে বেঁচে থাকতে পারে। সমস্ত স্ট্রিং লিটারেলের 'static লাইফটাইম থাকে, যা আমরা নিম্নরূপ অ্যানোটেট করতে পারি:

#![allow(unused)]
fn main() {
let s: &'static str = "I have a static lifetime.";
}

এই স্ট্রিংয়ের টেক্সট সরাসরি প্রোগ্রামের বাইনারিতে সংরক্ষণ করা হয়, যা সর্বদা উপলব্ধ। তাই, সমস্ত স্ট্রিং লিটারেলের লাইফটাইম হলো 'static

আপনি এরর মেসেজে 'static লাইফটাইম ব্যবহার করার পরামর্শ দেখতে পারেন। কিন্তু একটি রেফারেন্সের জন্য 'static লাইফটাইম নির্দিষ্ট করার আগে, ভাবুন যে আপনার রেফারেন্সটি আসলে আপনার প্রোগ্রামের পুরো লাইফটাইম বেঁচে থাকে কিনা, এবং আপনি তা চান কিনা। বেশিরভাগ সময়, 'static লাইফটাইম প্রস্তাবকারী একটি এরর মেসেজ একটি ড্যাংলিং রেফারেন্স তৈরি করার চেষ্টা বা উপলব্ধ লাইফটাইমের অমিলের ফলে হয়। এই ধরনের ক্ষেত্রে, সমাধান হলো সেই সমস্যাগুলো ঠিক করা, 'static লাইফটাইম নির্দিষ্ট করা নয়।

জেনেরিক টাইপ প্যারামিটার, ট্রেইট বাউন্ড এবং লাইফটাইম একসাথে

আসুন সংক্ষেপে জেনেরিক টাইপ প্যারামিটার, ট্রেইট বাউন্ড এবং লাইফটাইম একসাথে একটি ফাংশনে নির্দিষ্ট করার সিনট্যাক্স দেখি!

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest_with_an_announcement(
        string1.as_str(),
        string2,
        "Today is someone's birthday!",
    );
    println!("The longest string is {result}");
}

use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: Display,
{
    println!("Announcement! {ann}");
    if x.len() > y.len() { x } else { y }
}

এটি লিস্টিং ১০-২১-এর longest ফাংশন যা দুটি স্ট্রিং স্লাইসের মধ্যে দীর্ঘতরটি রিটার্ন করে। কিন্তু এখন এর ann নামে একটি অতিরিক্ত প্যারামিটার আছে যা জেনেরিক টাইপ T-এর, যা where ক্লজ দ্বারা নির্দিষ্ট করা Display trait ইমপ্লিমেন্ট করে এমন যেকোনো টাইপ দ্বারা পূরণ করা যেতে পারে। এই অতিরিক্ত প্যারামিটারটি {} ব্যবহার করে প্রিন্ট করা হবে, যার কারণে Display trait bound প্রয়োজন। যেহেতু লাইফটাইম একটি ধরনের জেনেরিক, তাই লাইফটাইম প্যারামিটার 'a এবং জেনেরিক টাইপ প্যারামিটার T-এর ডিক্লেয়ারেশন ফাংশনের নামের পরে অ্যাঙ্গেল ব্র্যাকেটের ভিতরে একই তালিকায় যায়।

সারাংশ

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

বিশ্বাস করুন বা না করুন, আমরা এই অধ্যায়ে যে বিষয়গুলো আলোচনা করেছি সে সম্পর্কে আরও অনেক কিছু শেখার আছে: চ্যাপ্টার ১৮ ট্রেইট অবজেক্ট নিয়ে আলোচনা করে, যা ট্রেইট ব্যবহার করার আরেকটি উপায়। লাইফটাইম অ্যানোটেশন জড়িত আরও জটিল পরিস্থিতিও রয়েছে যা আপনার কেবল খুব উন্নত পরিস্থিতিতে প্রয়োজন হবে; সেগুলোর জন্য, আপনার Rust Reference পড়া উচিত। কিন্তু এর পরে, আপনি শিখবেন কীভাবে Rust-এ টেস্ট লিখতে হয় যাতে আপনি নিশ্চিত করতে পারেন যে আপনার কোড যেভাবে কাজ করা উচিত সেভাবে কাজ করছে।