Menu
flag Ar iconالعربيةdown icon

الأنواع العامة Generics في Golang: معاملات النوع والقيود

تتيح الأنواع العامة (Go 1.18 وما بعده) لدالة أو نوع واحد العمل مع أنواع كثيرة مع الحفاظ على أمان الأنواع. تعلّم معاملات النوع، والقيود any وcomparable وcmp.Ordered، والقيود المخصّصة بـ ~، والأنواع العامة، ومتى لا تستخدمها.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

دالة عامة

توضع معاملات النوع بين قوسين مربّعين قبل المعاملات العادية. لكل منها قيد يحدّد الأنواع المسموحة.

يُستنتج T وU من الوسائط، فنادرًا ما تكتب Map[int, string](...). يعمل الاستنتاج من وسائط الدالة؛ ولا يستطيع استنتاج نوع يظهر في النتيجة فقط. في func Zero[T any]() T يجب أن تكتب Zero[int]().

قبل Go 1.18 كان يجب كتابة هذه الدالة مرة لكل نوع، أو جعلها تأخذ []interface{} وتفقد أمان الأنواع.

القيود

القيد واجهة. يحدّد الأنواع الممكن استخدامها، ويخبر المترجم في المقابل بالعمليات المسموحة على قيم ذلك النوع.

القيديسمح بـيتيح لك استخدام
anyكل نوعالإسناد والتمرير والتخزين
comparableالأنواع التي تدعم ==== و!= ومفاتيح الخرائط
cmp.Ordered (Go 1.21)الأعداد الصحيحة والعشرية والنصوص< و<= و> و>=، إضافة إلى ==
واجهة ذات توابعالأنواع التي لها تلك التوابعاستدعاء التوابع
اتحاد أنواع مثل ~int | ~float64تلك الأنواع بالضبطالمعاملات التي تدعمها جميعها

مع any بدل comparable تفشل v == target في الترجمة: invalid operation: v == target (incomparable types in type set). القيد هو ما يجعل المعامل مشروعًا.

هاتان الدالتان المساعدتان موجودتان أصلًا: slices.Index وslices.Max. تغطّي الحزم القياسية slices وmaps وcmp معظم الحاجات العامة اليومية، فتفقّدها قبل كتابة دوالك.

قيود مخصّصة باتحادات الأنواع و~

يمكن للقيد أن يسرد أنواعًا بـ |. المعاملات المسموحة هي التي يدعمها كل نوع مسرود.

العلامة ~ مهمّة. تعني ~int64 "أي نوع نوعه الأساسي int64"، فيُقبل النوع المسمّى Cents. ولو كُتب int64 وحده لفشلت Sum([]Cents{...}) برسالة Cents does not satisfy Number (possibly missing ~ for int64 in Number).

الواجهات التي تحتوي اتحادات أنواع لا تُستخدم إلا كقيود، ولا تُستخدم أبدًا كأنواع متغيّرات عادية. var n Number لا تُترجم.

في الحزمة golang.org/x/exp/constraints قيود جاهزة Integer وFloat وSigned. لكنها خارج المكتبة القياسية، فلا يستطيع المشغّل هنا استيرادها؛ والقيد القياسي هو cmp.Ordered.

الأنواع العامة

يمكن أن تملك البنى والشرائح والخرائط وغيرها معاملات نوع أيضًا. توابع النوع العام تستخدم معاملات المستقبِل.

تفصيلتان في هذا المثال تتكرّران باستمرار:

  • القيمة الصفرية لـ T. var zero T هي طريقة إعادة "لا شيء" من شيفرة عامة. لا يوجد T{} أو nil يعمل مع كل نوع.
  • التجسيد. يجب تجسيد النوع العام قبل استخدامه: Stack[string] لا Stack. وداخل توابعه يُكتب المستقبِل بالشكل Stack[T].

أضافت Go 1.24 الأسماء البديلة العامة للأنواع: type Set[T comparable] = map[T]struct{} تُترجم الآن.

