Rust পর্ব ০২ - বেসিক সিনট্যাক্স

Published on
-13 min read

সূচীপত্র

Rust 101: Rust প্রোগ্রামিং ল্যাঙ্গুয়েজের বেসিক


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

ইন্সটেলেশন

লিনাক্স কিংবা ম্যাকে Rust ইন্সটল করা খুব সহজ। টার্মিনালে গিয়ে নিচের কমান্ডটি রান করলেই Rust ইন্সটল হয়ে যাবে।

curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh

এটি মূলত একটি Bash স্ক্রিপ্ট যা আপনার কম্পিউটারের প্লাটফর্ম (CPU আর্কিটেকচার) অনুযায়ী Rustup অটোমেটিক্যালি ইন্সটল করে দেবে।

উইন্ডোজ কম্পিউটারের জন্য rustup-init.exe ডাউনলোড করে রান করলেই হবে।

Rust সঠিকভাবে ইন্সটল হয়েছে কিনা চেক করার জন্য টার্মিনালে গিয়ে ভার্শন চেক করতে পারেনঃ

> rustc --version

// rustc 1.70.0 (90c541806 2023-05-31)

Cargo ইন্সটল হয়েছে কিনা চেক করার জন্যঃ

> cargo --version

// cargo 1.70.0 (ec8a8a0ca 2023-04-25)

এরকম আউটপুট আসবে। আপনার ভার্শন এবং তারিখ আমার লেখার থেকে ভিন্ন হতে পারে। সেটি কোন সমস্যা নয়। কোন এরর না থাকলেই ধরে নিতে পারেন Rust প্রোগ্রামিং ল্যাঙ্গুয়েজ আপনার লোকাল মেশিনে ঠিকঠাক মত ইন্সটল হয়েছে।

মিউটেবল এবং নন-মিউটেবল

এই দুটি শব্দের সাথে শুরুতেই পরিচিত হয়ে যাওয়া দরকার। কারণ পুরো সিরিজে এই দুটি শব্দ প্রায়ই আসতে থাকবে তাই মাথায় গেঁথে নেবেনঃ

  1. মিউটেবলঃ মানে যার ভ্যালু পরিবর্তন করা যায়। অন্য কোন ভেরিএবল এ্যাসাইন করে দেওয়া যায়
  2. নন-মিউটেবলঃ এর মানে, ভ্যালু পরিবর্তন করা যাবে না। প্রথম যেটা ডিক্লেয়ার হয়েছে সেটাই সব সময় থাকবে।

মিউটেবল ভেরিএবল বা ভ্যালু নিয়ে আমরা প্রায়ই কাজ করি। একবার একটা ভ্যারিএবল ডিক্লেয়ার করি তারপরে সেখানে অন্য কোন ভ্যালু দিয়ে এ্যাসাইন করি।

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

তো, এরপর যদি কখনো মিউটেবল এবং ইম-মিটএবল শুনেন তাহলে উপরের কথা মনে রাখবেন।

ভেরিএবল

ভেরিএবলের ডেফিনেশন আশা করি নতুন করে শেখানোর কিছু নেই। Rust ব্যাতিক্রম হচ্ছে প্রতিটি ভেরিএবল ডিফল্ট ভাবে "ইমমিউটেবল"। মানে, ভেরিএবল এর ভ্যালু একবার এসাইন করে ফেললে সেটি আর পরিবর্তন করতে পারবেন না। তবে mut কিওয়ার্ড ব্যাবহার করে আপনি মিউটেবল করে নিতে পারেন। Rust-এ ভেরিএবল ডিক্লারেশন এর কিছু বৈশিষ্ট্য দেখে নেইঃ

  1. ডিফল্টভাবে সকল ভেরিএবল "অ-পরিবর্তনশীল" তবে mut কিওয়ার্ডের মাধ্যমে মিউটএবল করা যায়
  2. প্রতিটি ভেরিএবলের কোন ডেটা টাইপ থাকবে। যদি ডেটা টাইপ ডিক্লেয়ার না করা হয় তাহলে টাইপ ইনফ্রারেন্স করে ডেটা টাইপ ধরে নেবে এবং পরবর্তীতে এক ডেটা টাইপের ভেরিএবলে অন্য ডেটা টাইপের ভ্যালু এ্যাসাইন করা যাবে না
  3. যদি কোন ভেরিএবল ডিক্লেয়ার করি এরপরে সেটি কোথাও ব্যাবহৃত না হলে প্রোগ্রাম কম্পাইল টাইমে এরর দেবে। মানে, কোন ভেরিএবল unused অবস্থায় থাকতে পারবে না।

