ভেরিয়েবল এবং মিউটেবিলিটি (Variables and Mutability)

“ভেরিয়েবল ব্যবহার করে মান সংরক্ষণ করা” বিভাগে যেমন উল্লেখ করা হয়েছে, ডিফল্টভাবে ভেরিয়েবলগুলো ইমিউটেবল (immutable) হয়। Rust আপনাকে যে নিরাপত্তা এবং সহজে কনকারেন্সি (concurrency) ব্যবহারের সুবিধা দেয়, সেটির সুবিধা নিতে আপনার কোড লেখার ক্ষেত্রে এটি Rust-এর দেওয়া অনেকগুলো কৌশলের মধ্যে একটি। তবে, আপনার কাছে এখনও আপনার ভেরিয়েবলগুলোকে মিউটেবল (mutable) করার অপশন রয়েছে। চলুন, অনুসন্ধান করি কেন Rust আপনাকে ইমিউটেবিলিটিকে প্রাধান্য দিতে উৎসাহিত করে এবং কেন আপনি কখনও কখনও এর থেকে বেরিয়ে আসতে চাইতে পারেন।

যখন একটি ভেরিয়েবল ইমিউটেবল হয়, তখন একটি মান একটি নামের সাথে বাইন্ড হয়ে গেলে, আপনি সেই মান পরিবর্তন করতে পারবেন না। এটি বোঝানোর জন্য, cargo new variables ব্যবহার করে আপনার projects ডিরেক্টরিতে variables নামে একটি নতুন প্রোজেক্ট তৈরি করুন।

তারপর, আপনার নতুন variables ডিরেক্টরিতে, src/main.rs খুলুন এবং এর কোডটি নিচের কোড দিয়ে প্রতিস্থাপন করুন, যেটি এখনই কম্পাইল হবে না:

Filename: src/main.rs

