Rust: قوائم التعداد (Enums) و`Option` في لغة Rust

آخر تحديث: 2026-08-26

تعد القوائم (Enums) من أقوى أنواع البيانات في لغة Rust — فهي تسمح للقيمة بأن تكون واحدة من مجموعة من المتغيرات المحتملة، ويمكن أن يحمل كل متغير بيانات مختلفة.

إذا كانت البنية (struct) من النوع «و» (تحتوي على جميع الحقول في آن واحد)، فإن التعداد (enum) يكون من النوع «أو» (إما هذا أو ذاك).


1. ما ستتعلمه



2. المخططات المفاهيمية

100%
flowchart LR
    subgraph "enum Option<T>"
        S["Some(T)<br>Not null"]
        N["None<br>No value"]
    end
    subgraph "Use Cases"
        V["Calculation Results"] -->|"Success"| S
        F["Calculation Results"] -->|"Failure/None"| N
    end
    S --> MATCH["match Processing"]
    N --> MATCH
    MATCH -->|"Some(v) => Usage Value v"| OK["✅ Safety"]
    MATCH -->|"None => Handling Null Values"| SAFE["✅ No crashes"]


3. قصة نظام الطلبات

(1) المشكلة: تمثيل حالة الطلب بالأرقام

يعمل توم على تطوير نظام للتجارة الإلكترونية. وللطلبات أربع حالات:

TEXT 📖 للعرض فقط
0 = Payable, 1 = Paid, 2 = Shipped, 3 = Completed
RUST
let order_status = 0;  // Payable
// But what if someone were to write order_status = 99?
// Or confuse the status with the quantity: let order_status = product_count;

يُعد تمثيل الحالات بالأعداد الصحيحة ممارسة شائعة في لغة C — وهي ليست آمنة من حيث النوع. يمكن تعيين أي عدد صحيح لـ«حالة»، ولن يقوم المُترجم بالتحقق من ذلك نيابة عنك. علاوة على ذلك، لا توجد علاقة بين الحالات، مما يجعلها عرضة للأخطاء.

(2) حلول لتعدادات لغة Rust

RUST
enum OrderStatus {
    Pending,       // Payable
    Paid,          // Paid
    Shipped,       // Shipped -- you can bring your shipping tracking number
    Delivered,     // Completed -- delivery time (in-person)
}

fn main() {
    let status = OrderStatus::Paid;

    match status {
        OrderStatus::Pending => println!("Please complete the payment"),
        OrderStatus::Paid => println!("Paid, awaiting shipment"),
        OrderStatus::Shipped => println!("Shipped, on the way"),
        OrderStatus::Delivered => println!("Delivered, thank you for your purchase"),
    }

    // The compiler ensures that: you won't forget to handle any state!
    // If a new status is added Cancelled, forgot to update match, the compiler will report an error
}

تقوم قائمة التعداد (enum) بتجميع جميع «الخيارات» الممكنة في نوع واحد، ويضمن استخدام match مع قائمة التعداد التحقق الشامل — حيث يتم التعامل مع كل حالة دون إغفال أي منها.



4. أنواع التعداد

(1) يمكن أن تحمل متغيرات التعداد بيانات

RUST
enum Message {
    Quit,                       // No data available
    Move { x: i32, y: i32 },   // Anonymous Structures
    Write(String),              // Single value
    ChangeColor(i32, i32, i32), // Tuple
}
نوع التعداد البيانات التي يحملها الاستخدام
Quit لا شيء مجرد علامة
Move { x, y } بنية مجهولة Message::Move { x: 10, y: 20 }
Write(String) بنية التوبول Message::Write("hello".to_string())
ChangeColor(i,i,i) بنية التوبول Message::ChangeColor(255, 0, 0)

(2) مرجع سريع لطرق الخيارات الشائعة

الطريقة نوع القيمة المرجعة الوصف السلوك عند وجود «None»
unwrap() T استخراج القيمة حالة ذعر
expect(msg) T استرداد القيمة (رسالة مخصصة) panic + msg
unwrap_or(default) T استرداد القيمة أو القيمة الافتراضية إرجاع القيمة الافتراضية
map(f) Option<U> تحويل القيمة الموجودة في "Some" الاحتفاظ بـ "None"
and_then(f) Option<U> تشغيل خيار التسلسل عدم الاحتفاظ بأي شيء
filter(f) Option<T> مرشح الشرط «لا شيء» في حالة عدم استيفاء الشرط
is_some() bool له قيمة false
is_none() bool «لا شيء» صحيح
ok_or(err) Result<T, E> تحويل إلى النتيجة خطأ (err)

(3) مقارنة بين «Option» و«null»

البعد Option<T> null (لغات أخرى)
أمان الأنواع يُفرض من قِبل المُترجم التعامل مع القيم الفارغة قد تكون أي مرجعية فارغة
فحوصات القيم الفارغة التجاوز التلقائي عبر match/تسلسل الطرق يتطلب إجراء فحوصات يدوية if
نسيت التحقق خطأ في التجميع استثناء NullPointerException أثناء التشغيل
العمليات المتسلسلة map/and_then، وما إلى ذلك تتطلب جمل if متداخلة أو التسلسل الاختياري

(4) تخطيط ذاكرة القوائم التعدادية

الذاكرة التي يستهلكها التعداد = الذاكرة التي يستهلكها أكبر متغير + 1 بايت للعلامة. يستخدم المُترجم العلامة لتحديد المتغير المخزّن حاليًا.

100%
graph TB
    subgraph "Message Enumerations in Memory"
        TAG[tag: 1 Byte] -->|Indicates which variant is currently selected| LABEL
        DATA[Data on the Largest Variant: 8 Byte] --> LABEL[In total 9 Byte]
    end


5. أمثلة على التعداد

(1) ▶ المثال:تحمل متغيرات قائمة التعداد أنواعًا مختلفة من البيانات (الصعوبة ⭐⭐)

RUST
// ============================================
// Enumeration variants carry data -- Message Type
// ============================================

#[derive(Debug)]
enum Message {
    Quit,                              // No data available
    Move { x: i32, y: i32 },          // Anonymous Structures
    Write(String),                     // Tuple Structure
    ChangeColor(i32, i32, i32),       // Tuple Structure
}

impl Message {
    fn call(&self) {
        match self {
            Message::Quit => println!("Quit: Exit"),
            Message::Move { x, y } => println!("Move: Move to ({}, {})", x, y),
            Message::Write(text) => println!("Write: Message Content: {}", text),
            Message::ChangeColor(r, g, b) => {
                println!("ChangeColor: Color RGB({}, {}, {})", r, g, b);
            }
        }
    }
}

fn main() {
    let messages = vec![
        Message::Write(String::from("Hello")),
        Message::Move { x: 10, y: 20 },
        Message::ChangeColor(255, 0, 0),
        Message::Quit,
    ];

    for msg in &messages {
        msg.call();
    }
}

الناتج:

TEXT 📖 للعرض فقط
Write: Message Content: Hello
Move: Move to (10, 20)
ChangeColor: Color RGB(255, 0, 0)
Quit: Exit

يمكن أن يحمل كل متغير من متغيرات قائمة التعداد عددًا ونوعًا مختلفين من البيانات. لا يحتوي Quit على أي بيانات، بينما يحتوي Move على بنية مجهولة، وWrite على سلسلة أحرف، وChangeColor على ثلاثة قيم من نوع i32. وهنا تكمن مرونة قوائم التعداد مقارنة بالبنى.


(2) ▶ المثال:Option<T>—التعامل مع القيم الفارغة في لغة Rust (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Option<T> Enumeration: Not null (Some) or null (None)
// ============================================

// Option Definition (in the standard library)
// enum Option<T> {
//     Some(T),
//     None,
// }

fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None  // Back "no value" -- instead of crashing or returning NaN
    } else {
        Some(a / b)  // Back "not null"
    }
}

fn main() {
    let result1 = divide(10.0, 2.0);
    let result2 = divide(10.0, 0.0);

    // Use match to process Option
    match result1 {
        Some(value) => println!("10 / 2 = {}", value),
        None => println!("The divisor is 0"),
    }

    match result2 {
        Some(value) => println!("10 / 0 = {}", value),
        None => println!("The divisor is 0"),
    }

    // Simplify: unwrap or expect (risky, but convenient)
    // println!("{}", result2.unwrap());  // ❌ unwrap on None will panic!
    println!("result1 the value of: {}", result1.unwrap());  // ✅ unwrap on Some is safe
}

الناتج:

TEXT 📖 للعرض فقط
10 / 2 = 5
The divisor is 0
result1 the value of: 5

