لماذا يحتاج مطور Rust إلى لغة خاصة به؟ انغمس في عالم DSL
\n\nتخيل أنك تكتب محللاً لملف إعدادات معقد، أو تنفذ منطق لعبة، أو تصف قواعد عمل. في كل مرة تصطدم بنفس المشكلة: يصبح الكود ضخماً، وتقل قابلية القراءة. ماذا لو كان بإمكانك إنشاء لغة مصغرة مصممة خصيصاً لمهمتك؟ هذا هو اللغة الموجهة للمجال (DSL بلغة Rust).
\n\nRust هي البيئة المثالية لإنشاء DSL. نظام وحدات الماكرو الخاص بها، واستنتاج الأنواع القوي، والتكلفة الصفرية للتجريدات تسمح ببناء لغات لا تقل أداءً عن الكود المكتوب يدوياً. في هذه المقالة، سنشرح كيفية تحويل Rust إلى أداة بناء للغاتك الخاصة، ونوضح لماذا قد يكون هذا مفيداً لك تحديداً.
\n\nما هو DSL ولماذا هو مطلوب في Rust؟
\n\nDSL (Domain-Specific Language) هي لغة تم إنشاؤها لحل مشاكل في مجال معين. على عكس لغة البرمجة العامة، تضحي DSL بالعمومية من أجل التعبيرية في مجال ضيق. في نظام Rust البيئي، يوجد نهجان رئيسيان:
\n\n- \n
- DSL داخلي (مدمج) — يُبنى فوق بناء جملة Rust باستخدام وحدات الماكرو وسلاسل الدوال. مثال: مكتبة
clapلتحليل وسائط سطر الأوامر. \n - DSL خارجي — بناء جملة منفصل يتم تحليله في وقت التشغيل. مثال:
regexأوsqlx. \n
تطوير DSL بلغة Rust مهم بشكل خاص لـ:
\n- \n
- ملفات الإعدادات (YAML/TOML كـ DSL خارجي) \n
- محركات الألعاب (وصف سلوك الشخصيات غير القابلة للعب) \n
- أطر الاختبار (بناء جملة خاص للسيناريوهات) \n
- الموازنة بين الأمان والمرونة \n
إنشاء DSL داخلي بلغة Rust: وحدات الماكرو والسلاسل
\n\nلنبدأ بشيء بسيط — DSL داخلي لوصف طلبات HTTP. بدلاً من كتابة:
\n\nlet mut req = Request::new(Method::GET);\nreq.set_url("https://api.example.com/data");\nreq.add_header("Authorization", "Bearer token");\nreq.add_header("Content-Type", "application/json");\n\n\nنريد الحصول على بناء جملة موجز:
\n\nlet req = request! {\n GET "https://api.example.com/data"\n headers {\n "Authorization": "Bearer token",\n "Content-Type": "application/json"\n }\n};\n\n\nسنقوم بتنفيذ ذلك باستخدام وحدات الماكرو في Rust — أقوى أداة لإنشاء DSL داخلي:
\n\nmacro_rules! request {\n ($method:ident $url:expr) => {\n Request::new(stringify!($method).parse().unwrap(), $url)\n };\n ($method:ident $url:expr, headers { $($key:expr => $value:expr),* $(,)? }) => {\n {\n let mut req = Request::new(stringify!($method).parse().unwrap(), $url);\n $(\n req.add_header($key, $value);\n )*\n req\n }\n };\n}\n\n\nهذا النهج هو مثال كلاسيكي على اللغة الموجهة للمجال في Rust: نحن لا ننشئ بناء جملة جديداً، بل نعيد تعريف الموجود لجعل الكود أكثر قابلية للقراءة. لاحظ: وحدة الماكرو تولد كود Rust عادي في مرحلة التجميع، لذلك يتم اكتشاف أخطاء الأنواع قبل التشغيل.
\n\nDSL داخلي متقدم: نمط البناء (Builder pattern)
\n\nوحدات الماكرو ليست الطريقة الوحيدة. غالباً ما يُستخدم نمط Builder مع سلاسل الدوال لإنشاء DSL في Rust. لنأخذ مثالاً على DSL لاستعلامات SQL:
\n\nlet query = Query::select()\n .from("users")\n .where_clause("age > ?")\n .bind(18)\n .order_by("name")\n .build();\n\n\nتنفيذ مثل هذا DSL بسيط وأنيق:
\n\nstruct QueryBuilder {\n select: Vec<String>,\n from: String,\n condition: Option<String>,\n order: Option<String>,\n}\n