حدود ستصطدم بها

  • لا معاملات نوع في التوابع. func (s *Stack[T]) Map[U any](...) غير مسموحة. استخدم دالة على المستوى الأعلى.
  • لا تخصيص. لا يمكنك كتابة تطبيق منفصل لـ T = string. الحل البديل type switch على any(v) داخل الدالة، وهو عادة إشارة إلى أن الأنواع العامة ليست الأداة المناسبة.
  • لا قيود معاملات بالاسم. لا يمكنك قول "أي نوع له تابع +"؛ بل تسرد الأنواع في اتحاد.
  • الوصول إلى الحقول عبر قيد لا يعمل. حتى لو كان لكل نوع في الاتحاد حقل ID، فإن v.ID غير مسموحة. استخدم تابعًا في القيد بدلًا من ذلك.

متى لا تستخدم الأنواع العامة

تناسب الأنواع العامة أنواع الحاويات (المكدّسات، المجموعات، الذاكرات المؤقّتة، الأشجار) والخوارزميات على الشرائح والخرائط التي تفعل الشيء نفسه لكل نوع عنصر. ولا تناسب كل مكان.

  • نوع محدّد واحد. إذا كانت الدالة لا تُستدعى إلا مع []User، فالنسخة العامة تضيف كلفة قراءة ولا شيء غيرها.
  • سلوك يختلف حسب النوع. هذا ما وُجدت الواجهات من أجله. الدالة التي تأخذ io.Writer أبسط من التي تأخذ [T io.Writer]، وتعمل بالطريقة نفسها.
  • استبدال معاملات الواجهات. func Print[T fmt.Stringer](v T) ليست أفضل من func Print(v fmt.Stringer).

إرشاد فريق Go نفسه: اكتب الشيفرة لنوع محدّد أولًا، ولجأ إلى معاملات النوع عندما تلاحظ أنك تكتب الشيفرة نفسها مرة ثانية ولم يتغيّر إلا الأنواع.

أخطاء شائعة

  • استخدام any حيث يلزم comparable أو cmp.Ordered. يرفض المترجم == أو < على معامل نوع any.
  • نسيان ~. الأنواع التي يعرّفها المستخدم مثل type ID int تفشل مع قيد int عادي.
  • إعادة nil لمعامل نوع. غير مسموح ما لم يحصر القيد T في أنواع شبيهة بالمؤشرات. أعد قيمة صفرية.
  • كتابة دالة مساعدة عامة موجودة أصلًا. تفقّد slices وmaps وcmp أولًا.

الأسئلة الشائعة

هل تملك Go أنواعًا عامة (generics)؟

نعم، منذ Go 1.18 (مارس 2022). يمكن للدوال والأنواع تعريف معاملات نوع بين قوسين مربّعين: func Map[T, U any](s []T, f func(T) U) []U. لكل معامل نوع قيد، وهو واجهة تصف ما يجب أن يدعمه النوع.

ما الفرق بين any وcomparable في generics في Go؟

يسمح any بكل نوع لكنه لا يتيح فعل شيء تقريبًا بالقيم سوى إسنادها وتمريرها وتخزينها. أما comparable فيسمح فقط بالأنواع التي تدعم == و!=، وهذا ما تحتاجه لمفاتيح الخرائط أو فحوص المساواة. ولـ < و> استخدم cmp.Ordered.

ماذا تعني العلامة ~ في قيد في Go؟

~T تعني "أي نوع نوعه الأساسي T". تطابق ~int النوع int وأيضًا type Celsius int. دون العلامة، يطابق int في القيد int نفسه فقط، فتُرفض الأنواع التي يعرّفها المستخدم.

هل يمكن أن تملك توابع Go معاملات نوع؟

لا. يمكن للتابع استخدام معاملات النوع الخاصة بنوع مستقبِله (func (s *Stack[T]) Push(v T))، لكنه لا يستطيع تعريف معاملات جديدة. اكتب دالة عامة على المستوى الأعلى بدلًا من ذلك: func Map[T, U any](s *Stack[T], f func(T) U) *Stack[U].

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن