Rust: قوائم التعداد (Enums) و`Option` في لغة Rust
آخر تحديث: 2026-08-26
تعد القوائم (Enums) من أقوى أنواع البيانات في لغة Rust — فهي تسمح للقيمة بأن تكون واحدة من مجموعة من المتغيرات المحتملة، ويمكن أن يحمل كل متغير بيانات مختلفة.
إذا كانت البنية (struct) من النوع «و» (تحتوي على جميع الحقول في آن واحد)، فإن التعداد (enum) يكون من النوع «أو» (إما هذا أو ذاك).
1. ما ستتعلمه
- تعريف أنواع التعداد والمتغيرات
- يمكن أن تحتوي متغيرات التعداد على أنواع مختلفة من البيانات
- استخدم
implلإضافة طرق إلى قائمة التعداد Option<T>التعداد — نهج لغة «Rust» في التعامل مع «القيم الفارغة»- التحقق من شمولية عمليات التعداد وعبارات
match Option<T>الطرق الشائعة (unwrap، map، expect)
2. المخططات المفاهيمية
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) المشكلة: تمثيل حالة الطلب بالأرقام
يعمل توم على تطوير نظام للتجارة الإلكترونية. وللطلبات أربع حالات:
0 = Payable, 1 = Paid, 2 = Shipped, 3 = Completed
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
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) يمكن أن تحمل متغيرات التعداد بيانات
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 بايت للعلامة. يستخدم المُترجم العلامة لتحديد المتغير المخزّن حاليًا.
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) ▶ المثال:تحمل متغيرات قائمة التعداد أنواعًا مختلفة من البيانات (الصعوبة ⭐⭐)
// ============================================
// 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();
}
}
الناتج:
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 (مستوى الصعوبة ⭐⭐)
// ============================================
// 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
}
الناتج:
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 (مستوى الصعوبة ⭐⭐)
// ============================================
// 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
}
الناتج:
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 (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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);
}
}
}
الناتج:
Value of Coins: 25 cents
IPv4: 127.0.0.1
يجب معالجة كل متغير من متغيرات قائمة التعداد في عبارة
match— وهو ما يُعرف بـ«التحقق الشامل». وإذا نسيت معالجة أحد المتغيرات، فسيبلغ المُجمِّع عن وجود خطأ. وهذا أكثر أمانًا بكثير منswitchفي لغة C (ففي لغة C، إذا نسيتcaseفي عبارةswitch، فسيتخطاها ببساطة دون إخطار).
(5) ▶ المثال:تمرين شامل — محلل مدخلات المستخدم (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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"),
}
}
}
الناتج:
=== 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 للتعامل مع «النتائج المحتملة المتعددة».
❓ أسئلة شائعة
Option بدلاً من null؟Option آمنة من حيث النوع — حيث يُجبرك المُترجم على التعامل مع الحالات التي «قد تكون فيها القيمة فارغة».some وunwrap؟some تعني «قد تكون هناك قيمة»، بينما unwrap تعني «أنا متأكد من وجود قيمة؛ وإلا فسيحدث تعطل».📖 ملخص
- Enum هو «نوع اتحاد» — حيث يمكن أن تكون القيمة واحدة من عدة متغيرات
- يمكن أن تحتوي متغيرات القوائم على بيانات (في شكل مجموعات أو هياكل مجهولة)
Option<T>هو التعداد الأكثر استخدامًا في المكتبة القياسية، ويُستخدم كبديل للقيمة «null»- عند استخدامه مع
match، يفرض المُترجم التحقق الشامل يوفرOptionطرقًا متسلسلة مثلmapوunwrap_orوexpect. - استهلاك الذاكرة لعملية التعداد ≈ الحجم الأقصى للمتغير + علامة بحجم 1 بايت
📝 تمارين
- الصعوبة ⭐: عرّف قائمة
Weekdayتحتوي على أيام الأسبوع من الاثنين إلى الأحد. اكتب دالة تأخذWeekdayكمدخل وتُرجع ما إذا كان اليوم يوم عمل أم عطلة نهاية الأسبوع (باستخدامmatch). - الصعوبة ⭐⭐: اكتب دالة
fn safe_sqrt(x: f64) -> Option<f64>تُرجعSome(x.sqrt())إذا كان x >= 0، وNoneفي الحالات الأخرى. اختبر الدالة مع الأعداد الموجبة والسالبة فيmain. - الصعوبة ⭐⭐⭐: عرّف قائمة
Temperatureتتضمن متغيرين:Celsius(f64)وFahrenheit(f64). قم بتنفيذ الطريقتينfn to_celsius(&self) -> f64وfn to_fahrenheit(&self) -> f64. فيmain، أنشئ قيمتين لدرجة الحرارة وقم بالتحويل بينهما.