«X does not implement Y»: تحليل خطأ pointer receiver في Go

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

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

ابدأ الدورة

مقدمة: لحظة الحقيقة لمطور Go

\n\n

إذا سبق لك أن كتبت بلغة Go (Golang) واستخدمت الواجهات، فبالتأكيد صادفت رسالة المترجم هذه:

\n\n
cannot 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\n

1. الواجهات في Go: نبذة سريعة

\n\n

قبل الغوص في الخطأ، لنتذكر كيف تعمل واجهات Go. على عكس Java أو C++، الواجهة في Go هي مجموعة من الدوال. إذا قام نوع ما بتنفيذ جميع دوال الواجهة، فإنه يلبي تلك الواجهة تلقائيًا (الكتابة البطية).

\n\n

مثال لواجهة بسيطة:

\n\n
type Speaker interface {\n    Speak() string\n}
\n\n

الآن لنعرف هيكلًا ودالة واحدة:

\n\n
type 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:

\n\n
var s Speaker\ns = Dog{Name: "بوبي"}  // ✅ صحيح\ns = &Dog{Name: "ريكس"}  // ✅ صحيح (Go يقوم تلقائيًا بإلغاء الإشارة)
\n\n

حتى الآن كل شيء بسيط. الآن لنغير الدالة إلى pointer receiver:

\n\n
func (d *Dog) Speak() string {\n    return "هاو! أنا " + d.Name\n}
\n\n

وهنا يبدأ السحر.

\n\n

2. 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\n
    \n
  • النوع T يحتوي على جميع الدوال ذات value receiver (لـ T).
  • \n
  • النوع *T يحتوي على جميع الدوال ذات value receiver + pointer receiver.
  • \n
\n\n

بمعنى آخر: المؤشر يحصل على مجموعة دوال «إضافية»، بينما القيمة تحصل فقط على تلك المُعلنة مع value receiver.

\n\n

هذا ليس خطأ، بل ميزة في تصميم Go. لا يمكن للمترجم أن يأخذ عنوان القيمة تلقائيًا إذا لم تكن قابلة للعنونة (مثل نتيجة دالة أو حرفية).

\n\n

3. تحليل الخطأ: «method has a pointer receiver»

\n\n

لنعد إلى مثالنا. إذا تم تعريف Speak() مع *Dog، فإن:

\n\n
var s Speaker\ns = Dog{Name: "بوبي"}  // ❌ خطأ\n// cannot use Dog{...} as Speaker...\n// method has a pointer receiver
\n\n

لماذا؟ لأن Dog (وليس المؤشر) لا يحتوي على دالة Speak() في مجموعته. الدالة تنتمي فقط إلى *Dog.

\n\n

أما هذا فيعمل:

\n\n
s = &Dog{Name: "بوبي"}  // ✅ صحيح
\n\n

الآن لنحلل مثالًا أكثر واقعية من الكود الحقيقي، والذي يظهر غالبًا على Stack Overflow.

\n\n

مثال: واجهة وهيكل مع بيانات

\n\n
package main\n\nimport "fmt"\n\ntype User interface {\n    GetID() int\n    SetName(name string)\n}\n\ntype Admin struct {\n    ID   int\                

المدونات

توصيات الكتب