الشريحة الفارغة في Go: كيف لا تخطئ من البداية
\n\nكل مطور Go يواجه عاجلاً أم آجلاً السؤال: "كيف أهيئ شريحة فارغة بشكل صحيح؟". تبدو مهمة بسيطة، لكن الأمور ليست بهذا الوضوح. في الأسئلة الشائعة الرسمية لـ Go وفي Stack Overflow، هناك مئات التعليقات وآلاف الأصوات المخصصة لهذا الموضوع. لماذا؟
\n\nلأنه في Go هناك طريقتان مختلفتان جوهرياً لإنشاء شريحة فارغة، والاختيار بينهما يؤثر على الأداء، قراءة الكود، وحتى السلوك عند التسلسل. إذا كنت تكتب بلغة Go وتريد كتابة كود نظيف وفعال بدون مفاجآت - فهذه المقالة لك.
\n\nسنحلل:
\n- \n
- الفرق بين
var s []intوs := make([]int, 0)\n - متى تستخدم nil-slice، ومتى تستخدم empty slice \n
- كيف يؤثر ذلك على JSON، الذاكرة والأداء \n
- ما يقوله خبراء Go ومؤلفو المكتبة القياسية \n
شقيقان توأم: nil slice مقابل empty slice
\n\nفي Go، الشريحة هي هيكل من ثلاثة حقول: مؤشر إلى مصفوفة، الطول والسعة. عندما نتحدث عن شريحة فارغة، نعني شريحة بطول 0. لكن كيف ننشئها - هذا هو المهم.
\n\nالطريقة 1: تعريف var (nil slice)
\n\nvar s []int // s == nil, len(s) == 0, cap(s) == 0\n\nهذه nil-slice. لا تشير إلى أي مصفوفة في الذاكرة. ومع ذلك، يمكنك استدعاء append عليها بأمان - Go ستخصص الذاكرة تلقائياً.
الطريقة 2: تهيئة make (empty slice)
\n\ns := make([]int, 0) // s != nil, len(s) == 0, cap(s) == 0\n\nهذه شريحة فارغة. تشير بالفعل إلى مصفوفة صفرية (zerobase خاص في وقت تشغيل Go).
\n\nللوهلة الأولى، الفرق غير ملحوظ. لكن دعنا نتعمق أكثر.
\n\nالإخراج إلى وحدة التحكم و JSON - الفخ الأول
\n\nجرب تشغيل هذا الكود:
\n\npackage main\n\nimport (\n "encoding/json"\n "fmt"\n)\n\nfunc main() {\n var nilSlice []int\n emptySlice := make([]int, 0)\n\n fmt.Printf("nilSlice: %v, is nil: %v\\n", nilSlice, nilSlice == nil)\n fmt.Printf("emptySlice: %v, is nil: %v\\n", emptySlice, emptySlice == nil)\n\n // JSON-сериализация\n nilJSON, _ := json.Marshal(nilSlice)\n emptyJSON, _ := json.Marshal(emptySlice)\n\n fmt.Printf("nilSlice JSON: %s\\n", nilJSON)\n fmt.Printf("emptySlice JSON: %s\\n", emptyJSON)\n}\n\nالنتيجة ستفاجئك:
\n\nnilSlice: [], is nil: true\nemptySlice: [], is nil: false\nnilSlice JSON: null\nemptySlice JSON: []\n\nها هي المفاجأة الكبرى! تسلسل JSON يتصرف بشكل مختلف. إذا كان API الخاص بك يتوقع [] في الرد، وأنت ترسل nil-slice - سيحصل العميل على null. وهذا يكسر العقود.
العديد من عملاء REST (خاصة في JavaScript) يتحققون بشدة: إذا كان من المتوقع مصفوفة في الرد، ولكن يأتي null - قد يحدث خطأ أو تتعطل المنطق في الواجهة الأمامية.
الأداء: هل هناك فرق؟
\n\nالإجابة المختصرة: نعم، لكن في 99% من الحالات يكون غير مهم.
\n\nعند استخدام make([]int, 0)، يقوم Go بتخصيص الذاكرة فوراً لهيكل الشريحة (24 بايت على نظام 64 بت) ويربطها بـ zerobase. عند استخدام var - يحدث التخصيص فقط عند أول append.
إذا كنت تنشئ شريحة من المحتمل أن تبقى فارغة، فإن nil-slice يوفر تخصيص ذاكرة واحد. إذا كنت تعرف بالتأكيد أنك ستضيف عناصر - فإن make مع تحديد السعة سيعطي أداء أفضل:
// إذا كنت تعرف الحجم التقريبي - حدد capacity\ns := make([]int, 0, 100) // سعة 100، طول 0\nfor i := 0; i < 100; i++ {\n s = append(s, i) // بدون إعادة تخصيص!\n}\n\nلكن بالنسبة لمعظم المهام التطبيقية، الفرق في