আপনি যদি আগে অন্য কোনো প্রোগ্রামিং ভাষা (C/C++, Python, Java, Ruby, Lisp ইত্যাদি) ব্যবহার করে থাকেন, তাহলে সম্ভবত null বা nil আপনার আগেই চোখে পড়েছে।
এই ভাষাগুলো null বা nil ব্যবহার করে বোঝায় যে কোনো একটি নির্দিষ্ট ভ্যারিয়েবলের কোনো মান নেই।
তবে এর ফলে null-এর দিকে নির্দেশ করা কোনো ভ্যারিয়েবল ভুলবশত ব্যবহার করে ফেলা সহজ (এবং ঘন ঘন ঘটে যাওয়া) একটি ভুল হয়ে দাঁড়ায়।
আপনি যেমন ভাবতে পারেন, যে ফাংশনটি নেই সেটিকে কল করার চেষ্টা করা, বা যে মানটি নেই সেটি অ্যাক্সেস করার চেষ্টা করা, নানা রকম bug ও ক্র্যাশের দিকে নিয়ে যেতে পারে।
null-এর স্রষ্টা তো এটিকে তাঁর 'বিলিয়ন ডলারের ভুল' বলে অভিহিত করেছিলেন।
Option টাইপএই সমস্যাগুলো এড়াতে Rust নাল-রেফারেন্স ব্যবহার করে না।
তবে কোনো নির্দিষ্ট ভ্যারিয়েবলের কোনো মান নেই, সেটি বোঝানোর একটি নিরাপদ উপায় এর এখনও দরকার।
এখানেই Option আসে।
মান নেই এমন একটি ভ্যারিয়েবল রাখার বদলে Rust-এর ভ্যারিয়েবলগুলো Option এনাম ব্যবহার করতে পারে।
এই এনামের দুটি ভ্যারিয়েন্ট আছে: None, যা Rust-এ null-এর সমতুল্য; এবং Some(T), যেখানে T হলো যেকোনো টাইপের একটি মান।
এটি দেখতে এমন:
enum Option<T> {
None,
Some(T),
}
Option-কে আপনি আপনার এবং নাল-রেফারেন্স যে সমস্যাগুলো ডেকে আনতে পারে তার মাঝখানে নিরাপত্তার একটি স্তর হিসেবে ভাবতে পারেন, যা তাদের ধারণাগত উপযোগিতা তখনও ধরে রাখে।
Option ব্যবহার করাএকটি ভ্যারিয়েবলকে None-এ সেট করা বেশ সহজ:
let nothing: Option<u32> = None; // Variable nothing is set to None
তবে আপনি যদি চান Option টাইপটি একটি মান বহন করুক, তাহলে সেই মানটি সরাসরি অ্যাসাইন করতে পারবেন না।
একটি Option টাইপের ভ্যারিয়েবল আর ধরা যাক একটি i32 টাইপের ভ্যারিয়েবল এক জিনিস নয়।
আপনাকে Some ব্যবহার করতে হবে:
let wrong_way: Option<i32> = -4; // This will not work
let right_way: Option<i32> = Some(-4); // This will work
let another_right_way = Some(-4); // Compiler infers that this is Option<i32>
এই কারণেই নিচেরটিও কাজ করবে না:
let number = 47;
let option_number = Some(15);
let compile_error = number + option_number; // Cannot add an i32 and an Option<i32> - they are of different types
Some যে মানটি ধারণ করে, সেটি পেতে চাইলে প্রথমে যাচাই করতে হবে যে মানটি আদৌ আছে:
let mut some_words = Some("choose something to say"); // some_words set to something
match some_words {
Some(str) => println!("Here, we will {}", str),
None => println!("I've got nothing to say"),
} // Prints "Here, we will choose something to say"
some_words = None; // some_words now set to None
// exactly the same match block as above
match some_words {
Some(str) => println!("Here, we will {}", str),
None => println!("I've got nothing to say"),
} // Prints "I've got nothing to say"
match ছাড়াও Option-এর ভেতরে থাকা মান যাচাই ও অ্যাক্সেস করার জন্য Rust-এ আরও কিছু উপায় আছে, তবে match এতক্ষণে আপনার পরিচিত হয়ে যাওয়ার কথা।
এছাড়া, Rust কেন নাল-রেফারেন্সের বদলে Option ব্যবহার করে, তার একটি প্রদর্শনী হিসেবেও এটিকে ভাবতে পারেন।
মূল কথা হলো, Option ভ্যারিয়েবলটি Some কি না (সেক্ষেত্রে আপনি এগিয়ে গিয়ে তার ভেতরে থাকা মানটি বের করে ব্যবহার করতে পারেন), নাকি None, তা আপনাকে অবশ্যই যাচাই করতে হবে।
এর বাইরে অন্য কিছু করলে আপনার প্রোগ্রাম কম্পাইল হবে না; কম্পাইলার আপনাকে null থেকে নিরাপদে রেখে দিচ্ছে।
আপনি একটি রোল-প্লেয়িং গেম তৈরি করার কাজ করছেন। খেলোয়াড়ের চরিত্রকে নিচের স্ট্রাকচার দিয়ে প্রকাশ করা হয়:
pub struct Player {
health: u32,
mana: Option<u32>,
level: u32,
}
এই গেমের খেলোয়াড়দের স্পেল কাস্ট করা শুরু করার আগে ম্যানা পুল আনলক করতে হলে তাদের লেভেল ১০-এ পৌঁছাতে হবে। তার আগে পর্যন্ত Player-এর ম্যানা None.
আপনি এই গেমের দুটি কার্যকারিতা নিয়ে কাজ করছেন: রিভাইভ মেকানিজম এবং স্পেল কাস্টিং মেকানিজম।
revive মেথডটি যাচাই করা উচিত যে Player সত্যিই মৃত (তাদের হেলথ ০-এ পৌঁছেছে), আর যদি তাই হয়, মেথডটি ১০০ হেলথ নিয়ে একটি নতুন Player ইনস্ট্যান্স রিটার্ন করা উচিত।
Player-এর লেভেল ১০ বা তার বেশি হলে, তাদের ১০০ ম্যানা নিয়েও রিভাইভ করা উচিত।
Player-এর লেভেল ১০-এর কম হলে, তাদের ম্যানা None হওয়া উচিত। revive মেথডটির Player-এর লেভেল অপরিবর্তিত রাখা উচিত।
let dead_player = Player { health: 0, mana: None, level: 2 };
dead_player.revive()
// Returns Player { health: 100, mana: None, level: 2 }
কোনো Player-এর হেলথ ১ বা তার বেশি থাকা অবস্থায় revive মেথডটি কল করা হলে, মেথডটি None রিটার্ন করা উচিত।
let alive_player = Player { health: 1, mana: Some(15), level: 11 };
alive_player.revive()
// Returns None
cast_spell মেথডটি Player-এর একটি মিউটেবল রেফারেন্স নেয়, সঙ্গে নেয় একটি mana_cost প্যারামিটার যা বোঝায় স্পেলের কত ম্যানা খরচ হয়। এটি রিটার্ন করে কাস্ট করা স্পেল যে পরিমাণ ড্যামেজ করে তা, যা স্পেল সফলভাবে কাস্ট হলে সর্বদা স্পেলের ম্যানা খরচের দুই গুণ হবে।
খেলোয়াড়ের ম্যানা পুলে প্রবেশাধিকার না থাকলে, স্পেল কাস্ট করার চেষ্টায় তাদের হেলথ স্পেলের ম্যানা খরচের সমান কমতে হবে। রিটার্ন করা ড্যামেজ ০ হতে হবে।
let not_a_wizard_yet = Player { health: 79, mana: None, level: 9 };
assert_eq!(not_a_wizard_yet.cast_spell(5), 0)
assert_eq!(not_a_wizard_yet.health, 74);
assert_eq!(not_a_wizard_yet.mana, None);
খেলোয়াড়ের ম্যানা পুল থাকলেও ম্যানা অপর্যাপ্ত হলে, মেথডটি পুলকে প্রভাবিত করবে না, বরং ০ রিটার্ন করবে।
let low_mana_wizard = Player { health: 93, mana: Some(3), level: 12 };
assert_eq!(low_mana_wizard.cast_spell(10), 0);
assert_eq!(low_mana_wizard.health, 93);
assert_eq!(low_mana_wizard.mana, Some(3));
অন্যথায়, mana_cost Player-এর ম্যানা পুল থেকে বিয়োগ করা উচিত এবং উপযুক্ত পরিমাণ ড্যামেজ রিটার্ন করা উচিত।
let wizard = Player { health: 123, mana: Some(30), level: 18 };
assert_eq!(wizard.cast_spell(10), 20);
assert_eq!(wizard.health, 123);
assert_eq!(wizard.mana, Some(20));
শুভকামনা!
Exercism-এ সাইন আপ করুন, Rust ট্র্যাকের 99টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।