Rust: مقدمة إلى ماكروات Rust

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

الماكرو هي «آلات توليد الكود» في لغة Rust — فما أن تكتب ماكروًّا مرة واحدة، حتى تقوم تلقائيًّا بتوليد عدد لا يحصى من النسخ المتماثلة لنفس الكود، مما يحرر المبرمجين من عناء النسخ واللصق.

إذا كانت الدالة «تغلف المنطق من أجل الاستدعاء المتكرر»، فإن الماكرو «يغلف قواعد توليد الكود من أجل التوسيع المتكرر». تعمل الدوال على القيم في وقت التشغيل، بينما تعمل الماكروات على الكود نفسه في وقت التحويل البرمجي. الأمر يشبه ما يلي: الدالة هي خط تجميع في مصنع (تستقبل المواد الخام وتنتج المنتجات)، بينما الماكرو هو مخطط المصنع (يستقبل رسومات التصميم وينتج خط التجميع بأكمله).


1. ما ستتعلمه



2. قصة آلة طباعة النقود

(1) المعاناة: مأزق الكود المكرر

توم هو مهندس لغة «Rust» في شركة لوجستية. وقد كُلف بمهمة بدت بسيطة: كتابة دالة لكل وسيلة من وسائل النقل الخمس (الشاحنة، والسفينة، والطائرة، والقطار، والطائرة بدون طيار) لحساب تكاليف الشحن وأوقات التسليم المتوقعة.

إذن، ماذا حدث؟

«سيكون من الرائع لو كانت هناك طريقة لكتابة قاعدة مرة واحدة، بحيث تقوم تلقائيًا بإنشاء جميع الوظائف المماثلة...»

(2) نهج ماكرو Rust

RUST
// Define using a macro"Shipping Cost Calculator"Template
macro_rules! create_shipping_calculator {
    // Matching Patterns: Name of Mode of Transportation + Rate per kilometer + Speed
    ($name:ident, $rate_per_km:expr, $speed:expr) => {
        fn $name(distance: f64) -> (f64, f64) {
            let base_cost = distance * $rate_per_km;
            let fuel_surcharge = base_cost * 0.1;
            let total = base_cost + fuel_surcharge;
            let time_hours = distance / $speed;
            (total, time_hours)
        }
    };
}

// Single-line macro call = Generate a complete function
create_shipping_calculator!(truck,  1.5,  60.0);
create_shipping_calculator!(ship,   0.8,  30.0);
create_shipping_calculator!(plane,  5.0, 800.0);
create_shipping_calculator!(train,  1.2,  80.0);
create_shipping_calculator!(drone,  2.0,  50.0);

fn main() {
    let distance = 500.0;
    // Every function exists by default.,Just like handwriting
    println!("Truck :  cost=${:.2}, time={:.1}h", truck(distance).0,  truck(distance).1);
    println!("Ship  :  cost=${:.2}, time={:.1}h", ship(distance).0,   ship(distance).1);
    println!("Plane :  cost=${:.2}, time={:.1}h", plane(distance).0,  plane(distance).1);
    println!("Train :  cost=${:.2}, time={:.1}h", train(distance).0,  train(distance).1);
    println!("Drone :  cost=${:.2}, time={:.1}h", drone(distance).0,  drone(distance).1);
}

الناتج:

TEXT 📖 للعرض فقط
Truck :  cost=$825.00, time=8.3h
Ship  :  cost=$440.00, time=16.7h
Plane :  cost=$2750.00, time=0.6h
Train :  cost=$660.00, time=6.2h
Drone :  cost=$1100.00, time=10.0h

تعتبر الماكروات بمثابة «طابعات أكواد»: فأنت تصمم قالبًا (لوحة الطباعة)، وفي كل مرة تستدعي فيها الماكرو، فإنها «تطبع» جزءًا كاملاً من الكود. وعندما تُجري تغييرات، ما عليك سوى تعديل مكان واحد في القالب، فيتم تحديث كل الكود الذي تم إنشاؤه في آن واحد — دون الحاجة إلى تعديل كل دالة يدويًّا واحدة تلو الأخرى.



3. المفاهيم الأساسية

(1) عملية التوسع الكلي

