تخطَّ إلى المحتوى

🐹 شرح Go

الأنواع العامة (Generics)

الدرس 25 من 31· ⏱ 4 دقائق قراءة· 🗓 آخر تحديث: ٢١ يوليو ٢٠٢٦

المشكلة قبل Generics

بدون Generics، لازم دالة منفصلة لكل نوع حتى لو المنطق متطابق:

func SumInts(nums []int) int {
  var total int
  for _, n := range nums {
    total += n
  }
  return total
}

func SumFloats(nums []float64) float64 {
  var total float64
  for _, n := range nums {
    total += n
  }
  return total
}

نفس الكود، مكرّر لكل نوع. هذا بالضبط ما تحلّه Generics.

دالة عامة بمعامل نوع

معامل النوع يُكتب بين قوسين مربّعين [T ...] قبل معاملات الدالة العادية:

func Sum[T int | float64](nums []T) T {
  var total T
  for _, n := range nums {
    total += n
  }
  return total
}

Sum([]int{1, 2, 3})       // 6
Sum([]float64{1.5, 2.5})  // 4

T هنا معامل نوع (type parameter)، و int | float64 قيد (constraint) يحدّد الأنواع المسموحة. غالبًا لا تحتاج كتابة النوع صراحةً — المترجم يستنتجه من الوسيطة.

القيد المدمج comparable

comparable قيد جاهز يسمح بأي نوع تدعم قيمه == و != — مطلوب مثلًا لمفاتيح الخرائط:

func Contains[T comparable](s []T, target T) bool {
  for _, v := range s {
    if v == target {
      return true
    }
  }
  return false
}

Contains([]string{"أ", "ب", "ج"}, "ب")   // true

تعريف قيد مخصّص

القيد نفسه واجهة (interface) تجمع الأنواع المسموحة باستخدام |:

type Number interface {
  int | int64 | float64
}

func Sum[T Number](nums []T) T {
  var total T
  for _, n := range nums {
    total += n
  }
  return total
}

هذا يفيد لما يتكرّر نفس القيد بأكثر من دالة بدل إعادة كتابة الاتحاد كل مرّة.

أنواع عامة (Generic Types)

الـ structs والأنواع الأخرى تقبل معاملات نوع أيضًا:

type Stack[T any] struct {
  items []T
}

func (s *Stack[T]) Push(item T) {
  s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
  var zero T
  if len(s.items) == 0 {
    return zero, false
  }
  last := s.items[len(s.items)-1]
  s.items = s.items[:len(s.items)-1]
  return last, true
}

var s Stack[int]
s.Push(10)
s.Push(20)
val, ok := s.Pop()   // 20, true

any هنا (اختصار interface{}) يعني «أي نوع بلا قيود» — Stack تعمل مع الأعداد والنصوص وأي نوع آخر بنفس الكود.

💡 استخدم Generics لما تكرّر نفس المنطق لأنواع مختلفة فعلًا — لا تعقّد كودك البسيط بها بلا داعٍ. أغلب كود Go اليومي لا يحتاجها.

الدوال العامة على المستقبِل (Generic Methods) — منذ Go 1.27

كل الأمثلة السابقة كانت دوال عامة (قوس مربّع بعد اسم الدالة) أو أنواع عامة بمُستقبِل عادي — لكن حتى Go 1.26 لم يكن مسموحًا لمُستقبِل method نفسه أن يعلن معامل نوع خاصًّا به منفصلًا عن النوع الذي ينتمي إليه. منذ Go 1.27، صار ذلك ممكنًا: تعريف method يقدر يعلن معاملات نوع خاصّة به، بالضبط كما تفعل دالة عادية بمستوى الحزمة:

type Box struct {
  label string
}

// معامل النوع T خاص بهذا الـ method فقط -- مش بنوع Box نفسه
// يُكتب بين اسم الـ method وقائمة معاملاته، تمامًا كما بدالة عامة عادية
func (b Box) Map[T any](items []T, transform func(T) T) []T {
  result := make([]T, len(items))
  for i, item := range items {
    result[i] = transform(item)
  }
  return result
}

b := Box{label: "أرقام"}
b.Map([]int{1, 2, 3}, func(n int) int { return n * 2 })   // [2 4 6]

الفرق عن نوع عام (مثل Stack[T] بالأعلى): هناك T جزء من تعريف النوع نفسه، فكل استخدام لـ Stack[int] يبقى مرتبطًا بـ int طوال عمر القيمة. هنا T خاص باستدعاء الـ method نفسه فقط — كل استدعاء لـ b.Map يقدر يمرّر نوعًا مختلفًا، وBox نفسه يبقى نوعًا عاديًا غير عام.

⚠️ قيد مهم: methods الواجهات (interfaces) لا يمكن أن تُعلن معاملات نوع خاصّة بها، ولا يمكن لـ generic method (بمعناها هذا) أن تحقّق method مطلوبة بواجهة — القيد موجود لأن التوقيع يختلف حسب T المُمرَّر، فلا يوجد توقيع ثابت واحد تحقّقه الواجهة.

🎯 التالي: حزمة context والإلغاء.

شرح الأنواع العامة (Generics) — Go بالعربي
الأنواع العامة (Generics)Go بالعربي · The Code Fix

📚 لمزيد من التعمّق في Go، راجِع التوثيق الرسمي لـ Go.

هل كان هذا الدرس مفيدًا؟