লাইফটাইম দিয়ে রেফারেন্স বৈধ করা (Validating References with Lifetimes)

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

চ্যাপ্টার ৪-এর “রেফারেন্স এবং বোরোয়িং” বিভাগে আমরা যে একটি বিশদ আলোচনা করিনি তা হল, Rust-এর প্রতিটি রেফারেন্সের একটি লাইফটাইম (lifetime) রয়েছে, যেটি হল সেই স্কোপ যার জন্য সেই রেফারেন্সটি বৈধ। বেশিরভাগ সময়, লাইফটাইমগুলো উহ্য এবং অনুমিত হয়, ঠিক যেমন বেশিরভাগ সময় টাইপগুলো অনুমিত হয়। একাধিক টাইপ সম্ভব হলেই কেবল আমাদের টাইপগুলো অ্যানোটেট করতে হয়। একইভাবে, যখন রেফারেন্সগুলোর লাইফটাইম কয়েকটি ভিন্ন উপায়ে সম্পর্কিত হতে পারে তখন আমাদের লাইফটাইমগুলো অ্যানোটেট করতে হয়। Rust চায় যে আমরা জেনেরিক লাইফটাইম প্যারামিটার ব্যবহার করে সম্পর্কগুলো অ্যানোটেট করি যাতে এটি নিশ্চিত করা যায় যে রানটাইমে ব্যবহৃত প্রকৃত রেফারেন্সগুলো অবশ্যই বৈধ হবে।

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

লাইফটাইম দিয়ে ড্যাংলিং রেফারেন্স প্রতিরোধ করা (Preventing Dangling References with Lifetimes)

লাইফটাইমের মূল লক্ষ্য হল ড্যাংলিং রেফারেন্স (dangling references) প্রতিরোধ করা, যা একটি প্রোগ্রামকে তার উদ্দিষ্ট ডেটা ছাড়া অন্য ডেটা রেফারেন্স করতে বাধ্য করে। Listing 10-16-এর প্রোগ্রামটি বিবেচনা করুন, যেখানে একটি আউটার স্কোপ (outer scope) এবং একটি ইনার স্কোপ (inner scope) রয়েছে।