100%
graph TB
    A[macro_rules! Declaration Macro] --> B[Matching Arm 1: Pattern => Template]
    A --> C[Matching Arm 2: Pattern => Template]
    A --> D[Matching Arm N: Pattern => Template]

    B --> E[Compiler Matching input token]
    C --> E
    D --> E

    E --> F{Match Successful?}
    F -->|Yes| G[Replace the template and expand the code]
    F -->|No| H[Compilation Error: Mismatch]

    G --> I[Generate AST Node]
    I --> J[Continue compiling]

    style A fill:#4a90d9,color:#fff
    style E fill:#e6a23c,color:#fff
    style F fill:#f56c6c,color:#fff
    style G fill:#67c23a,color:#fff

(2) مقارنة بين الدوال والماكرو

البعد الدالة (fn) الماكرو (macro_rules!)
توقيت التنفيذ استدعاء وقت التشغيل التوسيع في وقت التحويل البرمجي
عدد المعلمات ثابت متغير (عبر أنماط التكرار)
نوع المعلمة نوع ثابت دفق رموز تعسفي
توليد الكود لا يُولِّد كودًا يُولِّد شجرة AST جديدة للكود
قيمة الإرجاع له نوع إرجاع يمكنه إنشاء أي مقتطف برمجي
الاستخدام foo(args) foo!(args) مع علامة تعجب
حدود التكرار عمق المكدس عمق التكرار في الماكرو (الافتراضي: 128 مستوى)
النظافة نطاق العزل الطبيعي إعلان النظافة الجزئية على مستوى الماكرو

(3) الماكروات المدمجة الشائعة

ماكرو وظيفة مثال
println! الطباعة إلى stdout وإضافة سطر جديد println!("Hello, {}!", name)
print! الطباعة إلى stdout دون إضافة سطر جديد print!("count: {}", i)
format! السلسلة المنسقة تُرجع قيمة من نوع String let s = format!("{}:{}", h, m)
vec! إنشاء متجه بسرعة let v = vec![1, 2, 3]
todo! عنصر بديل، يتسبب في حدوث حالة ذعر أثناء وقت التشغيل fn foo() { todo!() }
unimplemented! علامة غير مُنفَّذة، حالة ذعر في وقت التشغيل fn bar() { unimplemented!() }
eprintln! الطباعة إلى stderr eprintln!("Error: {}", msg)
write! الأنواع التي تُنفِّذ fmt::Write write!(&mut s, "{}", val)
concat! ربط السلاسل في وقت التحويل البرمجي concat!("a", "b", "c")"abc"
stringify! تحويل تعبير إلى سلسلة نصية ثابتة stringify!(1+2)"1 + 2"

(4) مقارنة بين ماكروات الإعلان وماكروات الإجراءات

البعد ماكرو الإعلان macro_rules! ماكرو الإجراء (Proc Macro)
طريقة التعريف macro_rules! name { ... } صندوق مستقل + #[proc_macro_*]
توقيت التوسيع مطابقة الأنماط والاستبدال في وقت التحويل البرمجي توليد الكود الإجرائي في وقت التحويل البرمجي
الإمكانات مطابقة الأنماط + استبدال الرموز يمكنه قراءة/إنشاء أي شجرة تحليل جملية (AST)
التعقيد منخفض (تصريحي) مرتفع (يتطلب كتابة كود بلغة Rust لمعالجة شجرة التحليل البنيوي)
الفئة الفرعية لا شيء الماكروات المشتقة #[derive] / الماكروات الخاصة بالسمات #[attr] / الماكروات الوظيفية name!()
التطبيقات النموذجية vec![]، println! serde::Serialize، tokio::main
صعوبة التصحيح متوسطة عالية


4. أمثلة على الماكرو

(1) ▶ المثال:تعريف أول ماكرو لك — إنشاء دوال الاسترجاع تلقائيًا (مستوى الصعوبة ⭐)

RUST
// ============================================
// Usage macro_rules! Define a Macro
// Scene: Automatically Generate getter Methods for Structure Fields
// ============================================

// Macro Definitions: Generate getter Function based on field name and type
// A macro name followed by ! Indicates that this is a macro
macro_rules! create_getter {
    // Matching Patterns: $name is an identifier (ident), $ty is a type (ty)
    // => The code block below is a template expansion.
    ($name:ident, $ty:ty) => {
        pub fn $name(&self) -> $ty {
            self.$name.clone()
        }
    };
}

