مقدمة: السؤال الأبدي من Stack Overflow
\n\nإذا سبق لك أن بحثت عن "Declare a constant array in Go"، فمن المؤكد أنك رأيت ذلك الموضوع الشهير على Stack Overflow بآلاف المشاهدات. نعم، أنت لست وحدك في حيرتك. في Go، لا يمكنك ببساطة كتابة const arr = [...]int{1, 2, 3} — سيخبرك المترجم بكلمة "لا" بلطف.
لماذا؟ وماذا نفعل إذا أردنا قائمة بيانات غير قابلة للتغيير؟ في هذا المقال، سنتعمق في طبيعة الثوابت في Go، ونعرف لماذا لا يمكن أن تكون المصفوفات ثوابت، ونجد أفضل الحلول البديلة. مفاجأة: الحل موجود، بل إنه أجمل مما تتخيل.
\n\nلماذا لا يسمح Go بتعريف مصفوفة ثابتة؟
\n\nلفهم ذلك، يجب أن ننظر تحت غطاء اللغة. في Go، الثوابت هي قيم غير قابلة للتغيير تُحسب في مرحلة الترجمة. يجب أن تكون محددة النوع ومعروفة قبل تشغيل البرنامج. أما المصفوفات، فهي أنواع بيانات مركبة تشغل ذاكرة ويمكن تعديلها.
\n\nهذا مثال بسيط لن يتم ترجمته:
\n\npackage main\n\nconst arr = [3]int{1, 2, 3} // خطأ: const initializer [3]int{1, 2, 3} is not a constant\n\nfunc main() {\n fmt.Println(arr)\n}\n\nيقول المترجم: "هذا ليس ثابتًا!" وهو محق. حتى لو تم تعريف المصفوفة كـ const، فإن Go لا يضمن عدم تغيير محتواها عبر المؤشرات أو الشرائح. لذلك قرر مطورو اللغة: لا مصفوفات ثابتة.
لكن لا تتعجل في الإحباط. هناك على الأقل ثلاث طرق لتجاوز هذا القيد، ولكل منها مزاياها.
\n\nالطريقة 1: استخدام متغير مع تعليق "لا تلمس"
\n\nأبسط طريقة وأكثرها سذاجة هي تعريف متغير والأمل ألا يغيره أحد. لكننا مطورون محترفون، أليس كذلك؟
\n\npackage main\n\nimport "fmt"\n\n// arr - مصفوفة ثابتة. من فضلك لا تغيرها.\nvar arr = [3]int{1, 2, 3}\n\nfunc main() {\n fmt.Println(arr)\n // arr[0] = 100 // أزل التعليق وسيحدث الخراب\n}\n\nالعيوب واضحة: هذا ليس ثابتًا حقيقيًا. يمكن لأي شخص تغيير المصفوفة عن طريق التعيين. لكنه مناسب للنماذج الأولية السريعة أو المشاريع الصغيرة.
\n\nالطريقة 2: دالة جالبة (الطريقة الصحيحة)
\n\nيُستخدم هذا الأسلوب في كود الإنتاج. نخفي المصفوفة داخل دالة ونعيد نسخة منها. بهذا لا يمكن لأي شخص تغيير الأصل.
\n\npackage main\n\nimport "fmt"\n\n// getColors تعيد نسخة من مصفوفة الألوان.\n// المصفوفة محمية من التغييرات الخارجية.\nfunc getColors() [3]string {\n return [3]string{"Red", "Green", "Blue"}\n}\n\nfunc main() {\n colors := getColors()\n fmt.Println(colors)\n // colors[0] = "Yellow" // هذا سيغير النسخة، وليس الأصل\n}\n\nلماذا هذا جيد؟ لأن:
\n- \n
- لا يمكن تغيير المصفوفة من الخارج — الدالة تعيد نسخة. \n
- الكود قابل للقراءة والفهم. \n
- سهل الاختبار والصيانة. \n
لكن هناك نقطة: كل استدعاء للدالة ينشئ نسخة جديدة من المصفوفة. إذا كانت المصفوفة كبيرة (مثل 1000 عنصر)، فقد يكون ذلك مكلفًا. في هذه الحالة، استخدم الطريقة التالية.
\n\nالطريقة 3: متغير حزمة مع وصول خاص
\n\nإذا كنت تكتب حزمة وتريد حماية المصفوفة من التغييرات، اجعلها غير قابلة للتصدير (بحرف صغير) ووفر الوصول عبر دالة.
\n\npackage constants\n\n// المصفوفة متاحة فقط داخل الحزمة\nvar defaultPorts = [5]int{80, 443, 8080, 3000, 5000}\n\n// GetDefaultPorts تعيد نسخة من مصفوفة المنافذ.\nfunc GetDefaultPorts() [5]int {\n return defaultPorts\n}\n\nالآن يمكن لمستخدمي حزمتك قراءة البيانات فقط، دون تعديلها. مثالي للمكتبات.
\n\nالطريقة 4: استخدام iota لمجموعات ثابتة
\n\nماذا لو كنت تحتاج إلى الفهارس نفسها بدلاً من القيم؟ على سب