fn main() {
    let r;

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

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

দ্রষ্টব্য: Listing 10-16, 10-17 এবং 10-23-এর উদাহরণগুলো ভেরিয়েবল ঘোষণা করে সেগুলোকে প্রাথমিক মান না দিয়ে, তাই ভেরিয়েবলের নামটি আউটার স্কোপে বিদ্যমান। প্রথম দেখায়, এটি Rust-এর কোনো নাল মান না থাকার সাথে সাংঘর্ষিক বলে মনে হতে পারে। যাইহোক, যদি আমরা একটি ভেরিয়েবলকে মান দেওয়ার আগে ব্যবহার করার চেষ্টা করি, তাহলে আমরা একটি কম্পাইল-টাইম এরর পাব, যা দেখায় যে Rust প্রকৃতপক্ষে নাল মানগুলোর অনুমতি দেয় না।

আউটার স্কোপটি 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 ভেরিয়েবলটি "যথেষ্ট দিন বাঁচে না"। এর কারণ হল ইনার স্কোপটি ৭ নম্বর লাইনে শেষ হয়ে গেলে x স্কোপের বাইরে চলে যাবে। কিন্তু r এখনও আউটার স্কোপের জন্য বৈধ; যেহেতু এর স্কোপটি বড়, তাই আমরা বলি যে এটি "বেশি দিন বাঁচে"। যদি Rust এই কোডটিকে কাজ করার অনুমতি দিত, তাহলে x স্কোপের বাইরে চলে গেলে r এমন মেমরিকে রেফার করত যা ডিলোক্যাট করা হয়েছে এবং আমরা r দিয়ে যা কিছু করার চেষ্টা করতাম তা সঠিকভাবে কাজ করত না। তাহলে Rust কীভাবে নির্ধারণ করে যে এই কোডটি অবৈধ? এটি একটি বোরো চেকার (borrow checker) ব্যবহার করে।

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

Rust কম্পাইলারের একটি বোরো চেকার রয়েছে যা স্কোপগুলো তুলনা করে নির্ধারণ করে যে সমস্ত বোরো বৈধ কিনা। Listing 10-17 Listing 10-16-এর মতোই একই কোড দেখায় কিন্তু ভেরিয়েবলগুলোর লাইফটাইম দেখানো অ্যানোটেশন সহ।

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-এর চেয়ে ছোট: রেফারেন্সের বিষয়বস্তুটি রেফারেন্সের মতো দীর্ঘস্থায়ী হয় না।

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

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

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

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

ফাংশনে জেনেরিক লাইফটাইম (Generic Lifetimes in Functions)

আমরা দুটি স্ট্রিং স্লাইসের মধ্যে দীর্ঘতমটি রিটার্ন করে এমন একটি ফাংশন লিখব। এই ফাংশনটি দুটি স্ট্রিং স্লাইস নেবে এবং একটি একক স্ট্রিং স্লাইস রিটার্ন করবে। আমরা longest ফাংশনটি ইমপ্লিমেন্ট করার পরে, Listing 10-19-এর কোডটি 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 ফাংশনটি তার প্যারামিটারগুলোর ওনারশিপ নিক। আমরা কেন Listing 10-19-এ ব্যবহৃত প্যারামিটারগুলোই চাই, সে সম্পর্কে আরও আলোচনার জন্য চ্যাপ্টার ৪-এর “প্যারামিটার হিসাবে স্ট্রিং স্লাইস” দেখুন।

আমরা যদি Listing 10-20-তে দেখানো 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-এর একটি রেফারেন্স রিটার্ন করে!

যখন আমরা এই ফাংশনটি সংজ্ঞায়িত করছি, তখন আমরা জানি না যে এই ফাংশনে কোন কংক্রিট মানগুলো পাস করা হবে, তাই আমরা জানি না যে if কেস নাকি else কেস এক্সিকিউট হবে। আমরা যে রেফারেন্সগুলো পাস করা হবে তার কংক্রিট লাইফটাইমও জানি না, তাই আমরা Listing 10-17 এবং 10-18-এ যেভাবে স্কোপগুলো দেখেছি সেভাবে দেখতে পারি না, যাতে আমরা নির্ধারণ করতে পারি যে আমরা যে রেফারেন্সটি রিটার্ন করব সেটি সর্বদাই বৈধ হবে কিনা। বোরো চেকারও এটি নির্ধারণ করতে পারে না, কারণ এটি জানে না যে x এবং y-এর লাইফটাইম রিটার্ন মানের লাইফটাইমের সাথে কীভাবে সম্পর্কিত। এই এররটি ঠিক করার জন্য, আমরা জেনেরিক লাইফটাইম প্যারামিটার যোগ করব যা রেফারেন্সগুলোর মধ্যে সম্পর্ক সংজ্ঞায়িত করে যাতে বোরো চেকার তার বিশ্লেষণ করতে পারে।

লাইফটাইম অ্যানোটেশন সিনট্যাক্স (Lifetime Annotation Syntax)

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

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

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

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

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

ফাংশন সিগনেচারে লাইফটাইম অ্যানোটেশন (Lifetime Annotations in Function Signatures)

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

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

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 }
}

এই কোডটি কম্পাইল করা উচিত এবং Listing 10-19-এর 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 ফাংশনকে সীমাবদ্ধ করে, বিভিন্ন কংক্রিট লাইফটাইমের রেফারেন্স পাস করে। Listing 10-22 একটি সহজবোধ্য উদাহরণ।

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 ব্যবহার করে, সেটি ইনার স্কোপের পরে, বাইরের স্কোপে নিয়ে যাব। Listing 10-23-এর কোডটি কম্পাইল হবে না।

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 এখনও স্কোপের বাইরে যায়নি, তাই println! স্টেটমেন্টের জন্য string1-এর একটি রেফারেন্স এখনও বৈধ থাকবে। যাইহোক, কম্পাইলার এই ক্ষেত্রে দেখতে পাচ্ছে না যে রেফারেন্সটি বৈধ। আমরা Rust-কে বলেছি যে longest ফাংশন দ্বারা রিটার্ন করা রেফারেন্সের লাইফটাইমটি পাস করা রেফারেন্সগুলোর লাইফটাইমের ছোটটির সমান। অতএব, বোরো চেকার Listing 10-23-এর কোডটিকে সম্ভবত একটি অবৈধ রেফারেন্স হিসাবে বাতিল করে দেয়।

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

লাইফটাইমের পরিপ্রেক্ষিতে চিন্তা করা (Thinking in Terms of Lifetimes)

