Rust: مقدمة إلى ماكروات Rust
آخر تحديث: 2026-08-26
الماكرو هي «آلات توليد الكود» في لغة Rust — فما أن تكتب ماكروًّا مرة واحدة، حتى تقوم تلقائيًّا بتوليد عدد لا يحصى من النسخ المتماثلة لنفس الكود، مما يحرر المبرمجين من عناء النسخ واللصق.
إذا كانت الدالة «تغلف المنطق من أجل الاستدعاء المتكرر»، فإن الماكرو «يغلف قواعد توليد الكود من أجل التوسيع المتكرر». تعمل الدوال على القيم في وقت التشغيل، بينما تعمل الماكروات على الكود نفسه في وقت التحويل البرمجي. الأمر يشبه ما يلي: الدالة هي خط تجميع في مصنع (تستقبل المواد الخام وتنتج المنتجات)، بينما الماكرو هو مخطط المصنع (يستقبل رسومات التصميم وينتج خط التجميع بأكمله).
1. ما ستتعلمه
macro_rules!القواعد النحوية الأساسية وقواعد المطابقة الخاصة بمكروات الإعلان- كيفية استخدام أنماط التكرار
$()*و$()+ - لماذا يُعتبر كل من
vec!وprintln!ماكروات وليس دالات — الفرق الجوهري بين الدالات والماكروات الماكروات المدمجة الشائعة:vec!、println!、format!、todo!、unimplemented! - مقدمة في «النظافة الماكروية» ومفهوم «ماكروات العمليات»
2. قصة آلة طباعة النقود
(1) المعاناة: مأزق الكود المكرر
توم هو مهندس لغة «Rust» في شركة لوجستية. وقد كُلف بمهمة بدت بسيطة: كتابة دالة لكل وسيلة من وسائل النقل الخمس (الشاحنة، والسفينة، والطائرة، والقطار، والطائرة بدون طيار) لحساب تكاليف الشحن وأوقات التسليم المتوقعة.
- المنطق المتبع في كل وظيفة متطابق تقريبًا: قراءة المعدل الأساسي، وحساب عامل المسافة، وإضافة رسوم الوقود الإضافية
- الفرق الوحيد هو أن كل طريقة شحن لها جدول أسعار مختلف ومواعيد تسليم مختلفة.
- قام توم بنسخ النص ولصقه 5 مرات، وعدّل الأرقام قليلاً — يبدو أنه أنجز المهمة في غضون 10 دقائق.
إذن، ماذا حدث؟
- في اليوم الثالث، تغيرت قواعد حساب تكاليف الوقود — وكان على توم تعديل خمس دوال، لكنه أغفل واحدة منها.
- في الأسبوع الخامس، أُضيف خيار «النقل عبر سلسلة التبريد» — وقام توم بنسخه ولصقه للمرة السادسة.
- بحلول الشهر الثاني، تضخم حجم الكود ليصل إلى 300 سطر، كان 200 منها مكررة.
«سيكون من الرائع لو كانت هناك طريقة لكتابة قاعدة مرة واحدة، بحيث تقوم تلقائيًا بإنشاء جميع الوظائف المماثلة...»
(2) نهج ماكرو 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);
}
الناتج:
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) عملية التوسع الكلي
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) ▶ المثال:تعريف أول ماكرو لك — إنشاء دوال الاسترجاع تلقائيًا (مستوى الصعوبة ⭐)
// ============================================
// 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());
}
الناتج:
Name : Alice
Age : 20
Grade : A
يأخذ الماكرو
create_getter!اسم حقل ونوعًا، ويتم توسيعه ليصبح تعريفًا كاملاً لـpub fn. وعند توسيعه، فإن ثلاث استدعاءات لـcreate_getter!تعادل كتابة ثلاث دوال استرجاع يدويًّا. وهذا هو النموذج الأولي لـ «طابعة الكود» — حيث يحدد القالب قواعد توليد الكود، ويقوم كل استدعاء بطباعة جزء من الكود.
(2) ▶ المثال:أنماط التكرار $()* و$()+ — وحدات ماكرو ذات معلمات متغيرة (مستوى الصعوبة ⭐⭐)
// ============================================
// 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 ===");
}
الناتج:
=== 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! — فهم كيفية عمل الماكروات المدمجة (مستوى الصعوبة ⭐⭐)
// ============================================
// 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! ===");
}
الناتج:
=== 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! (مستوى الصعوبة: ⭐)
// ============================================
// 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.");
}
الناتج:
=== 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) ▶ المثال:النظافة في الماكرو — المتغيرات الداخلية للماكرو لا تلوث البيئة الخارجية (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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 ===");
}
الناتج:
=== 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) ▶ المثال:مثال شامل — إنشاء إطار عمل اختبار مصغر باستخدام الماكرو (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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 ===");
}
الناتج:
=== 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] استخدام إما $()+ أو $()*. إذا كنت تريد أن يقبل الماكرو معلمة واحدة على الأقل، فاستخدم $()+؛ وإذا سمحت بعدم وجود أي معلمات، فاستخدم $()*.tmp داخليًا، ولكن كان لدى المستدعي أيضًا متغير باسم tmp. أما ماكروات الإعلان في Rust فهي «صحية جزئيًا»: لا تتسرب أسماء المتغيرات التي يتم التقاطها بواسطة $ داخل الماكرو، مما يمنع حدوث مثل هذه الأخطاء.todo!() وunimplemented!()؟todo!() إلى أن «هذه الميزة مخطط لها ولكن لم يتم تنفيذها بعد»، ويمكن أن يتضمن معلمات لتحديد التقدم المحرز (على سبيل المثال، todo!("implement pagination")). unimplemented!() تشير إلى أن «هذه الواجهة/الطريقة غير مخطط لتنفيذها حاليًا». تُستخدم todo!() بشكل أكثر شيوعًا أثناء التطوير لتمييز العناصر في قائمة المهام، بينما تُستخدم unimplemented!() بشكل أكثر شيوعًا في التنفيذات الافتراضية للسمات.#[derive(...)] (مثل #[derive(Debug)])، وماكروات السمات (مثل #[test])، وماكروات الدوال (مثل #[async]، التي تقع خلف async fn). يجب تعريف الماكروات الإجرائية في proc-macro crate منفصل، بينما يمكن تعريف macro_rules! الماكروات التصريحية في أي مكان. يغطي هذا الدرس المفاهيم فقط؛ أما الاستخدام التفصيلي للماكروات الإجرائية فهو مادة متقدمة.📖 ملخص
macro_rules!هو نظام الماكروات التصريحي في لغة Rust، والذي يستخدم مطابقة الأنماط لاستبدال دفق من الرموز المدخلة بقوالب برمجية يتم توسيعها في وقت التحويل البرمجي.- أنماط التكرار
$()*(صفر مرة أو أكثر) و$()+(مرة واحدة أو أكثر) هما الآليتان الأساسيتان لتنفيذ الماكرو ذات الحجج المتغيرة. - الماكروات المدمجة الشائعة
vec!وprintln!وformat!وtodo!وunimplemented!هي في الأساس إعلانات ماكرو؛ أماvec!فتتوسع لتصبحVec::new()متبوعة بتكرارpush. - الدوال مقابل الماكرو: تعمل الدوال على القيم في وقت التشغيل، بينما تعمل الماكرو على الكود في وقت التحويل البرمجي؛ ومعلمات الدوال ثابتة، في حين أن معلمات الماكرو متغيرة؛ وتقوم الدوال بفحص الأنواع، بينما لا تخضع الماكرو لفحص الأنواع إلا بعد التوسيع.
- النظافة في الماكرو تضمن عدم تسرب المتغيرات الداخلية للماكرو إلى النطاق الخارجي، مما يمنع حدوث تعارض في الأسماء مع ماكروات لغة C.
- ماكروات العمليات هي نظام ماكروات أكثر تقدمًا يتضمن ثلاثة أنواع: الماكروات المشتقة، وماكروات السمات، وماكروات الدوال. ويجب تعريفها في صندوق
proc-macroمنفصل.
📝 تمارين
- الصعوبة ⭐: اكتب ماكرو
make_pair!يأخذ تعبيرين كمعاملين ويعيد توبلة(expr1, expr2). على سبيل المثال، يتم توسيعmake_pair!(42, "hello")ليصبح(42, "hello"). استدعِ هذا الماكرو فيmainواطبع النتيجة. - الصعوبة ⭐⭐: اكتب ماكرو
assert_equal!يأخذ تعبيرين كمعاملين. إذا كانا متساويين، فاطبع✅ PASS؛ وإلا، فاطبع❌ FAIL: left != right. استخدم ماكروstringify!لطباعة التعبيرات الأصلية. أنشئ 3 حالات اختبار على الأقل (بما في ذلك الحالات التي تكون فيها التعبيرات متساوية والحالات التي لا تكون فيها متساوية) وقم بتشغيلها. - الصعوبة ⭐⭐⭐: اكتب ماكرو
create_enum_with_display!يأخذ اسم قائمة التعداد ومجموعة من أسماء المتغيرات كمدخلات، ويقوم تلقائيًا بإنشاء تعريف قائمة التعداد وتنفيذ السمةDisplay(مع عرض كل متغير على شكل السلسلة المقابلة له). على سبيل المثال، يتم توسيعcreate_enum_with_display!(Color, Red, Green, Blue)إلى قائمةColor، حيث يتم عرضRedعلى أنه"Red". تلميح: استخدم نمط التكرار$()*وstringify!.