ভেরিএবল ডিক্লেয়ার করা খুব সিম্পলঃ


fn main() {
	 let blogName = "Rust 101";  // এখানে blogName কে String টাইপ হিসেবে ধরে নেবে

	 let viewCount: u32 = 460; // এখানে আমরা এক্সপ্লেসিট ভাবে বলে দিয়েছি viewCount ভেরিএবলের ডেটা টাইপ ৩২বিটের আন-সাইনড(সকল পজেটিভ নাম্বার, কোন নেগেটিভ নাম্বার থাকতে পারবে না) ইন্টেজার।

	 let readingTime = 22; // এখানে যেহুতু এক্সপ্লেসিট ভাবে বলা নেই তাই টাইপ ইনফ্রার করে কম্পাইলার ধরে নেবে i32 সাইনড(সকল পজেটিভ/নেগেটিভ ইন্টেজার নাম্বার) হিসেবে।
}

এবার আরেকটি উদাহরণ দেখি, যেখানে আমরা একটা ভেরিএবল ডিক্লেয়ার করবো কিন্তু কোন ভ্যালু এ্যাসাইন করবো না।


fn main() {
	let viewCount: u32;

	printViewCount(viewCount);
}

fn printViewCount(count: u32) {
	println!("Total views: {}", count);
}

উপরের আমরা viewCount নামে একটি ভেরিএবল ডিক্লেয়ার করেছি কিন্তু কোন ভ্যালু এ্যাসাইন করিনি। অন্য ল্যাংগুয়েজে এমন কোড রান করলে "Total views: undefiend" অথবা null এমন আসতো। কিন্তু Rust-এ এমন প্রোগ্রাম কম্পাইল-ই হবে না। used binding viewCount isn't initialized এরকম এরর আসবে।

error[E0381]: used binding `viewCount` isn't initialized
 --> src/main.rs:4:20
  |
2 |     let viewCount: u32;
  |         --------- binding declared here but left uninitialized
3 |
4 |     printViewCount(viewCount);
  |                    ^^^^^^^^^ `viewCount` used here but it isn't initialized
  |
help: consider assigning a value
  |
2 |     let viewCount: u32 = 0;
  |                        +++

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

যাহোক, কোন ভ্যালু ইনিশিয়ালাইজড না করে আপনি কোন ভেরিএবল রাখতে পারবেন না।

ডেটা টাইপ

Rust-এ অনেক ধরনের ডেটা টাইপ রয়েছে। এক নজরেঃ

bool: বুলিয়ান ভ্যালু। true বা false

char: কোন সিঙ্গেল একটি ক্যারেক্টর u8, u16, u32, u64, u128: পজেটিভ ইন্টেজার সংখ্যা

i8, i16, i32, i64, i128: পজেটিভ এবং নেগেটিভ (Signed) ইন্টেজার সংখ্যা

f32, f64: ৩২বিট এবং ৬৪বিটের দশমিক/ফ্লোটিং পয়েন্ট সংখ্যা

usize, isize: হোস্ট কম্পিউটারের আর্কিটেকচার অনুযায়ী ইন্টেজার টাইপ যা কোন একটা পয়েন্টারের সাইজ ইন্ডিকেট করে

&str: কোন স্ট্রিং এর রেফারেন্স, স্ট্রিং এর স্লাইস। এটি মিউটেবল নয়(এর ভ্যালু পরিবর্তন করা যাবে না)

String: পূর্নাঙ্গ স্ট্রিং এবং মিউটেবল

Arrays: নিদৃষ্ট সাইজের এ্যারে। Rust-এ এ্যারে একটা স্পেসিফিক সাইজের হয়। এবং সকল ভ্যালুর ডেটা টাইপ সেইম হতে হবে

Vec<T>: ভেক্টর। তবে সহজে মনে রাখার জন্য, এক রকম এ্যারে কিন্তু যেকোনো সাইজের হতে পারে। সাইজ বলতে কতটা ইলিমেন্ট থাকবে সেটা নিদৃষ্ট নয়। <T> দেখে হয়তো বুঝে গিয়েছেন এটি একটি জেনেরিক টাইপের