// Structures That Use Macros
#[derive(Debug)]
struct Student {
    name: String,
    age: u32,
    grade: String,
}

impl Student {
    // Written by hand getter —— You have to write one for each field.
    // pub fn name(&self) -> String { self.name.clone() }
    // pub fn age(&self) -> u32 { self.age }
    // pub fn grade(&self) -> String { self.grade.clone() }

    // Automatically Generated Using a Macro —— One per line getter
    create_getter!(name, String);
    create_getter!(age, u32);
    create_getter!(grade, String);
}

fn main() {
    let s = Student {
        name: "Alice".to_string(),
        age: 20,
        grade: "A".to_string(),
    };

    // Generated by calling a macro getter Methods
    println!("Name  : {}", s.name());
    println!("Age   : {}", s.age());
    println!("Grade : {}", s.grade());
}

الناتج:

TEXT 📖 للعرض فقط
Name  : Alice
Age   : 20
Grade : A

يأخذ الماكرو create_getter! اسم حقل ونوعًا، ويتم توسيعه ليصبح تعريفًا كاملاً لـ pub fn. وعند توسيعه، فإن ثلاث استدعاءات لـ create_getter! تعادل كتابة ثلاث دوال استرجاع يدويًّا. وهذا هو النموذج الأولي لـ «طابعة الكود» — حيث يحدد القالب قواعد توليد الكود، ويقوم كل استدعاء بطباعة جزء من الكود.


