مستقبل القيمة مقابل مستقبل المؤشر: مبارزة لا تهدأ
\n\nإذا كنت قد كتبت بلغة Go ولو مرة واحدة، فمن المحتمل أنك واجهت هذا السؤال. تفتح Stack Overflow - فتجد آلاف المشاهدات، ومئات التعليقات، وكل مبتدئ ثانٍ (وليس فقط المبتدئين) يحير عقله: "ماذا علي أن أختار؟ مستقبل القيمة أم مستقبل المؤشر؟"
\n\nملخص: لا توجد إجابة صحيحة لجميع الحالات. ولكن هناك قواعد واضحة ستساعدك على عدم الخطأ في 95% من الحالات. واليوم سنشرحها مرة واحدة وإلى الأبد.
\n\nفي هذه المقالة، لن أترجم الوثائق الجافة فقط. سنمر عبر حالات واقعية، وننظر إلى الأداء، وقابلية التغيير، والواجهات، وحتى كيف ينظر مجتمع Go إلى الأساليب المختلفة. هيا بنا!
\n\nما هو المستقبل (receiver) في Go؟
\n\nلأولئك الذين يتعرفون على اللغة لأول مرة: المستقبل هو معلمة تربط الدالة بنوع. هكذا يتم تنفيذ الدوال في Go.
\n\ntype User struct {\n Name string\n Age int\n}\n\n// مستقبل القيمة\nfunc (u User) Greet() string {\n return "مرحباً، أنا " + u.Name\n}\n\n// مستقبل المؤشر\nfunc (u *User) Birthday() {\n u.Age++\n}\n\n\nفي الحالة الأولى، نعمل مع نسخة من الهيكل. في الحالة الثانية، نعمل مع مؤشر إلى الأصل. قد يبدو الأمر بسيطاً. ولكن من هذا البساطة تنمو قرارات معمارية كاملة.
\n\nمتى تستخدم مستقبل القيمة
\n\nلنبدأ بأبسط وأكثر الخيارات أماناً.
\n\n1. الدالة لا تغير المستقبل
\n\nهذه هي القاعدة الذهبية. إذا كانت الدالة تقرأ البيانات فقط ولا تغير شيئاً - استخدم مستقبل القيمة. هذا آمن، يمكن التنبؤ به، وواضح.
\n\ntype Rectangle struct {\n Width, Height float64\n}\n\nfunc (r Rectangle) Area() float64 {\n return r.Width * r.Height\n}\n\n\nلا أحد يتوقع أن حساب المساحة سيغير المستطيل نفسه. مستقبل القيمة هنا هو الخيار المثالي.
\n\n2. الهياكل الصغيرة
\n\nإذا كان هيكلك يتكون من بضع حقول (مثل Rectangle أعلاه)، فإن النسخ لا يكلف شيئاً تقريباً. بل قد يكون أسرع من العمل مع المؤشر، لأن البيانات تكون على المكدس وليس في الكومة.
\n\n3. الأمان وعدم قابلية التغيير
\n\nأحياناً تريد عن قصد ضمان أن الدالة لن تغير البيانات الأصلية. مستقبل القيمة يمنحك هذا الضمان على مستوى المترجم.
\n\ntype Config struct {\n Timeout int\n}\n\nfunc (c Config) WithTimeout(t int) Config {\n c.Timeout = t\n return c\n}\n\n\nهذا هو نمط البرمجة الوظيفية: الدالة تُرجع نسخة جديدة، دون لمس الأصل. مثالي للإعدادات غير القابلة للتغيير.
\n\nمتى تختار مستقبل المؤشر
\n\nوهنا يبدأ الجزء المثير للاهتمام. مستقبل المؤشر هو أداة قوية، لكن يجب التعامل معه بحذر.
\n\n1. الدالة تغير المستقبل
\n\nهذه هي الحالة الأكثر وضوحاً. إذا كنت تكتب SetName، UpdateStatus أو Increment - فأنت بحاجة إلى مؤشر.
type Counter struct {\n Value int\n}\n\nfunc (c *Counter) Increment() {\n c.Value++\n}\n\n\nبدون المؤشر، ستقوم بزيادة نسخة، وسيبقى الأصل دون تغيير. خطأ كلاسيكي للمبتدئين.
\n\n2. الهياكل الكبيرة
\n\nإذا كان هيكلك يزن حوالي مئة بايت أو أكثر، فإن النسخ في كل استدعاء دالة مكلف. خاصة إذا تم استدعاء الدالة في حلقة.
\n\ntype LargeData struct {\n Buffer [1024]byte\n Meta map[string]interface{}\n}\n\n// نسخ هذا في كل استدعاء مؤلم\nfunc (l *LargeData) Process() {\n // نعمل مع الأصل\n}\n\n\nهنا، مستقبل المؤشر ليس مجرد راحة، بل تحسين للأداء.
\n\n