Option<T>: অপশনাল ভ্যালু, মনে রাখার জন্য null এর সাথে তুলনা করতে পারেন। পরবর্তিতে আরো বিস্তারিত জানবো

Result<T, E>: এটি একটি গুরুত্বপূর্ন টাইপ। কোন অপারেশন শেষ করে তার রেজাল্ট অথবা এরর ইন্ডিকেট করে। আমার মনে হয় এটা সকল প্রোগ্রামিং ল্যাংগুয়েজের থাকা উচিৎ। এটি সাধারণত Ok(value) অথবা Err(error) টাইপের হয়ে থাকে। যা দিয়ে আপনি চেক করে নিতে পারেন ওই অপারেশন কি সাকসেস হয়েছে নাকি এরর এসেছে। সেই অনুযায়ী আপনি আপনার প্রোগ্রাম হ্যান্ডেল করে নিতে পারেন।

Enums: কাস্টম ডেটা টাইপ যার মাধ্যমে পসিবল কি কি ভ্যালু হতে পারে ডিফাইন করে দিতে পারবেন

Structs: এটাও এক রকম কাস্টম ডেটা টাইপ যার মাধ্যমে আপনার ডেটার স্ট্রাকচার সাজিয়ে নিতে পারবেন। কিছুটা OOP-তে আমরা যেমন class লিখতাম তেমন

Tuples: একাধিক ভ্যালুকে একটা গ্রুপ করে রাখার জন্য। এটির সাইজও ফিক্সড এবং ইমমিউটিবল। অনেকটা পাইথনের Tuples এর মত

আসুন এগুলো কোড-এ দেখতে কেমন এবং ভ্যালু কি রকম এ্যাসাইন করতে হয় এক নজরে দেখে নেইঃ

fn main() {
    // বুলিয়ান
    let is_true: bool = true;

    // ক্যারেক্টার-Character
    let letter: char = 'A';

    // পজেটিভ ইন্টেজার-Unsigned integers
    let unsigned_8: u8 = 255;
    let unsigned_16: u16 = 65535;
    let unsigned_32: u32 = 4294967295;
    let unsigned_64: u64 = 18446744073709551615;
    let unsigned_128: u128 = 340282366920938463463374607431768211455;

    // পজেটিভ বা নেগেটিভ ইন্টেজার - Signed integers
    let signed_8: i8 = -128;
    let signed_16: i16 = -32768;
    let signed_32: i32 = -2147483648;
    let signed_64: i64 = -9223372036854775808;
    let signed_128: i128 = -170141183460469231731687303715884105728;

    // ফ্লোটিং পয়েন্ট-Floating-point
    let float_32: f32 = 3.14159;
    let float_64: f64 = 2.718281828459045;

    // Size types
    let size: usize = 10;
    let isize: isize = -5;

    // String slice
    let string_slice: &str = "Hello, World!";

    // String
    let string: String = String::from("Rust");

    // Arrays
    let array: [i32; 3] = [1, 2, 3];

    // Vector
    let vector: Vec<i32> = vec![4, 5, 6];

    // Tuple
    let tuple: (i32, f64, char) = (7, 3.14, 'X');

    // Struct
    struct Person {
        name: String,
        age: u32,
    }

    let person: Person = Person {
        name: String::from("Alice"),
        age: 25,
    };

    // Enum
    enum Result<T, E> {
        Ok(T),
        Err(E),
    }

    let result: Result<i32, &str> = Result::Ok(42);

    // Option
    let option: Option<i32> = Some(10);

    println!("Boolean: {}", is_true);
    println!("Character: {}", letter);
    println!("Unsigned integers: {}, {}, {}, {}, {}", unsigned_8, unsigned_16, unsigned_32, unsigned_64, unsigned_128);
    println!("Signed integers: {}, {}, {}, {}, {}", signed_8, signed_16, signed_32, signed_64, signed_128);
    println!("Floating-point: {}, {}", float_32, float_64);
    println!("Size types: {}, {}", size, isize);
    println!("String slice: {}", string_slice);
    println!("String: {}", string);
    println!("Array: {:?}", array);
    println!("Vector: {:?}", vector);
    println!("Tuple: {:?}", tuple);
    println!("Struct: {}, {}", person.name, person.age);
    println!("Result: {:?}", result);
    println!("Option: {:?}", option);
}

ফাংশন

