ডেটা টাইপ (Data Types)

Rust-এর প্রতিটি মানের একটি নির্দিষ্ট ডেটা টাইপ রয়েছে, যা Rust-কে জানায় যে কী ধরনের ডেটা নির্দিষ্ট করা হচ্ছে, যাতে এটি জানতে পারে যে সেই ডেটা নিয়ে কীভাবে কাজ করতে হবে। আমরা দুটি ডেটা টাইপ সাবসেট দেখব: স্কেলার (scalar) এবং কম্পাউন্ড (compound)।

মনে রাখবেন যে Rust হল একটি স্ট্যাটিকালি টাইপড (statically typed) ল্যাঙ্গুয়েজ, যার মানে হল কম্পাইল করার সময় এটিকে সমস্ত ভেরিয়েবলের টাইপ জানতে হবে। কম্পাইলার সাধারণত মান এবং আমরা কীভাবে এটি ব্যবহার করি তার উপর ভিত্তি করে আমরা কোন টাইপ ব্যবহার করতে চাই তা অনুমান করতে পারে। যেসব ক্ষেত্রে অনেকগুলো টাইপ সম্ভব, যেমন আমরা যখন চ্যাপ্টার ২-এর “অনুমানের সাথে গোপন সংখ্যার তুলনা করা” বিভাগে parse ব্যবহার করে একটি String-কে সাংখ্যিক টাইপে রূপান্তর করেছি, তখন আমাদের অবশ্যই একটি টাইপ অ্যানোটেশন যোগ করতে হবে, এইভাবে:

#![allow(unused)]
fn main() {
let guess: u32 = "42".parse().expect("সংখ্যা নয়!");
}

আমরা যদি উপরের কোডে দেখানো : u32 টাইপ অ্যানোটেশন যোগ না করি, তাহলে Rust নিম্নলিখিত এররটি প্রদর্শন করবে, যার মানে হল কম্পাইলারের আমাদের কাছ থেকে আরও তথ্য প্রয়োজন যাতে আমরা কোন টাইপ ব্যবহার করতে চাই তা জানা যায়:

$ cargo build
   Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
error[E0284]: type annotations needed
 --> src/main.rs:2:9
  |
2 |     let guess = "42".parse().expect("Not a number!");
  |         ^^^^^        ----- type must be known at this point
  |
  = note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `guess` an explicit type
  |
2 |     let guess: /* Type */ = "42".parse().expect("Not a number!");
  |              ++++++++++++

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

আপনি অন্যান্য ডেটা টাইপের জন্য ভিন্ন টাইপ অ্যানোটেশন দেখতে পাবেন।

স্কেলার টাইপ (Scalar Types)

একটি স্কেলার টাইপ একটি একক মান উপস্থাপন করে। Rust-এ চারটি প্রাথমিক স্কেলার টাইপ রয়েছে: ইন্টিজার (integers), ফ্লোটিং-পয়েন্ট সংখ্যা (floating-point numbers), বুলিয়ান (Booleans) এবং ক্যারেক্টার (characters)। আপনি হয়তো এগুলো অন্যান্য প্রোগ্রামিং ল্যাঙ্গুয়েজ থেকে চিনতে পারেন। চলুন, দেখি এগুলো Rust-এ কীভাবে কাজ করে।

ইন্টিজার টাইপ (Integer Types)

একটি ইন্টিজার হল ভগ্নাংশ ছাড়া একটি সংখ্যা। আমরা চ্যাপ্টার ২-এ একটি ইন্টিজার টাইপ ব্যবহার করেছি, u32 টাইপ। এই টাইপ ডিক্লারেশন নির্দেশ করে যে এর সাথে সম্পর্কিত মানটি একটি আনসাইনড ইন্টিজার হওয়া উচিত (সাইনড ইন্টিজার টাইপগুলো u-এর পরিবর্তে i দিয়ে শুরু হয়) যা 32 বিট জায়গা নেয়। টেবিল 3-1 Rust-এর বিল্ট-ইন ইন্টিজার টাইপগুলো দেখায়। আমরা একটি ইন্টিজার মানের টাইপ ঘোষণা করতে এই ভেরিয়েন্টগুলোর যেকোনো একটি ব্যবহার করতে পারি।

