مستقبل القيمة مقابل مستقبل المؤشر في Go: أيهما تختار؟

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

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

ابدأ الدورة

مستقبل القيمة مقابل مستقبل المؤشر: مبارزة لا تهدأ

\n\n

إذا كنت قد كتبت بلغة Go ولو مرة واحدة، فمن المحتمل أنك واجهت هذا السؤال. تفتح Stack Overflow - فتجد آلاف المشاهدات، ومئات التعليقات، وكل مبتدئ ثانٍ (وليس فقط المبتدئين) يحير عقله: "ماذا علي أن أختار؟ مستقبل القيمة أم مستقبل المؤشر؟"

\n\n

ملخص: لا توجد إجابة صحيحة لجميع الحالات. ولكن هناك قواعد واضحة ستساعدك على عدم الخطأ في 95% من الحالات. واليوم سنشرحها مرة واحدة وإلى الأبد.

\n\n

في هذه المقالة، لن أترجم الوثائق الجافة فقط. سنمر عبر حالات واقعية، وننظر إلى الأداء، وقابلية التغيير، والواجهات، وحتى كيف ينظر مجتمع Go إلى الأساليب المختلفة. هيا بنا!

\n\n

ما هو المستقبل (receiver) في Go؟

\n\n

لأولئك الذين يتعرفون على اللغة لأول مرة: المستقبل هو معلمة تربط الدالة بنوع. هكذا يتم تنفيذ الدوال في Go.

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

1. الدالة لا تغير المستقبل

\n\n

هذه هي القاعدة الذهبية. إذا كانت الدالة تقرأ البيانات فقط ولا تغير شيئاً - استخدم مستقبل القيمة. هذا آمن، يمكن التنبؤ به، وواضح.

\n\n
type Rectangle struct {\n    Width, Height float64\n}\n\nfunc (r Rectangle) Area() float64 {\n    return r.Width * r.Height\n}\n
\n\n

لا أحد يتوقع أن حساب المساحة سيغير المستطيل نفسه. مستقبل القيمة هنا هو الخيار المثالي.

\n\n

2. الهياكل الصغيرة

\n\n

إذا كان هيكلك يتكون من بضع حقول (مثل Rectangle أعلاه)، فإن النسخ لا يكلف شيئاً تقريباً. بل قد يكون أسرع من العمل مع المؤشر، لأن البيانات تكون على المكدس وليس في الكومة.

\n\n

3. الأمان وعدم قابلية التغيير

\n\n

أحياناً تريد عن قصد ضمان أن الدالة لن تغير البيانات الأصلية. مستقبل القيمة يمنحك هذا الضمان على مستوى المترجم.

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

1. الدالة تغير المستقبل

\n\n

هذه هي الحالة الأكثر وضوحاً. إذا كنت تكتب SetName، UpdateStatus أو Increment - فأنت بحاجة إلى مؤشر.

\n\n
type Counter struct {\n    Value int\n}\n\nfunc (c *Counter) Increment() {\n    c.Value++\n}\n
\n\n

بدون المؤشر، ستقوم بزيادة نسخة، وسيبقى الأصل دون تغيير. خطأ كلاسيكي للمبتدئين.

\n\n

2. الهياكل الكبيرة

\n\n

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

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

3. عندما يحتوي الهيكل على كائن مزامنة (mutex) أو

المدونات

توصيات الكتب