আমি Rust শেখা শুরু করেছি একটি নির্দিষ্ট উদ্দেশ্য নিয়ে—শুধু আরেকটি programming language শেখার জন্য নয়।
আমি একজন Web Developer এবং এতদিন মূলত JavaScript ecosystem-এর সাথেই কাজ করেছি। React, WordPress, Shopify, frontend development—এসব আমার পরিচিত জায়গা।
কিন্তু একটা সময় মনে হলো, শুধু application-এর উপরিভাগটা জানলেই হবে না। আমি আরও ভালোভাবে বুঝতে চাই—programming language, memory, performance, concurrency এবং computer-এর ভিতরের জিনিসগুলো আসলে কীভাবে কাজ করে।
সেখান থেকেই Rust শেখার শুরু।
আর প্রথম lesson-এই Rust আমাকে একটা ছোট কিন্তু গুরুত্বপূর্ণ বিষয় শেখালো:
কোন data পরিবর্তন হতে পারে, আর কোনটা পরিবর্তন হবে না—এটা code-এর মধ্যেই explicitly প্রকাশ করা ভালো।
Rust-এ আমার প্রথম Variable
Rust-এ variable declare করার জন্য let ব্যবহার করা হয়।
fn main() {
let name = "Sajeeb";
println!("Hello, {}", name);
}
এখানে name নামে একটি binding তৈরি হয়েছে।
JavaScript-এর সাথে তুলনা করলে এটাকে অনেকটা এমনভাবে ভাবা যায়:
const name = "Sajeeb";
তবে Rust-এর let এবং JavaScript-এর const পুরোপুরি একই জিনিস নয়। এই পার্থক্যগুলো পরে আরও পরিষ্কার হবে।
Rust Variables Are Immutable by Default
এখানেই Rust-এর প্রথম interesting behaviour পেলাম।
আমি যদি লিখি:
fn main() {
let age = 33;
age = 34;
println!("{}", age);
}
Rust এটা compile করবে না।
কারণ let age = 33; দিয়ে তৈরি binding-টি immutable।
অর্থাৎ Rust ধরে নিচ্ছে:
আপনি যদি explicitly না বলেন যে এই value পরিবর্তন হতে পারে, তাহলে এটি পরিবর্তন করা যাবে না।
JavaScript-এ আমরা সাধারণত লিখতে পারি:
let age = 33;
age = 34;
কিন্তু Rust-এ mutation চাইলে আমাকে সেটা explicitly বলতে হবে।
mut — When I Want Mutation
Rust-এ mutable binding তৈরি করতে mut ব্যবহার করতে হয়।
fn main() {
let mut age = 33;
age = 34;
println!("{}", age);
}
এখন code ঠিকভাবে compile করবে।
এখানে:
let mut age = 33;
এর অর্থ হলো age binding-এর value পরবর্তীতে পরিবর্তন করা যাবে।
অর্থাৎ:
age = 34;
এখানে নতুন variable তৈরি হয়নি।
আগের age binding-এর value পরিবর্তন হয়েছে।
এটাই mutation।
কেন Rust-এ Mutation Explicit?
প্রথমে ব্যাপারটা একটু অদ্ভুত লাগতে পারে।
JavaScript-এ mutation করা খুব সহজ। কিন্তু Rust আমাকে আগে থেকেই বলতে বাধ্য করছে:
“হ্যাঁ, এই data পরিবর্তন হতে পারে।”
আমার কাছে এর সবচেয়ে interesting দিক হলো code-এর intent স্পষ্ট হয়ে যায়।
যখন আমি দেখি:
let age = 33;
তখন আমি জানি এই binding immutable।
আর যখন দেখি:
let mut age = 33;
তখন আমি জানি এখানে mutation হওয়ার সম্ভাবনা আছে।
বড় codebase-এ এই ধরনের explicitness code বোঝা এবং reasoning করা সহজ করতে পারে।
Rust-এর Type Inference
আরেকটি বিষয় প্রথম lesson-এই দেখলাম।
আমি লিখতে পারি:
let age = 33;
এখানে আমি age-এর type explicitly লিখিনি।
তারপরও Rust compiler type infer করতে পারে।
চাইলে আমি explicitly type লিখতে পারি:
let age: i32 = 33;
এখানে:
age → i32 → 33
Rust বুঝতে পারছে 33 একটি integer এবং এখানে i32 type ব্যবহার করা হয়েছে।
এটাকে type inference বলা হয়।
অর্থাৎ Rust static typing ব্যবহার করলেও সব জায়গায় আমাকে manually type লিখতে হয় না।
তারপর এল Shadowing
এখানেই lesson-টা আরও interesting হয়ে উঠল।
এই code দেখুন:
fn main() {
let x = 10;
let x = 20;
println!("{}", x);
}
Output:
20
প্রথমে মনে হতে পারে, আমি তো বললাম Rust variable immutable। তাহলে x কীভাবে 10 থেকে 20 হলো?
এখানে আসলে mutation হয়নি।
এখানে হয়েছে shadowing।
Shadowing কী?
যখন আমি লিখি:
let x = 10;
let x = 20;
দ্বিতীয় let x পুরোনো x-এর value পরিবর্তন করছে না।
বরং একটি নতুন binding তৈরি করছে।
Conceptually:
First binding:
x → 10
Second binding:
x → 20
দ্বিতীয় x scope-এর মধ্যে প্রথম x-কে shadow করছে।
তাই শেষ পর্যন্ত যখন লিখি:
println!("{}", x);
তখন নতুন x অর্থাৎ 20 পাওয়া যায়।
Shadowing vs Mutation
এই পার্থক্যটা আমার কাছে Lesson 1-এর সবচেয়ে গুরুত্বপূর্ণ শেখা।
Mutation
let mut x = 10;
x = 20;
এখানে:
- একই binding
- নতুন value
- mutation হয়েছে
mutপ্রয়োজন
Shadowing
let x = 10;
let x = 20;
এখানে:
- নতুন binding তৈরি হয়েছে
- পুরোনো binding shadow হয়েছে
- mutation হয়নি
mutপ্রয়োজন নেই
দুটোর output একই হতে পারে, কিন্তু ভিতরের concept একই নয়।
একটা Practical Example
ধরুন আমি কোনো value-কে কয়েকটি ধাপে transform করতে চাই।
Shadowing ব্যবহার করে লিখতে পারি:
let name = "sajeeb";
let name = name.to_uppercase();
let name = name.trim();
প্রতিটি let নতুন binding তৈরি করছে।
অন্যদিকে mutation করলে আমাকে mutable binding ব্যবহার করতে হবে:
let mut name = String::from("sajeeb");
name = name.to_uppercase();
name = name.trim().to_string();
দুই approach-এর মধ্যে কোনটা কখন ভালো—সেটা context-এর উপর নির্ভর করবে।
কিন্তু Rust আমাকে দুটো concept আলাদা করে দিয়েছে:
“আমি কি existing data পরিবর্তন করছি?”
নাকি
“আমি কি নতুন binding তৈরি করছি?”
আমার প্রথম Engineering Insight
Lesson 1 খুব ছোট ছিল।
আমরা এখনো ownership, borrowing বা memory-তে যাইনি।
কিন্তু এখান থেকেই Rust-এর একটি বড় philosophy সম্পর্কে ধারণা পাওয়া যায়।
Rust আমাকে code-এর intent স্পষ্ট করতে উৎসাহিত করছে।
let x = 10;
মানে:
এই binding পরিবর্তন করার প্রয়োজন নেই।
আর:
let mut x = 10;
মানে:
এই binding পরিবর্তিত হতে পারে।
আর:
let x = 10;
let x = 20;
মানে:
আমি পুরোনোটাকে mutate করছি না; নতুন binding তৈরি করছি।
এই ছোট distinction-গুলোই পরবর্তীতে Rust-এর আরও বড় concept—বিশেষ করে ownership এবং borrowing—বোঝার foundation তৈরি করবে।
Lesson 1 থেকে আমার Takeaways
আজকের lesson শেষে আমার শেখাগুলো:
- Rust-এ variable binding immutable by default।
- Mutation করতে
mutব্যবহার করতে হয়। let mut xএবংlet xএকই ধরনের binding নয়।- Rust অনেক ক্ষেত্রে type নিজে infer করতে পারে।
- Shadowing এবং mutation এক জিনিস নয়।
- Shadowing নতুন binding তৈরি করে।
- Mutation existing binding-এর value পরিবর্তন করে।
- Rust আমাকে code-এর intent আরও explicitভাবে প্রকাশ করতে বাধ্য করে।
সবচেয়ে গুরুত্বপূর্ণভাবে, আমি বুঝতে শুরু করেছি যে Rust শেখা শুধু syntax শেখা নয়।
এটা আসলে কীভাবে এবং কেন code কাজ করে—সেটা আরও গভীরভাবে বোঝার একটি journey।
আর আমার পরের lesson-এর প্রশ্ন সম্ভবত আরও interesting:
let x = 10;লিখলে এই10আসলে memory-র কোথায় থাকে?
সেখান থেকেই শুরু হবে আমার পরের exploration:
Stack, Heap এবং Rust-এর Memory Model. 🦀