Option<T> هي الطريقة القياسية في لغة Rust للتعامل مع القيم «التي قد تكون فارغة». على عكس اللغات الأخرى التي تتسبب في تعطل وقت التشغيل عند استخدام null/nil/None، فإن Option<T> في Rust تجبرك على التعامل بشكل صريح مع كل من الحالتين «غير فارغة» و«فارغة» باستخدام match أو unwrap.


(3) ▶ المثال:الطرق الشائعة لحل Option (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Option Practical Methods for: map, unwrap_or, expect
// ============================================

fn main() {
    let some_value: Option<i32> = Some(10);
    let none_value: Option<i32> = None;

    // map: If there is a value, convert it; no value, maintain None
    let doubled_some = some_value.map(|x| x * 2);
    let doubled_none = none_value.map(|x| x * 2);
    println!("map after: {:?}, {:?}", doubled_some, doubled_none);

    // unwrap_or: Returns a value, if no value is provided, the default is used.
    println!("unwrap_or: {}, {}", 
        some_value.unwrap_or(0),   // 10
        none_value.unwrap_or(0),   // 0
    );

    // expect: Returns a value, if no value, panic and display a custom message
    println!("expect: {}", some_value.expect("There should be a value"));
    // println!("expect: {}", none_value.expect("OH NO! No value!"));  // ❌ panic

    // is_some / is_none: Check if a value exists
    println!("is_some: {}, is_none: {}", 
        some_value.is_some(), 
        none_value.is_none(),
    );

    // Example of a Chain Call
    let result = Some(5)
        .map(|x| x + 3)
        .map(|x| x * 2)
        .unwrap_or(0);
    println!("Results of a chained call: {}", result);  // 16
}

الناتج:

TEXT 📖 للعرض فقط
map after: Some(20), None
unwrap_or: 10, 0
expect: 10
is_some: true, is_none: true
Results of a chained call: 16

يوفر Option<T> مجموعة من الطرق التي تتيح لك ربط العمليات على القيم التي قد تكون فارغة بأسلوب وظيفي، دون الحاجة إلى كتابة match في كل مرة. يقوم map بتحويل القيم، ويوفر unwrap_or قيمًا افتراضية، ويقدم expect رسائل خطأ أوضح أثناء تصحيح الأخطاء.


(4) ▶ المثال:شمولية التعدادات وmatch (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Enumerate proprietary match Exhaustive Check Demonstration
// ============================================

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("Lucky Penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
        // If I don't write Quarter, the compiler will report an error: non-exhaustive patterns
    }
}

// Enumeration with Data
#[derive(Debug)]
enum IpAddr {
    V4(u8, u8, u8, u8),  // 4 bytes
    V6(String),           // Complete IPv6 Address
}

fn main() {
    let coin = Coin::Quarter;
    println!("Value of Coins: {} cents", value_in_cents(coin));

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

    match home {
        IpAddr::V4(a, b, c, d) => {
            println!("IPv4: {}.{}.{}.{}", a, b, c, d);
        }
        IpAddr::V6(addr) => {
            println!("IPv6: {}", addr);
        }
    }
}

الناتج:

TEXT 📖 للعرض فقط
Value of Coins: 25 cents
IPv4: 127.0.0.1

يجب معالجة كل متغير من متغيرات قائمة التعداد في عبارة match — وهو ما يُعرف بـ«التحقق الشامل». وإذا نسيت معالجة أحد المتغيرات، فسيبلغ المُجمِّع عن وجود خطأ. وهذا أكثر أمانًا بكثير من switch في لغة C (ففي لغة C، إذا نسيت case في عبارة switch، فسيتخطاها ببساطة دون إخطار).


(5) ▶ المثال:تمرين شامل — محلل مدخلات المستخدم (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example: Enumeration + Option + match Real-World Experience
// ============================================

#[derive(Debug)]
enum Command {
    Help,
    Greet(String),
    Calc(f64, char, f64),
    Quit,
}

fn parse_command(input: &str) -> Option<Command> {
    let parts: Vec<&str> = input.trim().split_whitespace().collect();
    if parts.is_empty() {
        return None;
    }
    match parts[0] {
        "help" | "h" => Some(Command::Help),
        "quit" | "q" => Some(Command::Quit),
        "greet" if parts.len() > 1 => {
            Some(Command::Greet(parts[1..].join(" ")))
        }
        "calc" if parts.len() == 4 => {
            let a = parts[1].parse::<f64>().ok()?;
            let op = parts[2].chars().next()?;
            let b = parts[3].parse::<f64>().ok()?;
            Some(Command::Calc(a, op, b))
        }
        _ => None,
    }
}

fn execute(cmd: Command) -> bool {
    match cmd {
        Command::Help => {
            println!("Command: help | greet <name> | calc <a> <op> <b> | quit");
            true
        }
        Command::Greet(name) => {
            println!("Hello, {}!", name);
            true
        }
        Command::Calc(a, op, b) => {
            let result = match op {
                '+' => Some(a + b),
                '-' => Some(a - b),
                '*' => Some(a * b),
                '/' if b != 0.0 => Some(a / b),
                '/' => { println!("Error: Divisor is zero"); None }
                _ => { println!("Unknown Operator: {}", op); None }
            };
            result.map(|r| println!("{} {} {} = {:.2}", a, op, b, r));
            true
        }
        Command::Quit => {
            println!("Goodbye!");
            false
        }
    }
}

fn main() {
    let inputs = [
        "help",
        "greet Alice",
        "calc 10 + 5",
        "calc 20 / 4",
        "calc 1 / 0",
        "unknown",
        "quit",
    ];

    println!("=== Command Parser ===");
    for input in inputs {
        println!("\n> {}", input);
        match parse_command(input) {
            Some(cmd) => {
                if !execute(cmd) { break; }
            }
            None => println!("Unrecognized command"),
        }
    }
}

الناتج:

TEXT 📖 للعرض فقط
=== Command Parser ===

> help
Command: help | greet <name> | calc <a> <op> <b> | quit

> greet Alice
Hello, Alice!

> calc 10 + 5
10 + 5 = 15.00

> calc 20 / 4
20 / 4 = 5.00

> calc 1 / 0
Error: Divisor is zero

> unknown
Unrecognized command

> quit
Goodbye!

يجمع هذا المثال بين استخدام متغيرات التعداد التي تحمل بيانات مختلفة، Option العمليات المتسلسلة (ok()?، .ok()?match عمليات الفحص الشاملة، ونمط parse_command الإرجاع Option<Command>. يُعد استخدام التعداد (Enum) + الخيار (Option) + المطابقة (match) النموذج القياسي في لغة Rust للتعامل مع «النتائج المحتملة المتعددة».



❓ أسئلة شائعة

س ما الفرق بين قائمة التعداد (enumeration) والبنية (struct)؟
ج البنية هي نوع «كلاهما» (نوع «و»)، بينما قائمة التعداد هي نوع «إما هذا أو ذاك» (نوع «أو»).
س لماذا تستخدم لغة Rust Option بدلاً من null؟
ج Option آمنة من حيث النوع — حيث يُجبرك المُترجم على التعامل مع الحالات التي «قد تكون فيها القيمة فارغة».
س ما الفرق بين some وunwrap؟
ج some تعني «قد تكون هناك قيمة»، بينما unwrap تعني «أنا متأكد من وجود قيمة؛ وإلا فسيحدث تعطل».
س ما مقدار البيانات التي يمكن أن يخزنها التعداد؟
ج المساحة التي يشغلها التعداد = حجم أكبر متغير + علامة بحجم 1 بايت.
س كيف تُسمى البيانات في متغيرات التعداد؟
ج استخدم صيغة البنية المجهولة.

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: عرّف قائمة Weekday تحتوي على أيام الأسبوع من الاثنين إلى الأحد. اكتب دالة تأخذ Weekday كمدخل وتُرجع ما إذا كان اليوم يوم عمل أم عطلة نهاية الأسبوع (باستخدام match).
  2. الصعوبة ⭐⭐: اكتب دالة fn safe_sqrt(x: f64) -> Option<f64> تُرجع Some(x.sqrt()) إذا كان x >= 0، وNone في الحالات الأخرى. اختبر الدالة مع الأعداد الموجبة والسالبة في main.
  3. الصعوبة ⭐⭐⭐: عرّف قائمة Temperature تتضمن متغيرين: Celsius(f64) وFahrenheit(f64). قم بتنفيذ الطريقتين fn to_celsius(&self) -> f64 وfn to_fahrenheit(&self) -> f64. في main، أنشئ قيمتين لدرجة الحرارة وقم بالتحويل بينهما.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%