আমরা কিন্তু ইতিমধ্যেই একটি ফাংশনের সাথে পরিচিত হয়ে গিয়েছি। main ফাংশনের সাথে। যেটি আমাদের প্রোগ্রামের এন্ট্রিপয়েন্ট। fn কি-ওয়ার্ড দিয়ে আমরা ফাংশন ডিক্লেয়ার করি এবং ফাংশনের আর্গুমেন্ট এবং রিটান টাইপ বলে দিতে হয়। একটি সাধারণ Rust ফাংশন দেখতে এরকমঃ

fn is_divisible(num: i32, dividend: i32) -> bool {
  num % dividend == 0
}

সেমিকোলন (;) এর ব্যাবহার

অন্য সকল ল্যাংগুয়েজের মতই Rust-এ কোন স্টেটেমেন্ট অথবা এক্সপ্রেশন শেষ হয়েছে কিনা বোঝানোর জন্য সেমিকোলন দিতে হয়। যেমনঃ

let x = 5;
is_divisible();

তবে কিছু ব্যাতিক্রম আছে সেজন্যই আলাদা করে সেমিকোলন এর মত নিরীহ ব্যাপারে নিয়ে জানা দরকারঃ

স্টেটমেন্ট টার্মিনেশনঃ অন্য সকল ল্যাংগুয়েজে প্রতি স্টেটমেন্ট শেষে সেমিকোলন দিতে হয় কিন্তু Rust-এ প্রতিলাইনে সেমিকোলন দেওয়া ম্যান্ডেটরি না। যেমন একটি উদাহরণ দেখিঃ

fn add(a: i32, b: i32) -> i32 {
    a + b
}

খেয়াল করে দেখুন এখানে সেমিকোলন নেই। তাই ফাংশনটি এখানেই শেষ এবং লাস্ট লাইনকে এক্সপ্রেশন হিসেবে ধরে নিয়ে রিটার্ন করে দেবে। মানে কোন return কিওয়ার্ড ছাড়াই শেষ লাইনটিকে একটি এক্সপ্রেশন হিসেবে রিটার্ন করে দেবে। কিন্তু এখন যদি a + b; এভাবে সেমিকোলন দিয়ে থাকেন তাহলে কম্পাইলার ভাববে এরপরেও কোন লাইন আছে তাই এটি ভ্যালু রিটার্ন না করে স্টেটমেন্ট হিসেবে ধরবে। একটু কনফিউজড লাগলে এখানে ট্রাই করে দেখুন

ভেরিএবল shadowing

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

কিন্তু অনেক সময়েই আমাদের একই নামের ভেরিএবলে ভিন্ন টাইপের ডেটা অথবা ভ্যালু চেঞ্জ করা লাগতে পারে সেক্ষেত্রে ভেরিএবল শ্যাডো করে আমরা ব্যাবহার করতে পারিঃ

fn main() {
    let x = 5;
    let x = "I am a string now!!";

    println!("Value is: {}", x)
}

এখানে x ভেরিএবল কে শ্যাডো করে ব্যাবহার করা হয়েছে। জানি এরকম কাজ দেখতে অড লাগছে। আমারও প্রথম প্রথম লেগেছিলো কিন্তু Rust-এ এটি একটি কমন প্রাকটিস তাই এরকম শ্যাডোইং দেখলে অবাক হওয়ার কিছু নেই।

স্কোপঃ

Rust-এ { } স্কোপ ডিফাইন করা হয়। ভেরিএবল স্যাডোইং এর চমৎকার ইউজ কেস দেখে নেই স্কোপ এরঃ

fn main() {
    let x = 5;

    {
        let x = "shadowed";  // উপরের x ভেরিএবলকে এখানে শ্যাডো করা হয়েছে
        println!("Inner x: {}", x);  // "shadowed" প্রিন্ট করবে
    }

    println!("Outer x: {}", x);  // "5" প্রিন্ট করবে এবং এখানে বাইরের স্কোপের x ব্যাবহার করা যাবে।

    let mut y = 10;

    {
        let y = y + 5;  // y এখন নতুন ভেরিএবল যা এই স্কোপের বাইরের y ক শ্যাডো করেছে
        println!("Inner y: {}", y);  // "15" প্রিন্ট করবে
    }

    println!("Outer y: {}", y);  // "10" প্রিন্ট করবে কারণ বাইরের স্কোপের ভ্যালু কোথাও চেঞ্জ হয়নি
}