Table 3-1: Integer Types in Rust

LengthSignedUnsigned
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

প্রতিটি ভেরিয়েন্ট সাইনড বা আনসাইনড হতে পারে এবং একটি স্পষ্ট আকার রয়েছে। সাইনড এবং আনসাইনড বলতে বোঝায় যে সংখ্যাটি ঋণাত্মক হতে পারে কিনা—অন্য কথায়, সংখ্যাটির সাথে একটি চিহ্ন থাকা দরকার কিনা (সাইনড) অথবা এটি সর্বদা ধনাত্মক হবে কিনা এবং তাই চিহ্ন ছাড়াই উপস্থাপন করা যেতে পারে কিনা (আনসাইনড)। এটি কাগজে সংখ্যা লেখার মতো: যখন চিহ্নটি গুরুত্বপূর্ণ, তখন একটি সংখ্যা প্লাস চিহ্ন বা মাইনাস চিহ্ন দিয়ে দেখানো হয়; তবে, যখন এটি ধনাত্মক বলে ধরে নেওয়া নিরাপদ, তখন এটি কোনো চিহ্ন ছাড়াই দেখানো হয়। সাইনড সংখ্যাগুলো টু’স কমপ্লিমেন্ট উপস্থাপনা ব্যবহার করে সংরক্ষণ করা হয়।

প্রতিটি সাইনড ভেরিয়েন্ট −(2n − 1) থেকে 2n − 1 − 1 পর্যন্ত সংখ্যা সংরক্ষণ করতে পারে, যেখানে n হল সেই ভেরিয়েন্টটি ব্যবহার করা বিটের সংখ্যা। সুতরাং একটি i8 −(27) থেকে 27 − 1 পর্যন্ত সংখ্যা সংরক্ষণ করতে পারে, যা −128 থেকে 127 এর সমান। আনসাইনড ভেরিয়েন্টগুলো 0 থেকে 2n − 1 পর্যন্ত সংখ্যা সংরক্ষণ করতে পারে, তাই একটি u8 0 থেকে 28 − 1 পর্যন্ত সংখ্যা সংরক্ষণ করতে পারে, যা 0 থেকে 255 এর সমান।

অতিরিক্তভাবে, isize এবং usize টাইপগুলো আপনার প্রোগ্রামটি যে কম্পিউটারের আর্কিটেকচারের উপর চলছে তার উপর নির্ভর করে, যা টেবিলে “arch” হিসাবে চিহ্নিত করা হয়েছে: আপনি যদি 64-বিট আর্কিটেকচারে থাকেন তবে 64 বিট এবং আপনি যদি 32-বিট আর্কিটেকচারে থাকেন তবে 32 বিট।

আপনি টেবিল 3-2-তে দেখানো যেকোনো ফর্ম্যাটে ইন্টিজার লিটারেল লিখতে পারেন। মনে রাখবেন যে সংখ্যা লিটারেলগুলো, যেগুলো একাধিক সাংখ্যিক টাইপ হতে পারে, টাইপ নির্ধারণ করতে 57u8-এর মতো একটি টাইপ সাফিক্স ব্যবহার করার অনুমতি দেয়। সংখ্যা লিটারেলগুলো সংখ্যাটিকে সহজে পড়ার জন্য _ কে ভিজ্যুয়াল বিভাজক হিসাবেও ব্যবহার করতে পারে, যেমন 1_000, যেটির মান 1000 নির্দিষ্ট করার মতোই হবে।

Table 3-2: Integer Literals in Rust

Number literalsExample
Decimal98_222
Hex0xff
Octal0o77
Binary0b1111_0000
Byte (u8 only)b'A'

