الشريحة الفارغة في Go: التهيئة الصحيحة مع أمثلة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

الشريحة الفارغة في 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
\n\n

شقيقان توأم: nil slice مقابل empty slice

\n\n

في Go، الشريحة هي هيكل من ثلاثة حقول: مؤشر إلى مصفوفة، الطول والسعة. عندما نتحدث عن شريحة فارغة، نعني شريحة بطول 0. لكن كيف ننشئها - هذا هو المهم.

\n\n

الطريقة 1: تعريف var (nil slice)

\n\n
var s []int  // s == nil, len(s) == 0, cap(s) == 0
\n\n

هذه nil-slice. لا تشير إلى أي مصفوفة في الذاكرة. ومع ذلك، يمكنك استدعاء append عليها بأمان - Go ستخصص الذاكرة تلقائياً.

\n\n

الطريقة 2: تهيئة make (empty slice)

\n\n
s := make([]int, 0)  // s != nil, len(s) == 0, cap(s) == 0
\n\n

هذه شريحة فارغة. تشير بالفعل إلى مصفوفة صفرية (zerobase خاص في وقت تشغيل Go).

\n\n

للوهلة الأولى، الفرق غير ملحوظ. لكن دعنا نتعمق أكثر.

\n\n

الإخراج إلى وحدة التحكم و JSON - الفخ الأول

\n\n

جرب تشغيل هذا الكود:

\n\n
package 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\n
nilSlice: [], is nil: true\nemptySlice: [], is nil: false\nnilSlice JSON: null\nemptySlice JSON: []
\n\n

ها هي المفاجأة الكبرى! تسلسل JSON يتصرف بشكل مختلف. إذا كان API الخاص بك يتوقع [] في الرد، وأنت ترسل nil-slice - سيحصل العميل على null. وهذا يكسر العقود.

\n\n

العديد من عملاء REST (خاصة في JavaScript) يتحققون بشدة: إذا كان من المتوقع مصفوفة في الرد، ولكن يأتي null - قد يحدث خطأ أو تتعطل المنطق في الواجهة الأمامية.

\n\n

الأداء: هل هناك فرق؟

\n\n

الإجابة المختصرة: نعم، لكن في 99% من الحالات يكون غير مهم.

\n\n

عند استخدام make([]int, 0)، يقوم Go بتخصيص الذاكرة فوراً لهيكل الشريحة (24 بايت على نظام 64 بت) ويربطها بـ zerobase. عند استخدام var - يحدث التخصيص فقط عند أول append.

\n\n

إذا كنت تنشئ شريحة من المحتمل أن تبقى فارغة، فإن nil-slice يوفر تخصيص ذاكرة واحد. إذا كنت تعرف بالتأكيد أنك ستضيف عناصر - فإن make مع تحديد السعة سيعطي أداء أفضل:

\n\n
// إذا كنت تعرف الحجم التقريبي - حدد capacity\ns := make([]int, 0, 100)  // سعة 100، طول 0\nfor i := 0; i < 100; i++ {\n    s = append(s, i)  // بدون إعادة تخصيص!\n}
\n\n

لكن بالنسبة لمعظم المهام التطبيقية، الفرق في

المدونات

توصيات الكتب