تعريف تابع
التابع (method) دالة لها مستقبِل (receiver): معامل إضافي يُكتب قبل اسم التابع. يربط المستقبِل التابع بنوع.
يكون اسم المستقبِل عادة حرفًا أو حرفين مأخوذين من النوع (c لـ Circle). لا تستخدم Go الكلمتين this أو self، وأدلة الأسلوب لا تنصح بهذين الاسمين.
لأن لـ Circle تابع String() string، فهو يحقّق fmt.Stringer، وتطبع fmt.Println النتيجة بدل {2}.
مستقبِلات القيمة ومستقبِلات المؤشر
يحصل مستقبِل القيمة (c Circle) على نسخة. ويحصل مستقبِل المؤشر (c *Circle) على العنوان، فتبقى التغييرات على الحقول.
تُترجم IncByValue ولا تفعل شيئًا ظاهرًا، وهذا خطأ كلاسيكي. إذا كان التابع يهدف إلى تعديل مستقبِله، فهو يحتاج مستقبِل مؤشر.
تُظهر الاستدعاءات أيضًا وسيلتي الراحة اللتين تمنحهما Go. تُعاد كتابة c.Inc() على متغيّر له عنوان إلى (&c).Inc()، وتُعاد كتابة p.Value() على مؤشر إلى (*p).Value(). نادرًا ما تكتب & أو * لمجرّد استدعاء تابع.
يحتاج & التلقائي شيئًا له عنوان. عنصر الخريطة والحرفية المركّبة ليسا كذلك:
m := map[string]Counter{"a": {}}
m["a"].Inc() // compile error: cannot call pointer method Inc on Counter
Counter{}.Inc() // same error: a composite literal is not addressable
خزّن مؤشرات في الخريطة (map[string]*Counter) أو انسخ القيمة خارجًا وغيّرها ثم أعد كتابتها.
أي مستقبِل تختار
| استخدم مستقبِل مؤشر عندما | استخدم مستقبِل قيمة عندما |
|---|---|
| يعدّل التابع المستقبِل | يكون النوع صغيرًا ولا تعدّله توابعه أبدًا (time.Time، نقطة) |
| تكون البنية كبيرة (النسخ مكلف) | يكون النوع خريطة أو دالة أو قناة (مرجع أصلًا) |
يحمل النوع sync.Mutex أو ما يشبهه | تريد أن تتصرّف القيمة كنوع أولي آمن للنسخ |
| توابع أخرى على النوع تستخدم مؤشرات أصلًا |
تتبع الأسئلة الشائعة في Go والمكتبة القياسية قاعدة عملية واحدة: كن متّسقًا. إذا احتاج أي تابع مستقبِل مؤشر، فأعطِ كل التوابع مستقبِلات مؤشر، لتكون مجموعة التوابع متوقّعة. يبلغ go vet عن بنية تحمل sync.Mutex تُنسخ عبر مستقبِل قيمة (passes lock by value).
مجموعات التوابع والواجهات
يهمّ هذا التمييز أكثر ما يهمّ مع الواجهات. مجموعة توابع T تحتوي توابع مستقبِل القيمة. ومجموعة توابع *T تحتوي توابع مستقبِل القيمة ومستقبِل المؤشر معًا.
كتابة Square{Side: 3} دون & في تلك الشريحة تفشل في الترجمة:
cannot use Square{…} (value of struct type Square) as Shape value in array or slice literal: Square does not implement Shape (method Area has pointer receiver)
السبب: يمكن للواجهة أن تحمل نسخة من Square لا عنوان لها، فلا تستطيع Go أخذ عنوانها لاستدعاء تابع مؤشر. راجع الواجهات لبقية القصة.
توابع على أنواع ليست بنى
أي نوع مسمّى معرّف في حزمتك يمكن أن تكون له توابع، لا البنى وحدها.
الحدود: يجب أن يكون النوع معرّفًا في حزمة التابع نفسها، ولا يمكن أن يكون نوعه الأساسي مؤشرًا أو واجهة. لا تستطيع إضافة توابع إلى int أو string أو time.Time من حزمتك. التغليف في نوع مسمّى جديد هو طريقة Go. وString() على نوع صحيح هو أيضًا طريقة حصول التعدادات على مخرجات مقروءة؛ راجع التعدادات مع iota.
قيم التوابع وتعابير التوابع
التابع المربوط بمستقبِل محدّد قيمة دالة:
قيمة التابع ذات مستقبِل القيمة تنسخ المستقبِل عند إنشائها، ولهذا ما زالت f تقول Ana. ومع مستقبِل مؤشر كانت ستخزّن المؤشر وترى التغييرات اللاحقة. قيم التوابع مفيدة لدوال رد النداء: http.HandleFunc("/", srv.handleIndex).
أخطاء شائعة
- مستقبِل قيمة لتابع يعدّل. يُترجم، ويغيّر نسخة بصمت.
- خلط أنواع المستقبِلات. يجعل من غير الواضح هل يحقّق
Tأم*Tالواجهة. اختر نوعًا واحدًا لكل نوع. - استدعاء تابع مؤشر على مؤشر nil. مسموح، ويعمل التابع بمستقبِل nil. لا يسبّب panic إلا عندما يلمس حقلًا. بعض الأنواع (مثل قائمة مترابطة آمنة مع nil) تستخدم هذا عمدًا؛ ومعظمها لا تتوقّعه.
الأسئلة الشائعة
ما الفرق بين التابع والدالة في Go؟
للتابع مستقبِل يُكتب بين func والاسم: func (c Circle) Area() float64. تستدعيه على قيمة (c.Area())، وينتمي إلى مجموعة توابع النوع، وبه يحقّق النوع الواجهات. وفيما عدا ذلك يتصرّف كدالة وسيطها الأول هو المستقبِل.
متى أستخدم مستقبِل مؤشر في Go؟
استخدم مستقبِل مؤشر عندما يجب أن يعدّل التابع المستقبِل، أو عندما تكون البنية كبيرة بما يجعل نسخها في كل استدعاء هدرًا، أو عندما يحتوي النوع شيئًا لا يجوز نسخه مثل sync.Mutex. وإذا احتاج أي تابع لنوع ما مستقبِل مؤشر، فالنصيحة المعتادة جعلها كلها مستقبِلات مؤشر للاتساق.
لماذا لا يطبّق نوعي الواجهة عندما يكون للتابع مستقبِل مؤشر؟
التوابع ذات مستقبِل المؤشر تنتمي إلى مجموعة توابع *T لا T. لذا تفشل var s Shape = Square{} برسالة Square does not implement Shape (method Area has pointer receiver). أسند مؤشرًا بدلًا من ذلك: var s Shape = &Square{}.
هل يمكن تعريف توابع على الأنواع المدمجة في Go؟
ليس مباشرة. لا تُعرَّف التوابع إلا على أنواع مسمّاة معرّفة في الحزمة نفسها. عرّف نوعك أولًا، type Celsius float64، ثم أضف إليه التوابع. لا يمكنك إضافة توابع إلى int أو string أو نوع من حزمة أخرى.