তাহলে আপনি কীভাবে জানবেন কোন ধরনের ইন্টিজার ব্যবহার করতে হবে? আপনি যদি নিশ্চিত না হন, তাহলে Rust-এর ডিফল্টগুলো সাধারণত শুরু করার জন্য ভালো জায়গা: ইন্টিজার টাইপগুলো ডিফল্টভাবে i32 হয়। isize বা usize ব্যবহার করার প্রাথমিক পরিস্থিতি হল যখন কোনো ধরনের কালেকশন ইনডেক্স করা হয়।

ইন্টিজার ওভারফ্লো (Integer Overflow)

ধরা যাক আপনার কাছে u8 টাইপের একটি ভেরিয়েবল রয়েছে যা 0 থেকে 255-এর মধ্যে মান ধারণ করতে পারে। আপনি যদি সেই ভেরিয়েবলের মান সেই সীমার বাইরের কোনো মানে পরিবর্তন করার চেষ্টা করেন, যেমন 256, তাহলে ইন্টিজার ওভারফ্লো ঘটবে, যার ফলে দুটি আচরণের মধ্যে একটি হতে পারে। আপনি যখন ডিবাগ মোডে কম্পাইল করছেন, তখন Rust ইন্টিজার ওভারফ্লোর জন্য চেক অন্তর্ভুক্ত করে যা এই আচরণ ঘটলে আপনার প্রোগ্রামটিকে রানটাইমে প্যানিক (panic) ঘটায়। Rust প্যানিকিং শব্দটি ব্যবহার করে যখন একটি প্রোগ্রাম এরর-সহ প্রস্থান করে; আমরা চ্যাপ্টার ৯-এর panic! দিয়ে পুনরুদ্ধার করা যায় না এমন এরর” বিভাগে প্যানিক নিয়ে আরও বিস্তারিত আলোচনা করব।

আপনি যখন --release ফ্ল্যাগ দিয়ে রিলিজ মোডে কম্পাইল করছেন, তখন Rust ইন্টিজার ওভারফ্লোর জন্য চেক অন্তর্ভুক্ত করে না যা প্যানিকের কারণ হয়। পরিবর্তে, যদি ওভারফ্লো ঘটে, তাহলে Rust টু’স কমপ্লিমেন্ট র‍্যাপিং (two’s complement wrapping) সম্পাদন করে। সংক্ষেপে, টাইপটি যে মানগুলো ধারণ করতে পারে তার সর্বাধিক মানের চেয়ে বড় মানগুলো টাইপটি যে মানগুলো ধারণ করতে পারে তার সর্বনিম্ন মানে “র‍্যাপ অ্যারাউন্ড” করে। u8-এর ক্ষেত্রে, 256 মানটি 0 হয়ে যায়, 257 মানটি 1 হয়ে যায়, এবং এইভাবে চলতে থাকে। প্রোগ্রামটি প্যানিক করবে না, তবে ভেরিয়েবলের একটি মান থাকবে যা সম্ভবত আপনি যা আশা করছিলেন তা নয়। ইন্টিজার ওভারফ্লোর র‍্যাপিং আচরণের উপর নির্ভর করা একটি এরর হিসাবে বিবেচিত হয়।

ওভারফ্লোর সম্ভাবনা স্পষ্টভাবে হ্যান্ডেল করতে, আপনি প্রিমিটিভ সাংখ্যিক টাইপগুলোর জন্য স্ট্যান্ডার্ড লাইব্রেরি দ্বারা সরবরাহ করা এই ফ্যামিলি অফ মেথডগুলো ব্যবহার করতে পারেন:

  • সমস্ত মোডে র‍্যাপ করার জন্য wrapping_* মেথডগুলো ব্যবহার করুন, যেমন wrapping_add
  • যদি checked_* মেথডগুলোর সাথে ওভারফ্লো হয় তবে None মানটি রিটার্ন করুন।
  • overflowing_* মেথডগুলোর সাথে মান এবং একটি বুলিয়ান রিটার্ন করুন যা নির্দেশ করে যে ওভারফ্লো হয়েছে কিনা।
  • saturating_* মেথডগুলোর সাথে মানের সর্বনিম্ন বা সর্বোচ্চ মানগুলোতে স্যাচুরেট করুন।

