إنشاء HashMap في Rust: من الألم إلى الجمال في عام 2025
\n\nإذا كنت قد كتبت بلغة Rust من قبل وتساءلت "How do I create a HashMap literal?" — فأنت لست وحدك. هذا السؤال على Stack Overflow حصد آلاف المشاهدات، وليس بدون سبب. في لغات مثل Python أو JavaScript، إنشاء قاموس هو بضع أقواس معقوفة فقط. أما في Rust فالأمر أكثر تعقيدًا بعض الشيء… ولكن فقط للوهلة الأولى.
\n\nفي عام 2025، قطعت بيئة Rust شوطًا طويلاً. ظهرت ماكروات جديدة، وتحسن العمل مع الثوابت، وأصبح المترجم أكثر ذكاءً. في هذه المقالة، سنستعرض جميع طرق إنشاء HashMap، من الطرق الكلاسيكية إلى الأكثر حداثة. ستتعلم كيف تكتب كودًا ليس فقط عمليًا، بل جميلًا وفعالًا أيضًا.
\n\nهيا بنا! 🦀
\n\nلماذا لا يوجد قيمة حرفية مدمجة لـ HashMap في Rust؟
\n\nهذا هو السؤال الأكثر شيوعًا من المبتدئين. الإجابة بسيطة: Rust ليست Python. لا يوجد "سحر" في Rust على مستوى بناء الجملة للمجموعات، لأن اللغة تسعى إلى قابلية التوقع والتحكم في الذاكرة.
\n\nتخيل أنك تكتب:
\n\nlet map = {"key1": "value1", "key2": "value2"};\n\nيبدو منطقيًا؟ نعم. ولكن ماذا لو كانت المفاتيح أو القيم ليست نصوصًا، بل هياكل معقدة؟ كيف سيعرف المترجم أي مُخصص ذاكرة (allocator) يستخدم؟ وماذا عن التجزئة (hashing)؟ Rust يترك هذه القرارات للمطور.
\n\nلهذا السبب، لإنشاء HashMap في المكتبة القياسية، يتم استخدام HashMap::new() والطرق .insert(). ولكن، اعترف بذلك، كتابة 10 أسطر من الكود لإنشاء قاموس من ثلاثة أزواج هو أمر مؤلم. لحسن الحظ، هناك حلول.
الطريقة رقم 1: الكلاسيكية — HashMap::new() + insert()
\n\nلنبدأ بالأساسيات. هذه هي الطريقة الأكثر وضوحًا وشفافية. تقوم بإنشاء HashMap فارغة ثم تملؤها:
\n\nuse std::collections::HashMap;\n\nfn main() {\n let mut scores = HashMap::new();\n scores.insert("Alice".to_string(), 42);\n scores.insert("Bob".to_string(), 27);\n scores.insert("Charlie".to_string(), 35);\n\n println!("{:?}", scores);\n}\n\nالإيجابيات:
\n- \n
- تحكم كامل في العملية. \n
- يمكن إضافة عناصر بناءً على شرط. \n
- لا يتطلب تبعيات إضافية. \n
السلبيات:
\n- \n
- كثير الكلام (verbose). \n
- يتطلب
mut. \n - غير مناسب للثوابت. \n
هذه الطريقة جيدة للتعبئة الديناميكية، ولكن للبيانات الثابتة فهي مبالغ فيها.
\n\nالطريقة رقم 2: from() — عندما تكون البيانات جاهزة
\n\nإذا كان لديك متجه (vector) من الأزواج (tuples)، يمكنك استخدام HashMap::from():
use std::collections::HashMap;\n\nfn main() {\n let data = vec![\n ("Alice", 42),\n ("Bob", 27),\n ("Charlie", 35),\n ];\n\n let scores: HashMap<&str, i32> = HashMap::from(data);\n println!("{:?}", scores);\n}\n\nهذا أقرب إلى "القيمة الحرفية". يبدو أنظف، لكنه لا يزال يتطلب متجهًا وسيطًا. يعمل بسرعة، ولكن لمجموعات البيانات الكبيرة قد يكون غير فعال بسبب التخصيص المزدوج للذاكرة.
\n\nالطريقة رقم 3: collect() — النهج الوظيفي
\n\nسيعجب عشاق المُكررات (iterators) بهذه الطريقة:
\n\nuse std::collections::HashMap;\n\nfn main() {\n let scores: HashMap<&str, i32> = [\n ("Alice", 42),\n ("Bob", 27),\n ("Charlie", 35),\n ].iter().cloned().collect();\n\n println!("{:?}", scores);\n}\n\nجميل؟ نعم. قابل للقراءة؟ بصعوبة. لكنها واحدة من أكثر الطرق اصطلاحية (idiomatic) في Rust. .iter().cloned().collect() هو نمط قياسي لتحويل مصفوفة إلى مجموعة.
بالمناسبة، إذا كانت مفاتيحك وقيمك تطبق الخاصية Copy، يمكنك استخدام .copied() بدلاً من .cloned() — وهذا أسرع قليلاً.
الطريقة رقم 4: ماكرو hashmap! — المنقذ من عام 2025
\n\nوهنا