مقدمة: لحظة الحقيقة لمطور Go
\n\nإذا سبق لك أن كتبت بلغة Go (Golang) واستخدمت الواجهات، فبالتأكيد صادفت رسالة المترجم هذه:
\n\ncannot use x (variable of type *MyStruct) as type MyInterface value in assignment:\n*MyStruct does not implement MyInterface (method GetName has a pointer receiver)\n\nعلى Stack Overflow، يجمع هذا السؤال آلاف المشاهدات. المبتدئون يصابون بالحيرة، والمحترفون يهزون أكتافهم. لكن في الحقيقة، وراء هذا الخطأ يكمن مفهوم أنيق ومهم: الفرق بين value receiver و pointer receiver في لغة Go.
\n\nفي هذه المقالة، سننظر «تحت غطاء» مترجم Go، ونحلل أمثلة حقيقية من الكود، ونفهم كيفية تجنب هذا الفخ. ستتعلم ليس فقط لماذا يحدث الخطأ، بل أيضًا كيف يرتبط بنظام الأنواع والواجهات.
\n\n1. الواجهات في Go: نبذة سريعة
\n\nقبل الغوص في الخطأ، لنتذكر كيف تعمل واجهات Go. على عكس Java أو C++، الواجهة في Go هي مجموعة من الدوال. إذا قام نوع ما بتنفيذ جميع دوال الواجهة، فإنه يلبي تلك الواجهة تلقائيًا (الكتابة البطية).
\n\nمثال لواجهة بسيطة:
\n\ntype Speaker interface {\n Speak() string\n}\n\nالآن لنعرف هيكلًا ودالة واحدة:
\n\ntype Dog struct {\n Name string\n}\n\n// Value receiver — الدالة تعمل مع نسخة من الهيكل\nfunc (d Dog) Speak() string {\n return "هاو! أنا " + d.Name\n}\n\nهنا، Dog ينفذ Speaker. يعمل مع كل من Dog و *Dog:
var s Speaker\ns = Dog{Name: "بوبي"} // ✅ صحيح\ns = &Dog{Name: "ريكس"} // ✅ صحيح (Go يقوم تلقائيًا بإلغاء الإشارة)\n\nحتى الآن كل شيء بسيط. الآن لنغير الدالة إلى pointer receiver:
\n\nfunc (d *Dog) Speak() string {\n return "هاو! أنا " + d.Name\n}\n\nوهنا يبدأ السحر.
\n\n2. Pointer receiver مقابل Value receiver: ما الفرق؟
\n\nفي Go، يمكن أن يكون للدوال نوعان من المستقبلات (receivers):
\n\n- \n
- Value receiver
(t Type)— تستقبل الدالة نسخة من القيمة. \n - Pointer receiver
(t *Type)— تستقبل الدالة مؤشرًا إلى القيمة الأصلية. \n
لماذا هذا مهم؟ لأن مجموعة دوال النوع تعتمد على أي مستقبل يُستخدم.
\n\nجدول الحقيقة:
\n\n- \n
- النوع
Tيحتوي على جميع الدوال ذات value receiver (لـT). \n - النوع
*Tيحتوي على جميع الدوال ذات value receiver + pointer receiver. \n
بمعنى آخر: المؤشر يحصل على مجموعة دوال «إضافية»، بينما القيمة تحصل فقط على تلك المُعلنة مع value receiver.
\n\nهذا ليس خطأ، بل ميزة في تصميم Go. لا يمكن للمترجم أن يأخذ عنوان القيمة تلقائيًا إذا لم تكن قابلة للعنونة (مثل نتيجة دالة أو حرفية).
\n\n3. تحليل الخطأ: «method has a pointer receiver»
\n\nلنعد إلى مثالنا. إذا تم تعريف Speak() مع *Dog، فإن:
var s Speaker\ns = Dog{Name: "بوبي"} // ❌ خطأ\n// cannot use Dog{...} as Speaker...\n// method has a pointer receiver\n\nلماذا؟ لأن Dog (وليس المؤشر) لا يحتوي على دالة Speak() في مجموعته. الدالة تنتمي فقط إلى *Dog.
أما هذا فيعمل:
\n\ns = &Dog{Name: "بوبي"} // ✅ صحيح\n\nالآن لنحلل مثالًا أكثر واقعية من الكود الحقيقي، والذي يظهر غالبًا على Stack Overflow.
\n\nمثال: واجهة وهيكل مع بيانات
\n\npackage main\n\nimport "fmt"\n\ntype User interface {\n GetID() int\n SetName(name string)\n}\n\ntype Admin struct {\n ID int\