ফ্লোটিং-পয়েন্ট টাইপ (Floating-Point Types)

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

এখানে একটি উদাহরণ দেওয়া হল যা ফ্লোটিং-পয়েন্ট সংখ্যাগুলো অ্যাকশনে দেখায়:

Filename: src/main.rs

fn main() {
    let x = 2.0; // f64

    let y: f32 = 3.0; // f32
}

ফ্লোটিং-পয়েন্ট সংখ্যাগুলো IEEE-754 স্ট্যান্ডার্ড অনুযায়ী উপস্থাপন করা হয়।

সাংখ্যিক অপারেশন (Numeric Operations)

Rust সমস্ত সংখ্যা টাইপের জন্য আপনার প্রত্যাশিত বেসিক গাণিতিক অপারেশনগুলো সমর্থন করে: যোগ, বিয়োগ, গুণ, ভাগ এবং অবশিষ্টাংশ (remainder)। ইন্টিজার বিভাজন শূন্যের দিকে নিকটতম ইন্টিজারে ছোট হয়। নিম্নলিখিত কোডটি দেখায় যে আপনি কীভাবে একটি let স্টেটমেন্টে প্রতিটি সাংখ্যিক অপারেশন ব্যবহার করবেন:

Filename: src/main.rs

fn main() {
    // addition
    let sum = 5 + 10;

    // subtraction
    let difference = 95.5 - 4.3;

    // multiplication
    let product = 4 * 30;

    // division
    let quotient = 56.7 / 32.2;
    let truncated = -5 / 3; // Results in -1

    // remainder
    let remainder = 43 % 5;
}

এই স্টেটমেন্টগুলোর প্রতিটি এক্সপ্রেশন একটি গাণিতিক অপারেটর ব্যবহার করে এবং একটি একক মানে মূল্যায়ন করে, যা তারপর একটি ভেরিয়েবলের সাথে বাইন্ড করা হয়। Appendix B-তে Rust যে সমস্ত অপারেটর সরবরাহ করে তার একটি তালিকা রয়েছে।

বুলিয়ান টাইপ (The Boolean Type)

বেশিরভাগ অন্যান্য প্রোগ্রামিং ল্যাঙ্গুয়েজের মতো, Rust-এ একটি বুলিয়ান টাইপের দুটি সম্ভাব্য মান রয়েছে: true এবং false। বুলিয়ানগুলোর আকার এক বাইট। Rust-এ বুলিয়ান টাইপ bool ব্যবহার করে নির্দিষ্ট করা হয়। উদাহরণস্বরূপ:

Filename: src/main.rs

fn main() {
    let t = true;

    let f: bool = false; // with explicit type annotation
}

বুলিয়ান মানগুলো ব্যবহার করার প্রধান উপায় হল কন্ডিশনালের মাধ্যমে, যেমন একটি if এক্সপ্রেশন। আমরা “কন্ট্রোল ফ্লো” বিভাগে Rust-এ if এক্সপ্রেশনগুলো কীভাবে কাজ করে তা আলোচনা করব।

ক্যারেক্টার টাইপ (The Character Type)

Rust-এর char টাইপ হল ভাষার সবচেয়ে প্রিমিটিভ আলফাবেটিক টাইপ। char মান ঘোষণার কয়েকটি উদাহরণ নিচে দেওয়া হলো:

Filename: src/main.rs

fn main() {
    let c = 'z';
    let z: char = 'ℤ'; // with explicit type annotation
    let heart_eyed_cat = '😻';
}