আপনাকে যে উপায়ে লাইফটাইম প্যারামিটারগুলো নির্দিষ্ট করতে হবে তা নির্ভর করে আপনার ফাংশন কী করছে তার উপর। উদাহরণস্বরূপ, যদি আমরা 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 আমাদের একটি ড্যাংলিং রেফারেন্স তৈরি করতে দেবে না। এই ক্ষেত্রে, সর্বোত্তম সমাধান হবে একটি ওনড ডেটা টাইপ রিটার্ন করা, রেফারেন্স নয়, যাতে কলিং ফাংশনটি তখন মানের ক্লিনিং আপ করার জন্য দায়ী থাকে।

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

স্ট্রাকট সংজ্ঞায় লাইফটাইম অ্যানোটেশন (Lifetime Annotations in Struct Definitions)

এখন পর্যন্ত, আমরা যে স্ট্রাকটগুলো সংজ্ঞায়িত করেছি সেগুলো সবই ওনড টাইপ ধারণ করে। আমরা রেফারেন্স ধারণ করার জন্য স্ট্রাকট সংজ্ঞায়িত করতে পারি, কিন্তু সেই ক্ষেত্রে আমাদের স্ট্রাকটের সংজ্ঞার প্রতিটি রেফারেন্সে একটি লাইফটাইম অ্যানোটেশন যোগ করতে হবে। Listing 10-24-এ ImportantExcerpt নামে একটি স্ট্রাকট রয়েছে যা একটি স্ট্রিং স্লাইস ধারণ করে।

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,
    };
}

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

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

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

আপনি শিখেছেন যে প্রতিটি রেফারেন্সের একটি লাইফটাইম রয়েছে এবং আপনাকে সেই ফাংশন বা স্ট্রাকটগুলোর জন্য লাইফটাইম প্যারামিটার নির্দিষ্ট করতে হবে যেগুলো রেফারেন্স ব্যবহার করে। যাইহোক, Listing 4-9-এ আমাদের একটি ফাংশন ছিল, যা Listing 10-25-এ আবারও দেখানো হয়েছে, যেটি লাইফটাইম অ্যানোটেশন ছাড়াই কম্পাইল হয়েছিল।

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-এর প্রাথমিক ভার্সনগুলোতে (pre-1.0), এই কোডটি কম্পাইল হত না কারণ প্রতিটি রেফারেন্সের একটি স্পষ্ট লাইফটাইম প্রয়োজন হত। সেই সময়ে, ফাংশন সিগনেচারটি এইভাবে লেখা হত:

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

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

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

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-এর লাইফটাইম সমস্ত আউটপুট লাইফটাইম প্যারামিটারে বরাদ্দ করা হয়। এই তৃতীয় নিয়মটি মেথডগুলোকে পড়তে এবং লিখতে অনেক সুন্দর করে তোলে কারণ কম সংখ্যক চিহ্নের প্রয়োজন হয়।

আসুন ধরে নিই আমরা কম্পাইলার। Listing 10-25-এর 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 ফাংশনটি ব্যবহার করে যেখানে আমরা Listing 10-20-এ কাজ শুরু করার সময় কোনো লাইফটাইম প্যারামিটার ছিল না:

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

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

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

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

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

মেথড সংজ্ঞায় লাইফটাইম অ্যানোটেশন (Lifetime Annotations in Method Definitions)

যখন আমরা লাইফটাইম সহ স্ট্রাকটগুলোতে মেথড ইমপ্লিমেন্ট করি, তখন আমরা জেনেরিক টাইপ প্যারামিটারের মতোই সিনট্যাক্স ব্যবহার করি, যেমনটি Listing 10-11-তে দেখানো হয়েছে। আমরা কোথায় লাইফটাইম প্যারামিটারগুলো ঘোষণা করি এবং ব্যবহার করি তা নির্ভর করে সেগুলো স্ট্রাকট ফিল্ডগুলোর সাথে সম্পর্কিত কিনা বা মেথড প্যারামিটার এবং রিটার্ন ভ্যালুগুলোর সাথে।

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

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

প্রথমে আমরা 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 লাইফটাইম নির্দিষ্ট করা নয়।

জেনেরিক টাইপ প্যারামিটার, ট্রেইট বাউন্ড এবং লাইফটাইম একসাথে (Generic Type Parameters, Trait Bounds, and Lifetimes Together)

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

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 }
}

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

সারসংক্ষেপ (Summary)

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

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