अगर आपने कभी कोई दूसरी प्रोग्रामिंग भाषा (C/C++, Python, Java, Ruby, Lisp, आदि) इस्तेमाल की है, तो बहुत संभव है कि आपका सामना पहले null या nil से हो चुका हो।
null या nil का इस्तेमाल इन भाषाओं में यह बताने का तरीका है कि किसी खास वेरिएबल के पास कोई वैल्यू नहीं है।
लेकिन इस वजह से ऐसे वेरिएबल को गलती से इस्तेमाल कर लेना, जो null की ओर इशारा करता है, एक आसान और बार-बार होने वाली गलती बन जाता है।
जैसा आप सोच सकते हैं, ऐसा फंक्शन कॉल करने की कोशिश करना जो है ही नहीं, या ऐसी वैल्यू तक पहुँचने की कोशिश करना जो मौजूद ही नहीं है, कई तरह के बग और क्रैश पैदा कर सकता है।
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,
}
इस गेम में खिलाड़ियों को जादू करना शुरू करने से पहले लेवल 10 तक पहुँचना होता है, तभी उनका माना पूल खुलता है। उससे पहले Player का माना None होता है।
आप इस गेम में दो चीज़ों पर काम कर रहे हैं, फिर से ज़िंदा करने की सुविधा और जादू करने की सुविधा।
revive मेथड को यह जाँचना चाहिए कि Player वाकई मर चुका है (उसकी हेल्थ 0 तक पहुँच गई है), और अगर ऐसा है, तो मेथड को 100 हेल्थ वाली एक नई Player इंस्टेंस लौटानी चाहिए।
अगर Player का लेवल 10 या उससे अधिक है, तो उसे 100 माना के साथ भी ज़िंदा किया जाना चाहिए।
अगर Player का लेवल 10 से कम है, तो उसका माना None होना चाहिए। revive मेथड को Player का लेवल बनाए रखना चाहिए।
let dead_player = Player { health: 0, mana: None, level: 2 };
dead_player.revive()
// Returns Player { health: 100, mana: None, level: 2 }
अगर revive मेथड को ऐसे Player पर कॉल किया जाए जिसकी हेल्थ 1 या उससे अधिक है, तो मेथड को None लौटाना चाहिए।
let alive_player = Player { health: 1, mana: Some(15), level: 11 };
alive_player.revive()
// Returns None
cast_spell मेथड Player का एक म्यूटेबल रेफरेंस लेता है, और साथ में एक mana_cost पैरामीटर भी, जो बताता है कि इस जादू पर कितना माना लगता है। यह उस नुकसान की मात्रा लौटाता है जो जादू चलने पर होता है, और अगर जादू सफलतापूर्वक चलता है, तो यह मात्रा हमेशा जादू के माना खर्च की दोगुनी होती है।
अगर खिलाड़ी के पास माना पूल नहीं है, तो जादू करने की कोशिश करने पर उसकी हेल्थ जादू के माना खर्च जितनी घटनी चाहिए। लौटाया गया नुकसान 0 होना चाहिए।
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);
अगर खिलाड़ी के पास माना पूल है लेकिन माना काफी नहीं है, तो मेथड को पूल पर कोई असर नहीं डालना चाहिए, बल्कि 0 लौटाना चाहिए।
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));
वरना Player के माना पूल से mana_cost घटाया जाना चाहिए और उतना ही नुकसान लौटाया जाना चाहिए।
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));
मज़े कीजिए!