লক্ষ্য করুন যে আমরা char লিটারেলগুলোকে সিঙ্গেল কোট দিয়ে নির্দিষ্ট করি, স্ট্রিং লিটারেলগুলোর বিপরীতে, যেগুলো ডাবল কোট ব্যবহার করে। Rust-এর char টাইপের আকার চার বাইট এবং এটি একটি ইউনিকোড স্কেলার মান (Unicode Scalar Value) উপস্থাপন করে, যার মানে এটি কেবল ASCII-এর চেয়ে আরও অনেক কিছু উপস্থাপন করতে পারে। অ্যাকসেন্টেড অক্ষর; চীনা, জাপানি এবং কোরিয়ান অক্ষর; ইমোজি; এবং জিরো-উইথ স্পেস সবই Rust-এ বৈধ char মান। ইউনিকোড স্কেলার মানগুলো U+0000 থেকে U+D7FF এবং U+E000 থেকে U+10FFFF পর্যন্ত। তবে, একটি “ক্যারেক্টার” আসলে ইউনিকোডে একটি ধারণা নয়, তাই “ক্যারেক্টার” বলতে আপনি যা বোঝেন, সেটি Rust-এর char-এর সাথে নাও মিলতে পারে। আমরা চ্যাপ্টার ৮-এ “স্ট্রিং দিয়ে UTF-8 এনকোডেড টেক্সট সংরক্ষণ করা”-তে এই বিষয়টি নিয়ে বিস্তারিত আলোচনা করব।

কম্পাউন্ড টাইপ (Compound Types)

কম্পাউন্ড টাইপগুলো একাধিক মানকে একটি টাইপে গ্রুপ করতে পারে। Rust-এর দুটি প্রিমিটিভ কম্পাউন্ড টাইপ রয়েছে: টাপল (tuples) এবং অ্যারে (arrays)।

টাপল টাইপ (The Tuple Type)

একটি টাপল হল বিভিন্ন টাইপের বেশ কয়েকটি মানকে একটি কম্পাউন্ড টাইপে একত্রিত করার একটি সাধারণ উপায়। টাপলগুলোর একটি নির্দিষ্ট দৈর্ঘ্য থাকে: একবার ঘোষণা করা হলে, সেগুলোর আকার বাড়তে বা কমতে পারে না।

আমরা প্যারেনথেসিসের মধ্যে কমা-দ্বারা-বিচ্ছিন্ন মানগুলোর একটি তালিকা লিখে একটি টাপল তৈরি করি। টাপলের প্রতিটি অবস্থানের একটি টাইপ রয়েছে এবং টাপলের বিভিন্ন মানের টাইপগুলো একই হতে হবে না। আমরা এই উদাহরণে ঐচ্ছিক টাইপ অ্যানোটেশন যোগ করেছি:

Filename: src/main.rs

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

ভেরিয়েবল tup সম্পূর্ণ টাপলের সাথে বাইন্ড করে, কারণ একটি টাপলকে একটি একক কম্পাউন্ড উপাদান হিসাবে বিবেচনা করা হয়। টাপল থেকে தனி মানগুলো পেতে, আমরা প্যাটার্ন ম্যাচিং ব্যবহার করে একটি টাপল মানকে ডিস্ট্রাকচার করতে পারি, এইভাবে:

Filename: src/main.rs

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

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

এই প্রোগ্রামটি প্রথমে একটি টাপল তৈরি করে এবং এটিকে tup ভেরিয়েবলের সাথে বাইন্ড করে। তারপর এটি let-এর সাথে একটি প্যাটার্ন ব্যবহার করে tup-কে তিনটি আলাদা ভেরিয়েবল, x, y এবং z-এ পরিণত করে। এটিকে ডিস্ট্রাকচারিং বলা হয়, কারণ এটি একক টাপলকে তিনটি ভাগে ভেঙে দেয়। অবশেষে, প্রোগ্রামটি y-এর মান প্রিন্ট করে, যেটি হল 6.4

আমরা একটি পিরিয়ড (.) ব্যবহার করে সরাসরি একটি টাপল এলিমেন্ট অ্যাক্সেস করতে পারি, তারপরে আমরা যে মানটি অ্যাক্সেস করতে চাই তার ইনডেক্স দিতে পারি। উদাহরণস্বরূপ:

