الأقواس الغريبة قبل اسم الدالة في Go: تحليل شامل
تخيل: تفتح كودًا لشخص آخر بلغة Go، وترى شيئًا يجعل عينك تنتفض:
func (u User) Greet() string { return "مرحبًا، " + u.Name}توقف. ما هذا (u User) قبل اسم الدالة؟ في Python للدوال يُستخدم def greet(self):، وفي Java — public String greet() داخل الكلاس. وهنا — بعض الأقواس، ومعها نوع بداخلها. هراء؟ لا، عبقرية!
هذا التركيب — واحد من أقوى ميزات Go، والتي غالبًا ما تحير المبتدئين. في الواقع، هذا هو المستقبل (receiver) — مفتاح فهم البرمجة كائنية التوجه في Go.
في هذه المقالة سننظر تحت غطاء مترجم Go، ونفهم كيف تعمل هذه الأقواس، ولماذا بدونها سيبدو كودك وكأنه من التسعينيات. مستعد؟ انطلق!
ما هو المستقبل ولماذا هو مطلوب؟
في Go لا توجد كلاسات بالمعنى التقليدي. ولكن هناك هياكل (struct) ودوال. الأقواس قبل اسم الدالة — هذا هو إعلان دالة لنوع معين. تقنيًا يُسمى هذا معامل المستقبل (receiver parameter).
التركيب بسيط:
func (receiver_name ReceiverType) MethodName(params) return_type { // جسم الدالة}هنا receiver_name — هو متغير تصل من خلاله إلى حقول الهيكل (مثل self في Python أو this في Java). و ReceiverType — هو نوعك (عادة هيكل).
مثال من الحياة:
package main
import "fmt"
type User struct { Name string Age int}
// دالة مع مستقبلfunc (u User) SayHello() { fmt.Printf("مرحبًا! أنا %s، عمري %d سنة.", u.Name, u.Age)}
func main() { user := User{Name: "أليس", Age: 25} user.SayHello() // الإخراج: مرحبًا! أنا أليس، عمري 25 سنة.}لاحظ: نستدعي الدالة عبر النقطة، كما في أي لغة OOP. ولكن تحت الغطاء، يقوم Go بحيلة ذكية.
مستقبل القيمة مقابل مستقبل المؤشر: معركة العمالقة
في Go هناك نوعان من المستقبلات: حسب القيمة (value receiver) وحسب المؤشر (pointer receiver). الفرق هائل، وعدم فهم هذا هو سبب شائع للأخطاء.
مستقبل القيمة
عندما تكتب func (u User) ...، يقوم Go بنسخ الهيكل بأكمله. الدالة تعمل مع نسخة من الكائن. التغييرات داخل الدالة لن تؤثر على الأصل.
func (u User) Birthday() { u.Age++ // هذا سيغير النسخة فقط!}
func main() { user := User{Name: "بوب", Age: 30} user.Birthday() fmt.Println(user.Age) // ما زال 30! خطأ؟}هذا الكود لن يتم تجميعه مع خطأ، لكنه منطقيًا غير صحيح. تتوقع أن يزيد العمر، لكنه يبقى كما هو. مرحبًا بك في عالم مستقبل القيمة!
مستقبل المؤشر
أما إذا استخدمت المؤشر: func (u *User) ...، فإن الدالة تستقبل مرجعًا للكائن الأصلي. الآن ستكون التغييرات مرئية للجميع.
func (u *User) Birthday() { u.Age++ // نغير الأصل}
func main() { user := &User{Name: "بوب", Age: 30} // ننشئ مؤشرًا user.Birthday() fmt.Println(user.Age) // 31! يعمل!}قاعدة الإبهام: إذا كانت الدالة تغير حالة الهيكل أو كان الهيكل كبيرًا (لتجنب النسخ)، استخدم مستقبل المؤشر. إذا كانت الدالة تقرأ البيانات فقط — استخدم مستقبل القيمة.
كيف يعمل هذا تحت الغطاء؟
الآن الجزء الأكثر إثارة. عندما يرى مترجم Go:
type Rectangle struct { Width, Height float64}
func (r Rectangle) Area() float64 { return r.Width * r.Height}يحول هذا إلى دالة عادية، حيث يصبح المستقبل الوسيط الأول:
المدونات
توصيات الكتب
اقرأ أيضًا