ماكروات Rust: كيف تجعلها تعمل مع الوحدات؟
هل سبق لك أن كتبت ماكرواً مفيداً في Rust، ثم حاولت استخدامه في وحدة مجاورة — وحصلت على خطأ غامض من المترجم؟ إذا كان الأمر كذلك، فأنت لست وحدك. سؤال "How do I use a macro across module files?" حصد آلاف المشاهدات على Stack Overflow، وهذا ليس بدون سبب.
في Rust، الماكروات هي أداة قوية للبرمجة الوصفية. فهي تسمح بتوليد الكود أثناء التنفيذ، وتجنب التكرار، وكتابة DSL (لغات خاصة بالمجال). ولكن المشكلة: الماكروات لا تتصرف مثل الدوال العادية. لها قواعدها الخاصة في الرؤية، وغالباً ما يتعثر المبتدئون بها.
في هذا البرنامج التعليمي، سأريكم ثلاث طرق مثبتة لتنظيم الماكروات بين ملفات الوحدات. هيا بنا!
لماذا لا تعمل الماكروات "افتراضياً"؟
قبل الغوص في الحل، دعنا نفهم السبب. في Rust، الدوال والأنواع تكون مرئية فقط في وحدتها الخاصة (إذا لم تكن موسومة بـ pub). نفس القصة تنطبق على الماكروات، ولكن هناك فارق: pub للماكروات لا يعمل بنفس طريقة الدوال.
لنأخذ مثالاً بسيطاً. لنفترض أن لدينا مشروعاً بهذا الهيكل:
src/├── main.rs├── helpers/│ ├── mod.rs│ └── macros.rsفي macros.rs، أعلنا عن ماكرو:
// src/helpers/macros.rsmacro_rules! greet { ($name:expr) => { println!("مرحباً، {}!", $name); };}الآن نريد استخدامه في main.rs:
// src/main.rsmod helpers;
fn main() { greet!("العالم"); // خطأ! الماكرو غير مرئي.}سيصدر المترجم شيئاً مثل: error: macro `greet` is not imported. لماذا؟ لأن الماكروات لا يتم تصديرها افتراضياً من الوحدة، حتى لو تم التصريح عن الوحدة كـ pub.
الطريقة 1: السمة #[macro_use] لاستيراد الماكروات
أبسط طريقة هي استخدام السمة #[macro_use] قبل التصريح عن الوحدة. هذا يجبر Rust على استيراد جميع الماكروات من تلك الوحدة إلى النطاق الحالي.
لنصلح مثالنا:
// src/main.rs#[macro_use]mod helpers;
fn main() { greet!("العالم"); // الآن يعمل!}ولكن هناك مشكلة: #[macro_use] يستورد جميع الماكروات من الوحدة. إذا كان لديك العديد من الماكروات، فقد يؤدي ذلك إلى تضارب في الأسماء. بالإضافة إلى ذلك، يعتبر هذا الأسلوب قديماً في Rust الحديث (بدءاً من الإصدار 2018).
أين لا يزال هذا مفيداً؟ في المشاريع الصغيرة أو عندما تكتب نموذجاً أولياً وتريد ربط بضع ماكروات بسرعة دون عناء إضافي.
فارق مهم: ترتيب التصريح
إذا كنت تستخدم #[macro_use]، تصبح الماكروات متاحة مباشرة بعد التصريح عن الوحدة. هذا يعني أن الكود التالي لن يعمل:
// src/main.rsfn use_greet() { greet!("اختبار"); // خطأ! الوحدة لم تُصرّح عنها بعد.}
#[macro_use]mod helpers;
fn main() { use_greet();}الترتيب مهم. ضع هذا في اعتبارك.
الطريقة 2: التصدير الصريح للماكروات باستخدام #[macro_export]
طريقة أكثر حداثة وتحكماً هي استخدام السمة #[macro_export] مباشرة في تعريف الماكرو. هذا يجعل الماكرو متاحاً على مستوى الكريت (crate)، وليس فقط الوحدة.
إليك كيف يعمل:
// src/helpers/macros.rs#[macro_export]macro_rules! greet { ($name:expr) => { println!("مرحباً، {}!", $name); };}الآن في main.rs يمكننا استيراد الماكرو صراحة باستخدام use:
// src/main.rsmod helpers;
use helpers::greet;
fn main() { greet!("العالم"); // يعمل!}لاحظ: نحن نستخدم use helpers::greet، وليس use hel