Filename: src/main.rs

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}

এই প্রোগ্রামটি টাপল x তৈরি করে এবং তারপর তাদের নিজ নিজ ইনডেক্স ব্যবহার করে টাপলের প্রতিটি এলিমেন্ট অ্যাক্সেস করে। বেশিরভাগ প্রোগ্রামিং ল্যাঙ্গুয়েজের মতো, একটি টাপলের প্রথম ইনডেক্স হল 0।

কোনো মান ছাড়া টাপলের একটি বিশেষ নাম আছে, ইউনিট (unit)। এই মান এবং এর সংশ্লিষ্ট টাইপ উভয়ই () লেখা হয় এবং একটি খালি মান বা একটি খালি রিটার্ন টাইপ উপস্থাপন করে। এক্সপ্রেশনগুলো যদি অন্য কোনো মান রিটার্ন না করে তবে অন্তর্নিহিতভাবে ইউনিট মান রিটার্ন করে।

অ্যারে টাইপ (The Array Type)

একাধিক মানের একটি সংগ্রহ রাখার আরেকটি উপায় হল একটি অ্যারে (array)। টাপলের মতো নয়, একটি অ্যারের প্রতিটি উপাদানের একই টাইপ হতে হবে। অন্য কিছু ল্যাঙ্গুয়েজের অ্যারের মতো নয়, Rust-এর অ্যারেগুলোর একটি নির্দিষ্ট দৈর্ঘ্য রয়েছে।

আমরা একটি অ্যারের মানগুলোকে স্কোয়ার ব্র্যাকেটের ভিতরে কমা-দ্বারা-বিচ্ছিন্ন তালিকা হিসাবে লিখি:

Filename: src/main.rs

fn main() {
    let a = [1, 2, 3, 4, 5];
}

অ্যারেগুলো দরকারী যখন আপনি চান আপনার ডেটা হিপের (heap) পরিবর্তে স্ট্যাকে (stack) বরাদ্দ করা হোক (আমরা স্ট্যাক এবং হিপ সম্পর্কে আরও আলোচনা করব চ্যাপ্টার ৪-এ), অথবা যখন আপনি নিশ্চিত করতে চান যে আপনার কাছে সর্বদা একটি নির্দিষ্ট সংখ্যক উপাদান রয়েছে। একটি অ্যারে ভেক্টর টাইপের মতো নমনীয় নয়। একটি ভেক্টর (vector) হল স্ট্যান্ডার্ড লাইব্রেরি দ্বারা সরবরাহ করা একটি অনুরূপ কালেকশন টাইপ যা আকারে বাড়তে বা কমতে পারে। আপনি যদি অ্যারে বা ভেক্টর ব্যবহার করবেন কিনা তা নিয়ে অনিশ্চিত হন, তাহলে সম্ভবত আপনার ভেক্টর ব্যবহার করা উচিত। চ্যাপ্টার ৮-এ ভেক্টর সম্পর্কে আরও বিস্তারিত আলোচনা করা হয়েছে।

তবে, অ্যারেগুলো আরও দরকারী যখন আপনি জানেন যে উপাদানগুলোর সংখ্যা পরিবর্তন করার প্রয়োজন হবে না। উদাহরণস্বরূপ, আপনি যদি একটি প্রোগ্রামে মাসের নামগুলো ব্যবহার করেন, তাহলে আপনি সম্ভবত ভেক্টরের পরিবর্তে একটি অ্যারে ব্যবহার করবেন কারণ আপনি জানেন যে এটিতে সর্বদা 12টি উপাদান থাকবে:

#![allow(unused)]
fn main() {
let months = ["জানুয়ারি", "ফেব্রুয়ারি", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই",
              "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর"];
}

