حزمة واحدة، كيانان: حقيقة أم خيال؟
\n\nتخيل أنك كتبت مكتبة رائعة بلغة Rust لمعالجة البيانات الضخمة. إنها سريعة وآمنة وأنيقة. لكنك لا تحتاج فقط إلى API للمطورين الآخرين، بل تحتاج أيضًا إلى أداة CLI جاهزة لاختبار المكتبة أو استخدامها في خطوط الأنابيب. يظهر السؤال الكلاسيكي الذي حصد آلاف المشاهدات على Stack Overflow: «Package with both a library and a binary?» (حزمة تحتوي على مكتبة وتطبيق).
\n\nللمبتدئ في Rust، يبدو هذا وكأنه لغز. للمطور الخبير، إنها مهمة لتحسين هيكل المشروع والأداء. في هذه المقالة، لن نجيب على هذا السؤال فحسب، بل سنركز على تحسين الأداء — الأمران اللذان يجعلان Rust محبوبًا للغاية.
\n\nسنشرح كيفية تنظيم هذه الحزمة بشكل صحيح، وتجنب تكرار الكود، وتسريع التجميع، وجعل تطبيقك سريعًا حقًا. هيا بنا!
\n\nهيكل الحزمة: Cargo.toml هو الحل
\n\nفي Rust، يمكن لحزمة Cargo واحدة أن تحتوي على مكتبة (lib) والعديد من التطبيقات (bin). هذا ليس سحرًا، بل مجرد اتفاقية بسيطة لهيكل المجلدات. دعنا نلقي نظرة على مثال بسيط.
\n\nلننشئ مشروعًا:
\n\ncargo new my_tool --lib\n\nالآن، سنعدل ملف Cargo.toml لنحدد بوضوح أن لدينا مكتبة وتطبيقًا:
[package]\nname = "my_tool"\nversion = "0.1.0"\nedition = "2021"\n\n[lib]\nname = "my_tool_lib"\npath = "src/lib.rs"\n\n[[bin]]\nname = "my_tool"\npath = "src/main.rs"\n\n[dependencies]\n\n\nسيكون هيكل المجلدات كالتالي:
\n\nmy_tool/\n├── Cargo.toml\n├── src/\n│ ├── lib.rs // المكتبة\n│ └── main.rs // التطبيق\n\n\nالأمر بسيط، أليس كذلك؟ لكن الشيطان يكمن في التفاصيل. إذا قمت بنسخ الكود من المكتبة إلى main.rs، فستحصل على تكرار ومشاكل في الأداء. يجب أن نكون أذكى.
\n\nالنهج الصحيح: التطبيق يستخدم المكتبة
\n\nيجب على التطبيق استيراد المكتبة كـ crate خارجي. حتى لو كانا في نفس الحزمة. هذا لا يقلل التكرار فحسب، بل يسمح لمترجم Rust بتحسين الكود بشكل أفضل عبر تحسين الربط عبر الوحدات (LTO).
\n\nمثال src/lib.rs:
pub fn calculate(data: &[u64]) -> u64 {\n data.iter().sum()\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_calculate() {\n assert_eq!(calculate(&[1, 2, 3]), 6);\n }\n}\n\n\nمثال src/main.rs:
use my_tool_lib::calculate;\n\nfn main() {\n let numbers = vec![10, 20, 30];\n println!("المجموع: {}", calculate(&numbers));\n}\n\n\nلاحظ: نحن نستخدم my_tool_lib — هذا هو الاسم الذي حددناه للمكتبة في Cargo.toml. سيقوم المترجم تلقائيًا بربطهما في تطبيق واحد، مع الحفاظ على النمطية.
تحسين الأداء: LTO و inline والاختبارات المعيارية
\n\nالآن بعد أن أصبح الهيكل جاهزًا، لنتحدث عن الشيء الرئيسي — كيفية استخراج أقصى أداء من حزمتك. لأنك إذا كنت تنشئ أداة لمعالجة البيانات، فكل ميكروثانية لها أهميتها.
\n\n1. تفعيل تحسين الربط عبر الوحدات (LTO)
\n\nيسمح LTO للمترجم بتحسين الكود عبر حدود الـ crates. بدون LTO، يتم تحسين الدوال من المكتبة والتطبيق بشكل منفصل. مع LTO، يتم تحسينها ككيان واحد.
\n\nأضف إلى Cargo.toml:
[profile.release]\nlto = true\ncodegen-units = 1\n\n\nسيؤدي هذا إلى زيادة وقت التجميع، لكنه سيجعل التطبيق أسرع بشكل ملحوظ. من المهم التأكيد: LTO هو المعيار لإصدارات الإنتاج في Rust.
\n\n2. استخدام تلميحات inline
\n\nإذا كانت المكتبة تحتوي على دوال حرجة، يمكنك استخدام #[inline] لتوجيه المترجم لدمج الكود مباشرة في موقع الاستدعاء. هذا يقلل من overhead الاستدعاء ويزيد السرعة.
مثال:
\n\n#[inline(always)]\npub fn fast_calculate(data: &[u64]) -> u64 {\n data.iter().sum()\n}\n\n\nلكن احذر: الإفراط في استخدام inline قد يزيد حجم الكود ويبطئ التجميع. استخدمه فقط للدوال الصغيرة والمتكررة.
\n\n3. الاختبارات المعيارية (Benchmarks)
\n\nلقياس الأداء، استخدم إطار criterion أو الاختبارات المعيارية المدمجة في Rust (غير مستقرة). أضف إلى Cargo.toml:
[dev-dependencies]\ncriterion = "0.5"\n\n[[bench]]\nname = "my_benchmark"\nharness = false\n\n\nمثال لملف benches/my_benchmark.rs:
use criterion::{black_box, criterion_group, criterion_main, Criterion};\nuse my_tool_lib::calculate;\n\nfn benchmark_calculate(c: &mut Criterion) {\n let data: Vec = (0..1000).collect();\n c.bench_function("calculate 1000", |b| b.iter(|| calculate(black_box(&data))));\n}\n\ncriterion_group!(benches, benchmark_calculate);\ncriterion_main!(benches);\n \n\nشغّل الاختبار باستخدام cargo bench. هذا سيعطيك تقارير دقيقة عن الأداء ويساعدك على تحديد الاختناقات.
تحسينات إضافية: التجميع الشرطي والميزات
\n\nلجعل حزمتك مرنة، استخدم ميزات Cargo (features). يمكنك إضافة ميزات اختيارية للمكتبة تفعّل وظائف إضافية في التطبيق.
\n\nمثال في Cargo.toml:
[features]\ndefault = []\nadvanced = []\n\n[dependencies]\nserde = { version = "1.0", optional = true }\n\n\nفي الكود:
\n\n#[cfg(feature = "advanced")]\npub fn advanced_calculate(data: &[u64]) -> u64 {\n // كود متقدم\n}\n\n\nهذا يسمح للمستخدمين بتخصيص الحزمة حسب احتياجاتهم دون تحميل غير ضروري.
\n\nالخلاصة: الأداء يبدأ من الهيكل
\n\nإنشاء حزمة Rust تحتوي على مكتبة وتطبيق ليس مجرد تنظيم للملفات. إنه قرار يؤثر على الأداء وقابلية الصيانة. باتباع النهج الصحيح — استخدام المكتبة من التطبيق، تفعيل LTO، استخدام inline بحكمة، وإجراء الاختبارات المعيارية — يمكنك بناء أدوات سريعة وموثوقة.
\n\nفي PythonLib، نؤمن بأن Rust هو مستقبل البرمجة عالية الأداء. جرب هذه التقنيات في مشروعك القادم، وشاركنا نتائجك!