নেইমস্পেস / namespaces

অন্য সকল ল্যাংগুয়েজের মতই namespace এর মাধম্যে আমরা অনেকগুলো একই কাজের কোডকে গ্রুপ করে রাখতে পারি। অথবা আমরা যদি কোন প্যাকেজ পাবলিশ করতে চাই তখনও নেইমস্পেস ব্যাবহার করে আমাদের ফাংশন, টাইপ ইত্যাদি আলাদা করে রাখতে পারি যাতে করে কোথাও কনফ্লিট না হয়।

Rust-এ নেইমস্পেস ম্যানেজ করা হয় মডিউল এর মাধ্যমে। mod কিওয়ার্ড ব্যাবহার করে নতুন মডিউল তৈরি করতে পারি।

mod my_module {
	fn private_function() {
		println!("Private, cant call outside of module");
	}

	pub fn public_function() {
		println!("Public, can call")
	}
}

আমরা একটি অতি সাধারণ মডিউল তৈরি করলাম। এখন এটি ইউজ করতে পারি এভাবেঃ

fn main() {
	my_module::public_function();
}

:: দিয়ে মডিউলের পাবলিক প্রপার্টি একসেস করা হয়। Cargo বা std লাইব্রেরী থেকে অনেক সময়েই আমাদের অন্যের বানানো প্যাকেজ ডাউনলোড করে ব্যাবহার করতে হয় তখন আমরা একই ভাবে কল করবোঃ

fn main() {
  let least = std::cmp::max(3, 50);

  println!("{}", least); // 50
}

এখানে আমরা std প্যাকেজ থেকে cmp মডিউলের max নামে ফাংশন ব্যাবহার করলাম। use কিওয়ার্ড ব্যাবহার করে ইম্পোর্টও করে নিতে পারেনঃ

use std::cmp::max;

fn main() {
  let least = max(3, 50);

  println!("{}", least); // 50
}

Structs

আগেই আমরা ডেটাটাইপ সেকশনে জেনে এসেছি Struct এক রকম কাস্টম ডেটা টাইপ। C প্রোগ্রামিং যারা করেছেন তাদের কাছে Struct পরিচত লাগবে। অন্যরা আপাতত বোঝার স্বার্থে struct-কে class এর সাথে তুলনা করতে পারেন। যেখানে, আমাদের কিছু ডেটা থাকবে, ফাংশন থাকবে আমরা সেগুলোকে বার বার ব্যাবহার করতে পারবো। অথবা struct মানে কিছু ডেটার স্ট্রাকচার।


struct Person {
	name: String,
	age: u8,
}

fn main() {
		let rahim = Person {
		name: String::from("Rahim"),
		age: 25
	};
}

যদিও উপড়ের কোড সেলফ এক্সপ্লেমেন্টরি, তাও আমরা Person নামে একটি struct তৈরি করেছি। তারপরে সেই স্ট্রাক্ট থেকে rahim নামে একটি পারসন তৈরি করেছি।

Struct এ ফাংশনও লেখা যায়। যেমন আমরা class এর ভেতর ফাংশন লিখে থাকি সেরকম। তবে এখানে impl কিওয়ার্ড ব্যাবহার করে ফাংশন লিখতে হবে


struct Person {
	name: String,
	age: u8,
}

impl Person {
	fn say_hello(&self) {
		println("Hello, {}", self.name)
	}
}

fn main() {
	let rahim = Person {
		name: String::from("Rahim"),
		age: 25
	};

	rahim.say_hello();
}

এখানে মনে রাখতে হবে প্রতিটি struct এর ফাংশনে &self হবে প্রথম আর্গুমেন্ট। এই self দিয়ে struct এর ভেতরের প্রপার্টি একসেস করা যায়।

Traits

Rust-এ ট্রেইট কে অনেকটা ইন্টারফেসের সাথে তুলনা করতে পারেন। যেমন আমরা যদি উপরের Person struct-কে trait ব্যাবহার করতে চাইঃ

আমাদের struct এরকমঃ

struct Person {
	name: String,
	age: u8
}

এবার আমরা say_hello ফাংশন-কে ট্রেইট-এর আন্ডারে নিয়ে আসতে চাই।

trait Hello {
	fn say_hello(&self);
}

আমরা ট্রেইট ডিক্লেয়ার করে ফেললাম। এবার ট্রেইট অনুযায়ী ফাংশন ডিক্লেয়ার করবো struct এরঃ