আপনি একটি অ্যারের টাইপ লেখেন স্কয়ার ব্র্যাকেট ব্যবহার করে, যার মধ্যে প্রতিটি উপাদানের টাইপ, একটি সেমিকোলন এবং তারপর অ্যারেতে থাকা উপাদানগুলোর সংখ্যা থাকে, এইভাবে:

#![allow(unused)]
fn main() {
let a: [i32; 5] = [1, 2, 3, 4, 5];
}

এখানে, i32 হল প্রতিটি উপাদানের টাইপ। সেমিকোলনের পরে, সংখ্যা 5 নির্দেশ করে যে অ্যারেটিতে পাঁচটি উপাদান রয়েছে।

আপনি প্রতিটি উপাদানের জন্য একই মান রাখতে একটি অ্যারে ইনিশিয়ালাইজ করতে পারেন, প্রাথমিক মানটি নির্দিষ্ট করে, তারপর একটি সেমিকোলন এবং তারপর স্কয়ার ব্র্যাকেটে অ্যারের দৈর্ঘ্য উল্লেখ করে, যেমনটি এখানে দেখানো হয়েছে:

#![allow(unused)]
fn main() {
let a = [3; 5];
}

a নামের অ্যারেটিতে 5টি উপাদান থাকবে যা প্রাথমিকভাবে 3 মানে সেট করা হবে। এটি let a = [3, 3, 3, 3, 3]; লেখার মতোই, কিন্তু আরও সংক্ষিপ্ত উপায়ে।

অ্যারে এলিমেন্ট অ্যাক্সেস করা (Accessing Array Elements)

একটি অ্যারে হল একটি পরিচিত, নির্দিষ্ট আকারের মেমরির একটি একক অংশ যা স্ট্যাকে বরাদ্দ করা যেতে পারে। আপনি ইনডেক্সিং ব্যবহার করে একটি অ্যারের উপাদানগুলো অ্যাক্সেস করতে পারেন, এইভাবে:

Filename: src/main.rs

fn main() {
    let a = [1, 2, 3, 4, 5];

    let first = a[0];
    let second = a[1];
}

এই উদাহরণে, first নামের ভেরিয়েবলটি 1 মান পাবে কারণ অ্যারের [0] ইনডেক্সে সেই মানটি রয়েছে। second নামের ভেরিয়েবলটি অ্যারের [1] ইনডেক্স থেকে 2 মান পাবে।

অবৈধ অ্যারে এলিমেন্ট অ্যাক্সেস (Invalid Array Element Access)

দেখা যাক কী ঘটে যদি আপনি অ্যারের শেষের বাইরের কোনো উপাদান অ্যাক্সেস করার চেষ্টা করেন। ধরুন আপনি এই কোডটি চালান, চ্যাপ্টার ২-এর অনুমান করার গেমের মতো, ব্যবহারকারীর কাছ থেকে একটি অ্যারে ইনডেক্স পেতে:

Filename: src/main.rs

use std::io;

fn main() {
    let a = [1, 2, 3, 4, 5];

    println!("Please enter an array index.");

    let mut index = String::new();

    io::stdin()
        .read_line(&mut index)
        .expect("Failed to read line");

    let index: usize = index
        .trim()
        .parse()
        .expect("Index entered was not a number");

    let element = a[index];

    println!("The value of the element at index {index} is: {element}");
}

এই কোডটি সফলভাবে কম্পাইল হয়। আপনি যদি cargo run ব্যবহার করে এই কোডটি চালান এবং 0, 1, 2, 3, বা 4 প্রবেশ করেন, তাহলে প্রোগ্রামটি অ্যারের সেই ইনডেক্সে সংশ্লিষ্ট মানটি প্রিন্ট করবে। আপনি যদি এর পরিবর্তে অ্যারের শেষের বাইরের কোনো সংখ্যা প্রবেশ করেন, যেমন 10, তাহলে আপনি এইরকম আউটপুট দেখতে পাবেন:

thread 'main' panicked at src/main.rs:19:19:
index out of bounds: the len is 5 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

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

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