ভ্যারিয়েবল এবং পরিবর্তনশীলতা (Variables and Mutability)
যেমনটি "ভ্যারিয়েবলের মাধ্যমে মান সংরক্ষণ করা" বিভাগে উল্লেখ করা হয়েছে, ডিফল্টরূপে, ভ্যারিয়েবলগুলো অপরিবর্তনীয় (immutable)। এটি রাস্টের অনেকগুলো পদ্ধতির মধ্যে একটি যা আপনাকে কোড এমনভাবে লিখতে উৎসাহিত করে যা রাস্টের দেওয়া নিরাপত্তা এবং সহজ কনকারেন্সি (concurrency)-এর সুবিধা নেয়। তবে, আপনার কাছে আপনার ভ্যারিয়েবলগুলোকে পরিবর্তনযোগ্য (mutable) করার বিকল্পও রয়েছে। চলুন অন্বেষণ করি কীভাবে এবং কেন রাস্ট আপনাকে অপরিবর্তনীয়তাকে অগ্রাধিকার দিতে উৎসাহিত করে এবং কেন কখনও কখনও আপনি এর থেকে সরে আসতে চাইতে পারেন।
যখন একটি ভ্যারিয়েবল অপরিবর্তনীয় হয়, একবার একটি মান একটি নামের সাথে বাইন্ড করা হলে, আপনি সেই মান পরিবর্তন করতে পারবেন না। এটি দেখানোর জন্য, আপনার projects ডিরেক্টরিতে cargo new variables ব্যবহার করে variables নামে একটি নতুন প্রজেক্ট তৈরি করুন।
তারপর, আপনার নতুন variables ডিরেক্টরিতে, src/main.rs খুলুন এবং এর কোডটি নিম্নলিখিত কোড দিয়ে প্রতিস্থাপন করুন, যা এখনই কম্পাইল হবে না:
ফাইলের নাম: 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
এই উদাহরণটি দেখায় কীভাবে কম্পাইলার আপনাকে আপনার প্রোগ্রামের ত্রুটি খুঁজে পেতে সাহায্য করে। কম্পাইলার এরর হতাশাজনক হতে পারে, কিন্তু আসলে এর মানে শুধু এই যে আপনার প্রোগ্রাম এখনও নিরাপদে যা করতে চায় তা করছে না; এর মানে এই নয় যে আপনি একজন ভাল প্রোগ্রামার নন! অভিজ্ঞ রাস্টেশিয়ানরাও কম্পাইলার এরর পান।
আপনি cannot assign twice to immutable variable `x` এরর বার্তাটি পেয়েছেন কারণ আপনি অপরিবর্তনীয় x ভ্যারিয়েবলে দ্বিতীয়বার একটি মান অ্যাসাইন করার চেষ্টা করেছেন।
যখন আমরা একটি মান পরিবর্তন করার চেষ্টা করি যা অপরিবর্তনীয় হিসাবে চিহ্নিত করা হয়েছে, তখন কম্পাইল-টাইম এরর পাওয়া গুরুত্বপূর্ণ কারণ এই পরিস্থিতিটি বাগের কারণ হতে পারে। যদি আমাদের কোডের একটি অংশ এই অনুমানের উপর কাজ করে যে একটি মান কখনও পরিবর্তন হবে না এবং আমাদের কোডের অন্য একটি অংশ সেই মানটি পরিবর্তন করে, তবে এটি সম্ভব যে কোডের প্রথম অংশটি যা করার জন্য ডিজাইন করা হয়েছিল তা করবে না। এই ধরনের বাগের কারণ পরে খুঁজে বের করা কঠিন হতে পারে, বিশেষ করে যখন দ্বিতীয় কোডটি কেবল মাঝে মাঝে মান পরিবর্তন করে। রাস্ট কম্পাইলার গ্যারান্টি দেয় যে যখন আপনি বলেন যে একটি মান পরিবর্তন হবে না, তখন এটি সত্যিই পরিবর্তন হবে না, তাই আপনাকে নিজে এটি ট্র্যাক রাখতে হবে না। আপনার কোড তাই বোঝা সহজ হয়।
কিন্তু পরিবর্তনশীলতা খুব দরকারী হতে পারে, এবং কোড লেখা আরও সুবিধাজনক করে তুলতে পারে। যদিও ভ্যারিয়েবলগুলো ডিফল্টরূপে অপরিবর্তনীয়, আপনি অধ্যায় ২-এর মতো ভ্যারিয়েবলের নামের সামনে mut যোগ করে সেগুলিকে পরিবর্তনযোগ্য করতে পারেন। mut যোগ করা কোডের ভবিষ্যতের পাঠকদের কাছেও উদ্দেশ্য প্রকাশ করে, এটা নির্দেশ করে যে কোডের অন্যান্য অংশ এই ভ্যারিয়েবলের মান পরিবর্তন করবে।
উদাহরণস্বরূপ, চলুন src/main.rs পরিবর্তন করে নিম্নলিখিতটি করা যাক:
ফাইলের নাম: 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)
অপরিবর্তনীয় ভ্যারিয়েবলের মতো, কনস্ট্যান্ট হলো এমন মান যা একটি নামের সাথে বাইন্ড করা থাকে এবং পরিবর্তন করার অনুমতি নেই, তবে কনস্ট্যান্ট এবং ভ্যারিয়েবলের মধ্যে কয়েকটি পার্থক্য রয়েছে।
প্রথমত, আপনাকে কনস্ট্যান্টের সাথে mut ব্যবহার করার অনুমতি নেই। কনস্ট্যান্ট শুধু ডিফল্টরূপে অপরিবর্তনীয় নয়—তারা সবসময়ই অপরিবর্তনীয়। আপনি let কীওয়ার্ডের পরিবর্তে const কীওয়ার্ড ব্যবহার করে কনস্ট্যান্ট ঘোষণা করেন, এবং মানের টাইপ অবশ্যই অ্যানোটেট করতে হবে। আমরা পরবর্তী বিভাগে, "ডেটা টাইপ"-এ, টাইপ এবং টাইপ অ্যানোটেশন নিয়ে আলোচনা করব, তাই এখনই বিস্তারিত নিয়ে চিন্তা করবেন না। শুধু জেনে রাখুন যে আপনাকে সবসময় টাইপ অ্যানোটেট করতে হবে।
কনস্ট্যান্ট যেকোনো স্কোপে, এমনকি গ্লোবাল স্কোপেও ঘোষণা করা যেতে পারে, যা তাদের সেইসব মানের জন্য দরকারী করে তোলে যা কোডের অনেক অংশের জানা প্রয়োজন।
শেষ পার্থক্য হল যে কনস্ট্যান্ট শুধুমাত্র একটি কনস্ট্যান্ট এক্সপ্রেশনে সেট করা যেতে পারে, এমন কোনো মানের ফলাফলে নয় যা কেবল রানটাইমে গণনা করা যেতে পারে।
এখানে একটি কনস্ট্যান্ট ঘোষণার উদাহরণ:
#![allow(unused)] fn main() { const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; }
কনস্ট্যান্টের নাম THREE_HOURS_IN_SECONDS এবং এর মান 60 (এক মিনিটে সেকেন্ডের সংখ্যা) কে 60 (এক ঘন্টায় মিনিটের সংখ্যা) দিয়ে এবং তারপর 3 (এই প্রোগ্রামে আমরা যত ঘন্টা গণনা করতে চাই) দিয়ে গুণ করার ফলাফলে সেট করা হয়েছে। কনস্ট্যান্টের জন্য রাস্টের নামকরণের প্রথা হল সব বড় হাতের অক্ষর ব্যবহার করা এবং শব্দগুলোর মধ্যে আন্ডারস্কোর ব্যবহার করা। কম্পাইলার কম্পাইল করার সময় একটি সীমিত সেট অপারেশন মূল্যায়ন করতে সক্ষম, যা আমাদের এই মানটিকে এমনভাবে লেখার সুযোগ দেয় যা বোঝা এবং যাচাই করা সহজ, এই কনস্ট্যান্টটিকে 10,800 মানে সেট করার পরিবর্তে। কনস্ট্যান্ট ঘোষণা করার সময় কোন অপারেশনগুলো ব্যবহার করা যেতে পারে সে সম্পর্কে আরও তথ্যের জন্য রাস্ট রেফারেন্সের কনস্ট্যান্ট মূল্যায়ন সম্পর্কিত বিভাগ দেখুন।
কনস্ট্যান্টগুলো একটি প্রোগ্রাম চলার পুরো সময় জুড়ে বৈধ থাকে, যে স্কোপে তারা ঘোষণা করা হয়েছিল তার মধ্যে। এই বৈশিষ্ট্যটি কনস্ট্যান্টগুলোকে আপনার অ্যাপ্লিকেশনের ডোমেনের এমন মানগুলোর জন্য দরকারী করে তোলে যা প্রোগ্রামের একাধিক অংশের জানা প্রয়োজন হতে পারে, যেমন কোনো খেলোয়াড় সর্বোচ্চ যত পয়েন্ট অর্জন করতে পারে, বা আলোর গতি।
আপনার প্রোগ্রাম জুড়ে ব্যবহৃত হার্ডকোডেড মানগুলোকে কনস্ট্যান্ট হিসাবে নামকরণ করা কোডের ভবিষ্যতের রক্ষণাবেক্ষণকারীদের কাছে সেই মানের অর্থ বোঝাতে দরকারী। এটি আপনার কোডে কেবল একটি জায়গা রাখতেও সাহায্য করে যা ভবিষ্যতে হার্ডকোডেড মান আপডেট করার প্রয়োজন হলে পরিবর্তন করতে হবে।
শ্যাডোইং (Shadowing)
যেমন আপনি অধ্যায় ২-এর গেসিং গেম টিউটোরিয়ালে দেখেছেন, আপনি একটি পূর্ববর্তী ভ্যারিয়েবলের একই নামে একটি নতুন ভ্যারিয়েবল ঘোষণা করতে পারেন। রাস্টেশিয়ানরা বলেন যে প্রথম ভ্যারিয়েবলটি দ্বিতীয়টি দ্বারা শ্যাডো (shadowed) করা হয়েছে, যার মানে হল যে দ্বিতীয় ভ্যারিয়েবলটি হল যা কম্পাইলার দেখবে যখন আপনি ভ্যারিয়েবলের নামটি ব্যবহার করবেন। কার্যকরভাবে, দ্বিতীয় ভ্যারিয়েবলটি প্রথমটিকে ছাপিয়ে যায়, ভ্যারিয়েবলের নামের যেকোনো ব্যবহার নিজের দিকে নিয়ে নেয় যতক্ষণ না এটি নিজে শ্যাডো করা হয় বা স্কোপ শেষ হয়। আমরা একই ভ্যারিয়েবলের নাম ব্যবহার করে এবং let কীওয়ার্ডের ব্যবহার পুনরাবৃত্তি করে একটি ভ্যারিয়েবলকে শ্যাডো করতে পারি, যেমনটি নিচে দেওয়া হল:
ফাইলের নাম: 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
এখন আমরা ভ্যারিয়েবলগুলো কীভাবে কাজ করে তা অন্বেষণ করেছি, চলুন দেখা যাক তাদের আর কী কী ডেটা টাইপ থাকতে পারে।