Make أم New؟ السؤال الأبدي لمطور Go
هل تكتب بلغة Go لمدة أسبوع أو عام، ولكنك لا تزال تتوقف أحيانًا للحظة قبل الاختيار بين: make() أو new()؟ هذا طبيعي. سؤال "لماذا أستخدم make() أو new()؟" على Stack Overflow حصد عشرات الآلاف من المشاهدات، ولا يزال ذا صلة حتى في عام 2025. لماذا؟ لأنه وراء البساطة الظاهرية يكمن اختلاف جوهري في فلسفة إدارة الذاكرة في Go.
دعونا نفهم الأمر مرة واحدة وإلى الأبد، حتى لا تضطر للتخمين بعد الآن. سننتقل من الأساسيات إلى الحالات الواقعية، ونسلط الضوء على المزالق، ونقدم قائمة مرجعية للعمل اليومي. هيا بنا!
إجابة مختصرة (للمستعجلين)
إذا كنت بحاجة إلى تخصيص ذاكرة لـ شريحة (slice)، أو خريطة (map)، أو قناة (channel) — استخدم make(). في جميع الحالات الأخرى — استخدم new() أو الصيغة الحرفية &Type{}. ولكن الشيطان، كما هو الحال دائمًا، يكمن في التفاصيل.
ما هو new() وكيف يعمل
الدالة new(T) هي دالة مدمجة في Go، تقوم بتخصيص ذاكرة للنوع T، وتصفيرها (قيمة صفرية)، وتعيد مؤشرًا إلى هذه الذاكرة: *T.
ببساطة: new() يعطيك كائنًا فارغًا ولكنه صالح. تحصل على مؤشر، لكن الكائن نفسه ليس جاهزًا للعمل الكامل بعد، إذا كان يحتاج إلى تهيئة الهياكل الداخلية.
package main
import "fmt"
type Person struct { Name string Age int}
func main() { p := new(Person) fmt.Println(p) // الإخراج: &{ 0} fmt.Println(p.Name == "") // true}هنا، new(Person) خصص ذاكرة للهيكل، وصفّر الحقول (سلسلة فارغة و0) وأعاد المؤشر. كل شيء صحيح.
ما هو make() وقوته الخارقة
make() هي أيضًا دالة مدمجة، لكنها تعمل فقط مع ثلاثة أنواع: الشرائح، الخرائط، والقنوات. لماذا هي فقط؟ لأن هذه الأنواع لها بنية داخلية (رأس) تحتاج إلى تهيئة، وليس مجرد تصفير.
make() لا يعيد مؤشرًا. إنه يعيد قيمة مهيأة من النوع المطلوب (وليس مؤشرًا!).
// شريحة: طول 5، سعة 10s := make([]int, 5, 10)fmt.Println(len(s), cap(s)) // 5 10
// خريطة: جاهزة لإدراج العناصرm := make(map[string]int)m["key"] = 42fmt.Println(m) // map[key:42]
// قناة: مخزنة مؤقتًا بـ 3 رسائلch := make(chan int, 3)ch <- 1fmt.Println(<-ch) // 1لاحظ: إذا حاولت كتابة m := new(map[string]int)، فستحصل على مؤشر لـ خريطة nil. إدراج عنصر في مثل هذه الخريطة سيؤدي إلى ذعر (panic).
لماذا لا يعمل new() مع الشرائح والخرائط والقنوات؟
دعونا ننظر إلى مثال الشريحة. الشريحة في Go هي هيكل من ثلاثة حقول: مؤشر إلى مصفوفة، الطول، والسعة. new([]int) سيخصص ذاكرة لهذا الهيكل ويصفره. سيكون المؤشر nil، والطول والسعة 0. ستحصل على &[] — مؤشر لشريحة nil. هل سينكسر عند التعيين بواسطة الفهرس؟ لا، سيتعطل.
أما make([]int, 3) فيقوم بثلاثة أشياء:
- يخصص تحت الغطاء مصفوفة حقيقية من 3 عناصر (أو أكثر إذا تم تحديد السعة).
- يهيئ رأس الشريحة (المؤشر، الطول، السعة).
- يعيد شريحة جاهزة للاستخدام.
نفس الشيء مع الخريطة: make(map[string]int) ينشئ جدول تجزئة ويخصص ذاكرة للدلاء. new(map[string]int) يعطي فقط خريطة nil.
متى تستخدم new() في عام 2025؟
الممارسة الحديثة في مجتمع Go هي: new() نادرًا ما يُستخدم. في أغلب الأحيان، يفضل المطورون الصيغ الحرفية مع أخذ العنوان: &Type{}. هذا أكثر قابلية للقراءة ويسمح بتعيين الحقول فورًا.