পাঠ ৬.১

Enum define করা

Defining an Enum

Struct related field group করে। Enum বলে দেয় — একটা value কয়েকটা সম্ভাব্য option-এর একটা মাত্র। যেমন Rectangle, Circle, Triangle হতে পারে — কিন্তু একসাথে দু'টা না। IP address V4 হতে পারে বা V6 — দু'টোর কোনো একটাই। এই "একটার একটাই" property-র জন্য enum uniquely ভালো fit।

Basic enum syntax

enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    route(IpAddrKind::V4);
    route(IpAddrKind::V6);
}

fn route(ip_kind: IpAddrKind) {}

V4 এবং V6 হলো এই enum-এর variant। Variant-গুলো enum-এর নামের নিচে namespaced — তাই double-colon দিয়ে access: IpAddrKind::V4। এখন route() function যেকোনো IpAddrKind accept করে — সব variant একই type।

Variant-এ data রাখা

এতদূর শুধু "kind" রাখলাম, address-ই নেই। struct দিয়ে combine করতে পারতাম:

struct IpAddr {
    kind: IpAddrKind,
    address: String,
}

let home = IpAddr {
    kind: IpAddrKind::V4,
    address: String::from("127.0.0.1"),
};

কিন্তু enum-এর variant-এ সরাসরি data রাখা যায় — আরও সংক্ষিপ্ত:

enum IpAddr {
    V4(String),
    V6(String),
}

let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));

IpAddr::V4() আসলে একটা constructor function — String নিয়ে IpAddr তৈরি করে। Rust auto-generate করে।

আরেকটা সুবিধা: প্রতিটা variant-এ আলাদা type ও সংখ্যক data থাকতে পারে। যেমন V4-কে চারটা u8 হিসেবে রাখা, V6-কে String হিসেবে:

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));

Standard library-তে আসলে এই pattern-ই আছে — variant-এর ভিতরে struct:

struct Ipv4Addr {
    // --snip--
}

struct Ipv6Addr {
    // --snip--
}

enum IpAddr {
    V4(Ipv4Addr),
    V6(Ipv6Addr),
}

বহুরকম variant — Message enum

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
  • Quit — কোনো data নেই।
  • Move — struct-এর মতো named field।
  • Write(String) — একটা String।
  • ChangeColor(i32, i32, i32) — তিনটা i32।

এই variant-গুলো আলাদা struct দিয়েও define করা যেত (QuitMessage, MoveMessage, ইত্যাদি)। কিন্তু তখন প্রতিটা আলাদা type হতো — একটা function-এ "যেকোনো message" নিতে পারতাম না। Enum সবগুলোকে একই type-এ রাখে।

Enum-এর method

Struct-এর মতো enum-এও impl দিয়ে method:

impl Message {
    fn call(&self) {
        // method body would be defined here
    }
}

let m = Message::Write(String::from("hello"));
m.call();

Option enum — null-এর সমাধান

Rust-এ null নেই। এটা deliberate design decision। Tony Hoare (যিনি ১৯৬৫ সালে null-এর ধারণা চালু করেছিলেন) ২০০৯-এ বলেছিলেন:

"I call it my billion-dollar mistake... Innumerable errors, vulnerabilities, and system crashes have probably caused a billion dollars of pain and damage in the last forty years."

সমস্যা — null-যুক্ত language-এ যেকোনো variable null হতে পারে; কিন্তু কোনটা হতে পারে আর কোনটা হতে পারে না — type system দিয়ে বোঝা যায় না। ফলে সারাদিন NullPointerException

কিন্তু "value আছে বা নেই" — এই ধারণা তো দরকার। Rust-এ এটা enum দিয়ে encode করা — Option<T>:

enum Option<T> {
    None,
    Some(T),
}

Option এতই common যে prelude-এ auto-import। SomeNone সরাসরি use করা যায় — Option:: prefix দরকার নেই।

<T> generic type parameter (Chapter 10-এ বিস্তারিত) — Some-এ যেকোনো type-এর data রাখা যায়:

fn main() {
    let some_number = Some(5);
    let some_char = Some('e');

    let absent_number: Option<i32> = None;
}

some_number-এর type Option<i32>। কিন্তু None-এ value নেই, তাই Rust infer করতে পারে না — annotate করতে হয়।

Option<T> কেন better than null?

কারণ — Option<T> এবং T ভিন্ন type। তাই Rust তোমাকে Option<T>-কে সরাসরি T-র মতো use করতে দেবে না:

fn main() {
    let x: i8 = 5;
    let y: Option<i8> = Some(5);

    let sum = x + y;
}
compile errortext
error[E0277]: cannot add `Option<i8>` to `i8`
 --> src/main.rs:5:17
  |
5 |     let sum = x + y;
  |                 ^ no implementation for `i8 + Option<i8>`

মানে — যখন তোমার hand-এ একটা i8, Rust guarantee দিচ্ছে এটা valid। null check-এর দরকার নেই। শুধু Option<i8>-এ পেলেই handle করতে হবে — এবং compiler force করবে handle করতে।

Some থেকে value বের করতে হলে match দিয়ে দু'টো case (Some এবং None) দু'টোই handle করতে হয়। সেটাই পরের পাঠের বিষয়।

এই পাঠ থেকে যা শিখলে

  • enum — কয়েকটা variant, value সবসময় একটা।
  • Variant-এ data attach করা যায় — আলাদা variant-এ আলাদা shape।
  • Variant-এর নাম-ই constructor function; impl দিয়ে method।
  • Option<T> = Some(T) বা None — null-এর type-safe বিকল্প।