(2) ▶ المثال:أنماط التكرار $()* و$()+ — وحدات ماكرو ذات معلمات متغيرة (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Repetition Patterns in Presentation Macros:$()* Zero or more times、$()+ Once or multiple times
// Scene: A Mini Test Framework, Supports multiple assertions
// ============================================

// Macro: Run Multiple Test Cases, each case includes a name + Expression + Expected Value
// $()* Indicates that the pattern inside the parentheses can be repeated zero or more times
macro_rules! run_tests {
    // Each test consists of (Name, Expression, Expected Value) Composition of Trios
    // $test_name It is an identifier,$expr It is an expression,$expected It is an expression
    ($( $test_name:ident, $expr:expr, $expected:expr );* $(;)?) => {
        $(
            println!("[Test] {} ...", stringify!($test_name));
            let result = $expr;
            let expected: i32 = $expected;
            if result == expected {
                println!("  ✅ PASS: {} == {}", result, expected);
            } else {
                println!("  ❌ FAIL: {} != {} (expected {})", stringify!($expr), result, expected);
            }
        )*
    };
}

// Macro: Calculate the sum of any number of values
// $()+ Indicates that the pattern inside the parentheses must be repeated at least once
macro_rules! sum_of {
    // Usage $()+ At least one argument is required.
    ($($x:expr),+ $(,)?) => {
        // 0 + $x Cumulative total: 0 + a + b + c ...
        {
            let mut sum = 0i64;
            $(
                sum += $x as i64;
            )+
            sum
        }
    };
}

fn main() {
    println!("=== Mini Test Framework ===");

    // Call run_tests! macro - Pass multiple test cases
    run_tests! {
        add_one,    1 + 1, 2;
        multiply,   3 * 4, 12;
        subtract,   10 - 3, 7;
        power,      2 * 2 * 2, 8
    }

    println!("\n=== Sum Calculator ===");

    // Call sum_of! macro - Pass any number of arguments
    let s1 = sum_of!(1, 2, 3, 4, 5);
    println!("sum_of!(1..5) = {}", s1);

    let s2 = sum_of!(10, 20, 30);
    println!("sum_of!(10,20,30) = {}", s2);

    // A single parameter is also acceptable.
    let s3 = sum_of!(42);
    println!("sum_of!(42) = {}", s3);

    println!("\n=== Done ===");
}

الناتج:

TEXT 📖 للعرض فقط
=== Mini Test Framework ===
[Test] add_one ...
  ✅ PASS: 2 == 2
[Test] multiply ...
  ✅ PASS: 12 == 12
[Test] subtract ...
  ✅ PASS: 7 == 7
[Test] power ...
  ✅ PASS: 8 == 8

=== Sum Calculator ===
sum_of!(1..5) = 15
sum_of!(10,20,30) = 60
sum_of!(42) = 42

=== Done ===

يُعد كل من $()* و$()+ عنصرين أساسيين لتنفيذ «المعلمات المتغيرة» في الماكرو. يشير $()* إلى «صفر تكرار أو أكثر» (على سبيل المثال، يمكن أن يكون Vec فارغًا)، بينما يشير $()+ إلى «تكرار واحد أو أكثر» (معلمة واحدة على الأقل). داخل أنماط التكرار، يمكن أيضًا استخدام الفواصل (,، ;، إلخ) للتحكم في كيفية تجميع المعلمات.


(3) ▶ المثال:تطبيق مبسط لماكرو vec! — فهم كيفية عمل الماكروات المدمجة (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Implement a simplified version of the vec! macro
// Understanding vec! The Underlying Principles of Macro Expansion
// ============================================

// Simplified vec! macro - Does not support vec![x; n] syntax
macro_rules! my_vec {
    // Empty vector
    () => {
        Vec::new()
    };
    // A single element
    ($elem:expr) => {
        {
            let mut v = Vec::new();
            v.push($elem);
            v
        }
    };
    // Multiple elements,Separated by commas
    ($($x:expr),+ $(,)?) => {
        {
            let mut v = Vec::new();
            $(
                v.push($x);
            )+
            v
        }
    };
}

fn main() {
    // Use the built-in vec! macro
    let builtin_empty: Vec<i32> = vec![];
    let builtin_one = vec![42];
    let builtin_multi = vec![1, 2, 3, 4, 5];

    println!("=== Built-in vec! ===");
    println!("empty  : {:?}", builtin_empty);
    println!("one    : {:?}", builtin_one);
    println!("multi  : {:?}", builtin_multi);

    // Use Custom my_vec! macro
    let my_empty: Vec<i32> = my_vec![];
    let my_one = my_vec![42];
    let my_multi = my_vec![10, 20, 30, 40, 50];

    println!("\n=== Custom my_vec! ===");
    println!("my_empty : {:?}", my_empty);
    println!("my_one   : {:?}", my_one);
    println!("my_multi : {:?}", my_multi);

    // Verify Functional Consistency
    assert_eq!(builtin_multi.len(), 5);
    assert_eq!(my_multi.len(), 5);
    assert_eq!(builtin_multi, vec![1, 2, 3, 4, 5]);
    assert_eq!(my_multi, vec![10, 20, 30, 40, 50]);

    println!("\n=== All assertions passed! ===");
}

الناتج:

TEXT 📖 للعرض فقط
=== Built-in vec! ===
empty  : []
one    : [42]
multi  : [1, 2, 3, 4, 5]

=== Custom my_vec! ===
my_empty : []
my_one   : [42]
my_multi : [10, 20, 30, 40, 50]

=== All assertions passed! ===

ما تراه من ماكرو vec! هو في الواقع ماكرو إعلان macro_rules!! وهو يستخدم $($x:expr),+ لمطابقة قائمة من التعبيرات المفصولة بفواصل، ثم يتوسع إلى كتل متكررة من كود v.push($x). وهذا «شيء لا تستطيع الدالة القيام به»— vec![1, 2, 3] لو كُتب كدالة، لكان من المستحيل تحديد عدد العناصر في وقت التحويل البرمجي وإنشاء كود push المقابل.


(4) ▶ المثال:استخدام الماكروات المدمجة todo! وunimplemented! (مستوى الصعوبة: ⭐)

RUST
// ============================================
// Demonstrate Built-in Macros: todo! / unimplemented! / format! / eprintln!
// Scene: An inventory management system currently under development
// ============================================

// Simulated Inventory Items
#[derive(Debug)]
struct InventoryItem {
    id: u32,
    name: String,
    quantity: u32,
}

// Inventory Manager
struct InventoryManager {
    items: Vec<InventoryItem>,
}

impl InventoryManager {
    fn new() -> InventoryManager {
        InventoryManager {
            items: Vec::new(),
        }
    }

    // Implemented: Add Item
    fn add_item(&mut self, id: u32, name: &str, quantity: u32) {
        self.items.push(InventoryItem {
            id,
            name: name.to_string(),
            quantity,
        });
        // Usage format! Macro-Formatted Log Messages
        let log_msg = format!("[INFO] Added item: {} (id={}, qty={})", name, id, quantity);
        println!("{}", log_msg);
    }

    // Implemented: Search for Products
    fn find_item(&self, id: u32) -> Option<&InventoryItem> {
        self.items.iter().find(|item| item.id == id)
    }

    // Not implemented: Update Inventory
    fn update_quantity(&mut self, _id: u32, _new_qty: u32) {
        // TODO: Implement the inventory update logic
        // todo!() will panic with a "Not implemented" message
        todo!("update_quantity: id={} quantity={}", _id, _new_qty);
    }

    // Not implemented: Generate an Inventory Report
    fn generate_report(&self) -> String {
        // unimplemented!() Indicates that this feature has not been implemented yet
        unimplemented!("generate_report() is not yet implemented");
    }

    // Implemented: Print All Items
    fn list_items(&self) {
        if self.items.is_empty() {
            println!("  (no items in inventory)");
            return;
        }
        for item in &self.items {
            println!("  #{} {} (qty: {})", item.id, item.name, item.quantity);
        }
    }
}

fn main() {
    let mut manager = InventoryManager::new();

    println!("=== Inventory Manager ===");

    // Add Item
    manager.add_item(101, "Laptop", 10);
    manager.add_item(102, "Mouse", 50);
    manager.add_item(103, "Keyboard", 30);

    // List Products
    println!("\nCurrent inventory:");
    manager.list_items();

    // Search for Products
    if let Some(item) = manager.find_item(102) {
        println!("\nFound: {:?}", item);
    }

    // Uncommenting the following code will panic (But it won't result in a compilation error):
    // manager.update_quantity(101, 8);   // panics: "not yet implemented"
    // let report = manager.generate_report(); // panics: "not yet implemented"

    println!("\n=== Demo completed ===");
    println!("Note: Try uncommenting update_quantity() or generate_report() to see todo!/unimplemented! in action.");
}

الناتج:

TEXT 📖 للعرض فقط
=== Inventory Manager ===
[INFO] Added item: Laptop (id=101, qty=10)
[INFO] Added item: Mouse (id=102, qty=50)
[INFO] Added item: Keyboard (id=103, qty=30)

Current inventory:
  #101 Laptop (qty: 10)
  #102 Mouse (qty: 50)
  #103 Keyboard (qty: 30)

Found: InventoryItem { id: 102, name: "Mouse", quantity: 50 }

=== Demo completed ===
Note: Try uncommenting update_quantity() or generate_report() to see todo!/unimplemented! in action.

يُعد كل من todo!() وunimplemented!() «أدوات قوية لاستخدام العناصر النائبة» لمطوري لغة Rust. ويمكن لـ todo!() أن يتضمن وصفًا (todo!("msg: {}", val))، مما يجعله مثاليًّا لتمييز الميزات غير المكتملة أثناء التطوير. تُعد unimplemented!() أكثر ملاءمةً لتمييز الطرق في تعريفات الواجهات التي لم يتم تنفيذها بعد. سيؤدي كلاهما إلى حدوث حالة ذعر (panic) أثناء وقت التشغيل، ولكنهما لن يتسببا في أخطاء تجميع — مما يتيح لك كتابة الكود أولاً وتنفيذه خطوة بخطوة.


(5) ▶ المثال:النظافة في الماكرو — المتغيرات الداخلية للماكرو لا تلوث البيئة الخارجية (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Demonstrating Macro Hygiene
// Variables created within a macro do not conflict with those in the outer scope.
// ============================================

// Note: Rust declaration macros have "partial hygiene"
// For internal use by Hong $ Captured variable names will not conflict with external ones
// However, identifiers written directly within the macro are in Rust 2018+ There are special rules in this case

// Macro: Create a local variable tmp and swap values
// Note: tmp written directly within the macro is's hygienic.——It will not affect the outside world. tmp
macro_rules! swap_with_tmp {
    ($a:expr, $b:expr) => {
        {
            let tmp = $a;
            $a = $b;
            $b = tmp;
        }
    };
}

// Macro: Demonstrate Hygiene - Even if there is a tmp variable outside
macro_rules! demonstrate_hygiene {
    ($x:expr) => {
        {
            // Defined internally by the macro tmp It's hygienic.
            let tmp = $x * 2;
            println!("  Inside macro: tmp = {}", tmp);
            tmp
        }
    };
}

// Counterexample of Unhygienic Conditions (Demonstrated differently)
// Note: Rust declarative macros do not allow creating variables that cause cross-scope conflicts.
// So here we use concat Simulation"Non-health-related"Potential Issues

fn main() {
    println!("=== Macro Hygiene Demonstration ===\n");

    // Scenario 1: Internal variables in a macro do not affect external variables
    println!("1. Macro internal variable vs external variable:");
    let mut x = 10;
    let mut y = 20;

    println!("  Before swap: x={}, y={}", x, y);

    // The macro uses the following internally: tmp,But the external variable names tmp Not affected
    swap_with_tmp!(x, y);

    println!("  After swap:  x={}, y={}", x, y);

    // External tmp The variable does not exist. —— Inside the macro tmp It's hygienic.
    // If you uncomment the following line, you'll get a compilation error.:
    // println!("tmp from macro = {}", tmp);  // ❌ Compilation Error: tmp not found

    // Scenario 2: Internal macro variables do not conflict with external variables of the same name
    println!("\n2. Hygiene with same name:");
    let tmp = 100;  // External tmp
    println!("  Outside macro: tmp = {}", tmp);

    let result = demonstrate_hygiene!(5);
    println!("  Return value: {}", result);
    println!("  Outside macro again: tmp = {}", tmp);  // It's still 100,Not affected by macros

    // Scenario 3: Why Is Hygiene Important?
    println!("\n3. Why hygiene matters:");
    println!("  Without hygiene, macros could accidentally:");
    println!("  - Overwrite variables in the caller's scope");
    println!("  - Create hard-to-find bugs");
    println!("  - Break encapsulation of the calling code");
    println!("  Rust's hygiene prevents these issues at compile time.");

    println!("\n=== Demo completed ===");
}

الناتج:

TEXT 📖 للعرض فقط
=== Macro Hygiene Demonstration ===

1. Macro internal variable vs external variable:
  Before swap: x=10, y=20
  After swap:  x=20, y=10

2. Hygiene with same name:
  Outside macro: tmp = 100
  Inside macro: tmp = 10
  Return value: 10
  Outside macro again: tmp = 100

3. Why hygiene matters:
  Without hygiene, macros could accidentally:
  - Overwrite variables in the caller's scope
  - Create hard-to-find bugs
  - Break encapsulation of the calling code
  Rust's hygiene prevents these issues at compile time.

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


(6) ▶ المثال:مثال شامل — إنشاء إطار عمل اختبار مصغر باستخدام الماكرو (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example: Building a Mini Unit Testing Framework Using Macros
// Use in combination:Matching Patterns、Repetition Pattern、Built-in Macros
// ============================================

// Test Results Summary
struct TestStats {
    total: u32,
    passed: u32,
    failed: u32,
}

impl TestStats {
    fn new() -> TestStats {
        TestStats { total: 0, passed: 0, failed: 0 }
    }

    fn print_summary(&self) {
        println!("\n==============================");
        println!("Test Summary:");
        println!("  Total : {}", self.total);
        println!("  Passed: {}", self.passed);
        println!("  Failed: {}", self.failed);
        if self.failed == 0 {
            println!("  ✅ All tests passed!");
        } else {
            println!("  ❌ {} test(s) failed", self.failed);
        }
        println!("==============================");
    }
}

// Macro: Define a set of test cases
// Each test is identified by its name、Assertion Expressions、Expected Results Breakdown
macro_rules! test_suite {
    // Matches zero or more tests
    ($( $name:ident: $left:expr, $op:tt, $right:expr );* $(;)?) => {{
        let mut stats = TestStats::new();
        $(
            stats.total += 1;
            print!("[Test] {}: {} {} {} ... ", stringify!($name),
                stringify!($left), stringify!($op), stringify!($right));

            let passed = match $op {
                == => { $left == $right }
                != => { $left != $right }
                <  => { $left < $right }
                <= => { $left <= $right }
                >  => { $left > $right }
                >= => { $left >= $right }
                _ => { panic!("Unsupported operator: {}", stringify!($op)); }
            };

            if passed {
                stats.passed += 1;
                println!("✅ PASS");
            } else {
                stats.failed += 1;
                println!("❌ FAIL (got {:?}, expected {:?})", $left, $right);
            }
        )*
        stats
    }};
}

fn main() {
    println!("=== Mini Test Framework ===");
    println!("Using macro-generated test suite\n");

    // Test Suites Defined Using Macros
    let stats = test_suite! {
        test_add:  2 + 2, ==, 4;
        test_sub:  10 - 3, ==, 7;
        test_mul:  3 * 4, ==, 12;
        test_div:  10 / 2, ==, 5;
        test_gt:   100, >, 50;
        test_lt:   3, <, 10;
        test_eq:   "hello", ==, "hello";
        test_neq:  42, !=, 0
    };

    // Print Statistics
    stats.print_summary();

    // Verify that all tests have passed
    assert_eq!(stats.total, 8);
    assert_eq!(stats.passed, 8);
    assert_eq!(stats.failed, 0);

    println!("\n=== Demo completed ===");
}

الناتج:

TEXT 📖 للعرض فقط
=== Mini Test Framework ===
Using macro-generated test suite

[Test] test_add: 2 + 2 == 4 ... ✅ PASS
[Test] test_sub: 10 - 3 == 7 ... ✅ PASS
[Test] test_mul: 3 * 4 == 12 ... ✅ PASS
[Test] test_div: 10 / 2 == 5 ... ✅ PASS
[Test] test_gt: 100 > 50 ... ✅ PASS
[Test] test_lt: 3 < 10 ... ✅ PASS
[Test] test_eq: "hello" == "hello" ... ✅ PASS
[Test] test_neq: 42 != 0 ... ✅ PASS

==============================
Test Summary:
  Total : 8
  Passed: 8
  Failed: 0
  ✅ All tests passed!
==============================

=== Demo completed ===

يوضح هذا المثال الشامل القوة الحقيقية للماكروات: حيث يأخذ الماكرو test_suite! مجموعة من تعريفات الاختبارات (الاسم، والتعبير، وعامل المقارنة، والقيمة المتوقعة) ويقوم تلقائيًا بتوسيعها إلى كود كامل لتنفيذ الاختبار. لا تتطلب حالات الاختبار الثماني المكتوبة باستخدام الماكرو سوى ثمانية أسطر من التعليمات البرمجية لاستدعائها، في حين أن كتابة التعليمات البرمجية المكافئة يدويًّا تتطلب ما لا يقل عن 60 سطرًا. والأهم من ذلك، إذا احتجت إلى إضافة ميزات مثل «مهلة انتهاء الاختبار» أو «تجميع الاختبارات»، فما عليك سوى تعديل مكان واحد في قالب الماكرو، وستتم تحديث جميع الاختبارات تلقائيًّا.



❓ أسئلة شائعة

س ما الفرق بالضبط بين الماكروات والدوال؟ متى ينبغي استخدام ماكرو بدلاً من دالة؟
ج يتم توسيع كود الماكروات في وقت التحويل البرمجي، بينما يتم تنفيذ الدوال في وقت التشغيل. يمكن للماكروات القيام بأمور لا تستطيع الدوال القيام بها: مثل التعامل مع عدد متغير من الوسيطات (مثل vec![1, 2, 3])، وإنشاء كود في وقت التحويل البرمجي، وقبول مقتطفات كود كوسيطات. ومع ذلك، فإن تصحيح أخطاء الماكروات أصعب، وتؤدي إلى كود أقل قابلية للقراءة، ويمكن أن تؤدي إلى رسائل خطأ غامضة في وقت التحويل البرمجي. أعطِ الأولوية لاستخدام الدوال، واستخدم الماكروات فقط عندما تحتاج إلى القيام بشيء لا تستطيع الدوال القيام به.
س ما الفرق بين $()* و$()+ في macro_rules!؟
ج $()* يطابق صفر تكرار أو أكثر (بما في ذلك السلسلة الفارغة)، بينما $()+ يطابق تكرارًا واحدًا أو أكثر (تكرار واحد على الأقل). على سبيل المثال، يستخدم vec![] (المتجه الفارغ) $()*، بينما يمكن لـ vec![1, 2] استخدام إما $()+ أو $()*. إذا كنت تريد أن يقبل الماكرو معلمة واحدة على الأقل، فاستخدم $()+؛ وإذا سمحت بعدم وجود أي معلمات، فاستخدم $()*.
س ما المقصود بـ«نظافة الماكرو»؟ ولماذا هي مهمة؟
ج تشير «النظافة» إلى حقيقة أن المتغيرات التي يتم إنشاؤها داخل الماكرو لا تتعارض مع المتغيرات الموجودة في النطاق الخارجي. في لغة C، تُعد الماكروات عمليات استبدال نصية، لذا يمكن أن تنشأ تعارضات في الأسماء بسهولة — على سبيل المثال، إذا استخدمت ماكرو ما tmp داخليًا، ولكن كان لدى المستدعي أيضًا متغير باسم tmp. أما ماكروات الإعلان في Rust فهي «صحية جزئيًا»: لا تتسرب أسماء المتغيرات التي يتم التقاطها بواسطة $ داخل الماكرو، مما يمنع حدوث مثل هذه الأخطاء.
س ما الفرق بين todo!() وunimplemented!()؟
ج كلاهما يتسبب في حدوث حالة ذعر (panic) أثناء وقت التشغيل، لكن دلالاتهما تختلف. يشير todo!() إلى أن «هذه الميزة مخطط لها ولكن لم يتم تنفيذها بعد»، ويمكن أن يتضمن معلمات لتحديد التقدم المحرز (على سبيل المثال، todo!("implement pagination")). unimplemented!() تشير إلى أن «هذه الواجهة/الطريقة غير مخطط لتنفيذها حاليًا». تُستخدم todo!() بشكل أكثر شيوعًا أثناء التطوير لتمييز العناصر في قائمة المهام، بينما تُستخدم unimplemented!() بشكل أكثر شيوعًا في التنفيذات الافتراضية للسمات.
س ما المقصود بالماكرو الإجرائي؟
ج الماكرو الإجرائي هو نظام ماكرو أكثر قوة من الماكروات التصريحية؛ فهو لا يقوم بـ«مطابقة الأنماط والاستبدال»، بل «يتلقى كودًا → ينفذ دالة في لغة Rust → يُخرج كودًا جديدًا». هناك ثلاثة أنواع: الماكروات المشتقة #[derive(...)] (مثل #[derive(Debug)])، وماكروات السمات (مثل #[test])، وماكروات الدوال (مثل #[async]، التي تقع خلف async fn). يجب تعريف الماكروات الإجرائية في proc-macro crate منفصل، بينما يمكن تعريف macro_rules! الماكروات التصريحية في أي مكان. يغطي هذا الدرس المفاهيم فقط؛ أما الاستخدام التفصيلي للماكروات الإجرائية فهو مادة متقدمة.

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: اكتب ماكرو make_pair! يأخذ تعبيرين كمعاملين ويعيد توبلة (expr1, expr2). على سبيل المثال، يتم توسيع make_pair!(42, "hello") ليصبح (42, "hello"). استدعِ هذا الماكرو في main واطبع النتيجة.
  2. الصعوبة ⭐⭐: اكتب ماكرو assert_equal! يأخذ تعبيرين كمعاملين. إذا كانا متساويين، فاطبع ✅ PASS؛ وإلا، فاطبع ❌ FAIL: left != right. استخدم ماكرو stringify! لطباعة التعبيرات الأصلية. أنشئ 3 حالات اختبار على الأقل (بما في ذلك الحالات التي تكون فيها التعبيرات متساوية والحالات التي لا تكون فيها متساوية) وقم بتشغيلها.
  3. الصعوبة ⭐⭐⭐: اكتب ماكرو create_enum_with_display! يأخذ اسم قائمة التعداد ومجموعة من أسماء المتغيرات كمدخلات، ويقوم تلقائيًا بإنشاء تعريف قائمة التعداد وتنفيذ السمة Display (مع عرض كل متغير على شكل السلسلة المقابلة له). على سبيل المثال، يتم توسيع create_enum_with_display!(Color, Red, Green, Blue) إلى قائمة Color، حيث يتم عرض Red على أنه "Red". تلميح: استخدم نمط التكرار $()* وstringify!.
Web-Tutorial.com

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

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

100%