fn main() {
    let x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

cargo run ব্যবহার করে প্রোগ্রামটি সংরক্ষণ করুন এবং চালান। আপনি এই আউটপুটে দেখানো ইমিউটেবিলিটি এরর সম্পর্কিত একটি এরর মেসেজ পাবেন:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:4:5
  |
2 |     let x = 5;
  |         - first assignment to `x`
3 |     println!("The value of x is: {x}");
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     let mut x = 5;
  |         +++

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

এই উদাহরণটি দেখায় যে কম্পাইলার কীভাবে আপনার প্রোগ্রামের এররগুলো খুঁজে পেতে সহায়তা করে। কম্পাইলার এররগুলো বিরক্তিকর হতে পারে, কিন্তু আসলে এগুলো কেবল বোঝায় যে আপনার প্রোগ্রামটি আপনি যা করতে চান তা এখনও নিরাপদে করছে না; এগুলোর মানে এই নয় যে আপনি ভালো প্রোগ্রামার নন! অভিজ্ঞ Rustacean-রাও কম্পাইলার এরর পান।

আপনি cannot assign twice to immutable variable `x` এরর মেসেজটি পেয়েছেন কারণ আপনি ইমিউটেবল x ভেরিয়েবলে দ্বিতীয়বার মান অ্যাসাইন করার চেষ্টা করেছিলেন।

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

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

উদাহরণস্বরূপ, চলুন src/main.rs পরিবর্তন করে নিচের মতো করি:

Filename: src/main.rs

fn main() {
    let mut x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

আমরা যখন এখন প্রোগ্রামটি চালাই, তখন আমরা এটি পাই:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/variables`
The value of x is: 5
The value of x is: 6

mut ব্যবহার করা হলে আমরা x-এর সাথে বাইন্ড করা মান 5 থেকে 6-এ পরিবর্তন করার অনুমতি পাই। শেষ পর্যন্ত, মিউটেবিলিটি ব্যবহার করবেন কিনা তা আপনার উপর নির্ভর করে এবং সেই নির্দিষ্ট পরিস্থিতিতে কোনটি সবচেয়ে পরিষ্কার বলে আপনি মনে করেন তার উপর নির্ভর করে।

কনস্ট্যান্ট (Constants)

ইমিউটেবল ভেরিয়েবলের মতো, কনস্ট্যান্টগুলোও (constants) এমন মান যেগুলো একটি নামের সাথে বাইন্ড করা থাকে এবং পরিবর্তন করার অনুমতি নেই, তবে কনস্ট্যান্ট এবং ভেরিয়েবলের মধ্যে কয়েকটি পার্থক্য রয়েছে।

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

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

শেষ পার্থক্য হল কনস্ট্যান্টগুলোকে শুধুমাত্র একটি কনস্ট্যান্ট এক্সপ্রেশনে সেট করা যেতে পারে, এমন কোনো মানের ফলাফলে নয় যা শুধুমাত্র রানটাইমে গণনা করা যেতে পারে।

এখানে একটি কনস্ট্যান্ট ঘোষণার উদাহরণ দেওয়া হল:

#![allow(unused)]
fn main() {
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
}

কনস্ট্যান্টের নাম হল THREE_HOURS_IN_SECONDS এবং এর মান 60 (এক মিনিটের সেকেন্ড সংখ্যা) গুণ 60 (এক ঘন্টার মিনিটের সংখ্যা) গুণ 3 (এই প্রোগ্রামে আমরা যত ঘন্টা গণনা করতে চাই) এর ফলাফলে সেট করা হয়েছে। কনস্ট্যান্টগুলোর জন্য Rust-এর নামকরণের নিয়ম হল শব্দগুলোর মধ্যে আন্ডারস্কোর সহ সমস্ত আপারকেস ব্যবহার করা। কম্পাইলার কম্পাইল টাইমে সীমিত সংখ্যক অপারেশন মূল্যায়ন করতে সক্ষম, যা আমাদের এই কনস্ট্যান্টটিকে 10,800 মানে সেট করার পরিবর্তে, এই মানটিকে এমনভাবে লিখতে দেয় যা বোঝা এবং যাচাই করা সহজ। কনস্ট্যান্ট ঘোষণা করার সময় কোন অপারেশনগুলো ব্যবহার করা যেতে পারে সে সম্পর্কে আরও তথ্যের জন্য Rust রেফারেন্সের কনস্ট্যান্ট মূল্যায়ন বিভাগটি দেখুন।

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

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

শ্যাডোয়িং (Shadowing)

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

Filename: src/main.rs

fn main() {
    let x = 5;

    let x = x + 1;

    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}");
    }

    println!("The value of x is: {x}");
}

এই প্রোগ্রামটি প্রথমে x-কে 5 মানের সাথে বাইন্ড করে। তারপর এটি let x = পুনরাবৃত্তি করে একটি নতুন ভেরিয়েবল x তৈরি করে, আসল মান নিয়ে এবং 1 যোগ করে, তাই x-এর মান তখন 6 হয়। তারপর, কার্লি ব্র্যাকেট দিয়ে তৈরি একটি অভ্যন্তরীণ স্কোপের মধ্যে, তৃতীয় let স্টেটমেন্টটিও x-কে শ্যাডো করে এবং একটি নতুন ভেরিয়েবল তৈরি করে, আগের মানকে 2 দিয়ে গুণ করে x-কে 12 মান দেয়। যখন সেই স্কোপটি শেষ হয়ে যায়, তখন অভ্যন্তরীণ শ্যাডোয়িং শেষ হয়ে যায় এবং x আবার 6 হয়ে যায়। যখন আমরা এই প্রোগ্রামটি চালাই, তখন এটি নিম্নলিখিত আউটপুট দেবে:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6

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

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

fn main() {
    let spaces = "   ";
    let spaces = spaces.len();
}

প্রথম spaces ভেরিয়েবলটি হল একটি স্ট্রিং টাইপ এবং দ্বিতীয় spaces ভেরিয়েবলটি হল একটি সংখ্যা টাইপ। এইভাবে শ্যাডোয়িং আমাদেরকে spaces_str এবং spaces_num-এর মতো আলাদা নাম নিয়ে আসার ঝামেলা থেকে বাঁচায়; পরিবর্তে, আমরা সহজ spaces নামটি পুনরায় ব্যবহার করতে পারি। তবে, যদি আমরা এর জন্য mut ব্যবহার করার চেষ্টা করি, যেমনটি এখানে দেখানো হয়েছে, তাহলে আমরা একটি কম্পাইল-টাইম এরর পাব:

fn main() {
    let mut spaces = "   ";
    spaces = spaces.len();
}

এরর বলছে যে আমাদের একটি ভেরিয়েবলের টাইপ মিউটেট করার অনুমতি নেই:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
 --> src/main.rs:3:14
  |
2 |     let mut spaces = "   ";
  |                      ----- expected due to this value
3 |     spaces = spaces.len();
  |              ^^^^^^^^^^^^ expected `&str`, found `usize`

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

এখন যেহেতু আমরা অনুসন্ধান করেছি যে ভেরিয়েবলগুলো কীভাবে কাজ করে, আসুন তারা যে আরও ডেটা টাইপ রাখতে পারে সেগুলোর দিকে তাকাই।