impl Hello for Person {
	fn say_hello(&self) {
		println("Hello, {}", self.name)
	}
}

ব্যাস! হয়ে গেল। ট্রেইট ইমপ্লেমেন্ট করে struct এর ফাংশন লেখা। তাহলে ট্রেইটের সুবিধা কি?

  1. ট্রেইটের মাধ্যমে আমরা বলে দিতে পারি কোন ফাংশনের আর্গুমেন্ট কেমন হবে, রিটার্ন টাইপ কি হবে, ফাংশনের নামটা কি হবে। অনেকটা ইন্টারফেস ব্যাবহার করে আমরা যে সকল কাজ করে থাকি, এটি এক প্রকার কন্ট্রাক্ট বা চুক্তি।
  2. ট্রেইটের মাধম্যে একটা জেনেরিক কোড লেখা যায়
  3. রিইউজেবল কোড লেখা যায়, যার মাধ্যমে আমরা আরো মডিউলার কোড লিখতে পারি। কোড ডুপ্লিকেসি কমে। আর যদি ডুপ্লিকেট কম থাকে তাহলে মেইনটেইন করতেও সহজ হয়।

ট্রেইট খুবই দরকারি একটি কনসেপ্ট ভালো কোয়ালিটির Rust কোড লেখার জন্য। PHP-প্রোগ্রামাররা হয়তো ভাবছেন "এতদিন যা জেনে এসেছি trait সম্পর্কে সবই তাহলে ভুল ছিল?" আসলে তা না। একেক প্রোগ্রামিং ল্যাংগুয়েজের একেক রকম ব্যাবহার। PHP এর ট্রেইটের সাথে মিলিয়ে ফেললে এখানে হবে না। এখানে ইন্টারফেসের সাথে তুলনা করতে পারেন মনে রাখার জন্য

ম্যাক্রো / Macros

আমরা কিন্তু ইতিমধ্যেই একটি ম্যাক্রো ব্যাবহার করে এসেছি। println! এটি একটি ম্যাক্রো। Rust-এ সকল ম্যাক্রোই ! চিহ্ন দিয়ে শেষ হবে। ম্যাক্রো আসলে মেটা প্রোগ্রামিং এর একটি অংশ। মেটা প্রোগ্রামিং নিয়ে অন্য একদিন আলোচনা করবো, আপাতত এটুকু জানলেই চলবে যখনই কোন কিছু ! চিহ্ন দিয়ে শেষ হবে তখনি বুঝে নিতে হবে এটি একটি ম্যাক্রো।

একটি সহজ ম্যাক্রো যদি তৈরি করতে চাই তাহলেঃ

macro_rules! say_hello {
    () => {
        println!("Hello, Rust Macro!");
    };
}

এখানে আমরা say_hello নামে একটি ম্যাক্রো তৈরি করেছি। এর আগে এই নামে ফাংশন তৈরি করেছিলাম। এবার এটি একটি ম্যাক্রো। macro_rules! কিওয়ার্ড দিয়ে ম্যাক্রো তৈরি করতে হয়।

এবার যদি আমাদের say_hello ম্যাক্রো কল করতে চাই তাহলেঃ

fn main() {
    say_hello!();
}

এখানে খেয়াল করে এবার কিন্তু আমরা say_hello লেখার পরে একটা ! চিহ্ন দিয়েছি। কারণ এটি একটি ম্যাক্রো। আমাদের সাধারণ ডে-টু-ডে ইউজেসে ম্যাক্রো তৈরি করার খুব একটা দরকার পরবে না যদি না আমরা Rust ল্যাঙ্গুয়েজকে এক্সটেন্ড করে আমাদের নিজস্ব কোন কিছু তৈরি করতে না চাই। তবে ম্যাক্রো ব্যাবহার করতে হবে, যখন অনেক এ্যাডভান্স মেটা প্রোগ্রামিং শিখবো তখন আমরা ভালো ভাবে ম্যাক্রো সম্পর্কে জানবো।

পরিশেষে

এই ছিলো আজকের মত Rust সম্পর্কে বেসিক ধারণা। পরবর্তিতে চ্যাপ্টারে আমরা মেমরি ম্যানেজমেন্ট, Ownership এবং Borrowing সম্পর্কে জানবো। কেমন লাগলো Rust কমেন্ট সেকশনে জানাবেন।