Go: تعميم الأنواع

آخر تحديث: 2026-08-26

أدخلت النسخة 1.18 من Go ميزة «الأنواع العامة» — مما سمح بكتابة الدوال وهياكل البيانات باستخدام معلمات الأنواع التي تعمل مع int وstring والأنواع المخصصة، وبالتالي ألغت تمامًا الحاجة إلى تأكيدات الأنواع interface{}.

عندما تحتاج إلى كتابة ثلاث دوال فرز متطابقة تقريبًا لـ int وstring وfloat64، تتيح لك العناصر العامة كتابة دالة واحدة فقط.

1. ستتعلم



2. قصة حقيقية لمهندس خوارزميات

(1) المشكلة: كتابة دالة فرز لكل نوع

يحتاج تشارلي إلى تنفيذ مكتبة فرز عامة تدعم ثلاثة أنواع: int، و float64، و string:

«لم تكن هناك أنواع عامة في لغة Go قبل الإصدار 1.18. كنت أكتب ثلاث دوال متطابقة — وكان الاختلاف الوحيد بينها هو النوع. وفي كل مرة أضيف فيها نوعًا جديدًا، كان عليّ نسخ الكود ولصقه. وكانت أعباء الصيانة باهظة للغاية.»

GO
// Bad code: no generics, copy-paste for each type
func SortInts(slice []int) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

func SortFloat64s(slice []float64) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

func SortStrings(slice []string) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}
// Every new type is Ctrl+C / Ctrl+V

(2) الحل في Go 1.18: الأنواع العامة

GO
// Good code: one function supports all ordered types
func Sort[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

// Usage: automatic type inference
ints := []int{3, 1, 2}
Sort(ints)

floats := []float64{3.14, 1.41, 2.72}
Sort(floats)

strs := []string{"c", "a", "b"}
Sort(strs)
// No need for three functions!

(3) الأداء: قبل استخدام العناصر العامة مقابل بعد استخدامها

البعد interface{} + تأكيد النوع العناصر العامة
حجم الكود نسخة واحدة لكل نوع نسخة واحدة
أمان الأنواع ❌ خطأ فادح في وقت التشغيل ✅ فحص في وقت الترجمة
الأداء ينطوي على عمليات التعبئة/التفريغ التي تتطلب جهدًا إضافيًّا ✅ بدون جهد إضافي
سهولة القراءة العديد من التأكيدات النوعية ✅ واضح


3. الدوال العامة

▶ مثال: الدوال العامة الأساسية

⚙️ المتطلب المسبق: تشغيل go get golang.org/x/exp/constraints

GO
package main

import (
    "fmt"
    "golang.org/x/exp/constraints"
)

// Generic function: T is the type parameter, any is the constraint (all types)
func Print[T any](value T) {
    fmt.Println(value)
}

// Multiple type parameters
func Pair[A, B any](a A, b B) (A, B) {
    return a, b
}

// Constrained to ordered types
func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

func main() {
    // Explicitly specify type parameters
    Print[int](42)
    Print[string]("hello")

    // Type inference (compiler automatically infers T)
    Print(42)       // T = int
    Print("hello")  // T = string

    fmt.Println(Max(3, 5))           // T = int → 5
    fmt.Println(Max(3.14, 2.72))    // T = float64 → 3.14
    fmt.Println(Max("apple", "banana")) // T = string → "banana"

    a, b := Pair(1, "one")
    fmt.Printf("A=%v (type: %T), B=%v (type: %T)\n", a, a, b, b)
}
▶ جرّب الكود

(2) بناء جملة الدالة العامة

GO
// Syntax: func Name[TypeParameter Constraint](paramList) returnType
func Name[T Constraint](param T) T { ... }

// Type parameter list is enclosed in [] (not angle brackets)
// Constraints can be any / comparable / custom interface
// Return values can use type parameters


4. قيود الأنواع

▶ مثال: القيود المضمنة

GO
package main

import (
    "fmt"
    "golang.org/x/exp/constraints"
)

// any: all types (equivalent to interface{})
func Identity[T any](القيمة T) T {
    return القيمة
}

// comparable: types that support == and !=
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

// constraints.Ordered: types that support < <= > >=
func Min[T constraints.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func main() {
    fmt.Println(Contains([]int{1, 2, 3}, 2))         // true
    fmt.Println(Contains([]string{"a", "b", "c"}, "d")) // false
    fmt.Println(Min(10, 20))                            // 10
}
▶ جرّب الكود

▶ مثال: القيود المخصصة

GO
package main

import "fmt"

// Custom constraint: interface + type set
type Numeric interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
        ~float32 | ~float64
}

// ~int means all types whose underlying type is int (including type MyInt int)
// int only matches int itself

type Price float64

func Sum[T Numeric](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

func main() {
    ints := []int{1, 2, 3, 4, 5}
    fmt.Printf("Sum(ints) = %d\n", Sum(ints)) // 15

    floats := []float64{1.5, 2.5, 3.0}
    fmt.Printf("Sum(floats) = %.1f\n", Sum(floats)) // 7.0

    prices := []Price{10.99, 20.99, 5.00}
    fmt.Printf("Sum(prices) = %.2f\n", Sum(prices)) // 36.98 (underlying type float64)
}
▶ جرّب الكود

(3) مستويات القيود

القيود العمليات المدعومة المصدر
any جميع العمليات (بدون قيود) مدمج
comparable == != مدمج
constraints.Ordered < <= > >= golang.org/x/exp
constraints.Integer جميع أنواع الأعداد الصحيحة golang.org/x/exp
constraints.Float جميع أنواع الأرقام العائمة golang.org/x/exp
مخصص نوع الاتحاد interface { ~int | ~string }
💡 نصيحة: any تعادل interface{}، وcomparable هي قيد مدمج (لا يتطلب استيرادًا). توجد constraints.Ordered وconstraints.Integer وغيرها في حزمة golang.org/x/exp/constraints — وهي حزمة تجريبية لكنها أصبحت معيارًا فعليًّا. بدءًا من الإصدار Go 1.21 وما بعده، تم نقل بعض القيود إلى المكتبة القياسية.



5. هياكل البيانات العامة

▶ مثال: مكدس عام (Generic Stack)

GO 📖 للعرض فقط
package main

import "fmt"

// Stack — generic stack
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) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

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

func (s *Stack[T]) IsEmpty() bool {
    return len(s.items) == 0
}

func (s *Stack[T]) Size() int {
    return len(s.items)
}

func main() {
    // int stack
    intStack := Stack[int]{}
    intStack.Push(1)
    intStack.Push(2)
    intStack.Push(3)

    for !intStack.IsEmpty() {
        if val, ok := intStack.Pop(); ok {
            fmt.Printf("Popped: %d\n", val)
        }
    }

    // string stack
    strStack := Stack[string]{}
    strStack.Push("hello")
    strStack.Push("world")
    fmt.Printf("Peek: %s\n", strStack.Peek()) // world
}
45 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

▶ مثال: مجموعة عامة

GO 📖 للعرض فقط
package main

import "fmt"

// Set — generic collection (comparable القيود)
type Set[T comparable] struct {
    item map[T]struct{}
}

func NewSet[T comparable]() *Set[T] {
    return &Set[T]{item: make(map[T]struct{})}
}

func (s *Set[T]) Add(item T) {
    s.item[item] = struct{}{}
}

func (s *Set[T]) Remove(item T) {
    حذف(s.item, item)
}

func (s *Set[T]) Contains(item T) bool {
    _, ok := s.item[item]
    return ok
}

func (s *Set[T]) Size() int {
    return len(s.item)
}

func (s *Set[T]) Items() []T {
    result := make([]T, 0, len(s.item))
    for item := range s.item {
        result = append(result, item)
    }
    return result
}

// Union — set union (method receivers cannot have additional type parameters)
func Union[T comparable](a, b *Set[T]) *Set[T] {
    result := NewSet[T]()
    for _, item := range a.Items() {
        result.Add(item)
    }
    for _, item := range b.Items() {
        result.Add(item)
    }
    return result
}

// Intersection — set intersection
func Intersection[T comparable](a, b *Set[T]) *Set[T] {
    result := NewSet[T]()
    for _, item := range a.Items() {
        if b.Contains(item) {
            result.Add(item)
        }
    }
    return result
}

func main() {
    set1 := NewSet[int]()
    set1.Add(1)
    set1.Add(2)
    set1.Add(3)

    set2 := NewSet[int]()
    set2.Add(3)
    set2.Add(4)
    set2.Add(5)

    fmt.Println("Set1:", set1.Items())
    fmt.Println("Set2:", set2.Items())
    fmt.Println("Union:", Union(set1, set2).Items())
    fmt.Println("Intersection:", Intersection(set1, set2).Items())
}
61 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)
🔥 خطأ شائع: لا يمكن لطرق البنية العامة (struct) إعلان معلمات نوع إضافية — فلا تتوفر سوى معلمات النوع الخاصة بالبنية نفسها. إذا كانت هناك حاجة إلى معلمات نوع إضافية، فاستخدم دالة (على سبيل المثال، Union[T comparable](a, b *Set[T])). بالإضافة إلى ذلك، لا يمكن استخدام الأنواع العامة مباشرةً في إعلانات const.



6. استدلال الأنواع وتحديد المثيلات

▶ مثال: استدلال النوع

GO
package main

import (
    "fmt"
    "strconv"
)

func Map[T, U any](input []T, fn func(T) U) []U {
    result := make([]U, len(input))
    for i, v := range input {
        result[i] = fn(v)
    }
    return result
}

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

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

func main() {
    // Type inference: T=int, U=string
    nums := []int{1, 2, 3, 4, 5}
    strs := Map(nums, strconv.Itoa)
    fmt.Println(strs) // ["1", "2", "3", "4", "5"]

    // Explicitly specify type parameters (when inference fails)
    explicit := Map[int, string](nums, strconv.Itoa)
    fmt.Println(explicit)

    // ❌ Type parameters can only be used for function params/return values, not variables
    // var list List[int]  ← type instantiation

    // Type instantiation: create a concrete type from a generic
    var intStack Stack[int]
    intStack.Push(10)
}
▶ جرّب الكود
100%
graph TB
    A[Generic function definition<br/>func Max[T Ordered](a, b T) T] --> B{Call Max(3, 5)}
    B --> C[Compiler infers T = int]
    C --> D[Instantiate Max[int]]
    D --> E[int version: func Max(a, b int) int]
    B --> F{Call Max(3.14, 2.72)}
    F --> G[Compiler infers T = float64]
    G --> H[Instantiate Max[float64]]
    H --> I[float64 version: func Max(a, b float64) float64]

(2) الأنواع العامة مقابل interface{}

مقارنة interface{} + تأكيد النوع الأنواع العامة
أمان الأنواع ❌ خطأ فادح في وقت التشغيل ✅ فحص في وقت الترجمة
الأداء التعبئة/التفريغ (الهروب إلى الـheap) ✅ بدون أي عبء إضافي (التوسيع في وقت التحويل البرمجي)
حجم الكود نسخة واحدة لكل نوع ✅ نسخة واحدة من الكود العام
المرونة يمكن تخزين أنواع مختلفة في نفس الشريحة ✅ يتم تحديد النوع في وقت التحويل البرمجي
التعقيد سهل الفهم ⚠️ بناء جملة معقد


7. مثال كامل: مكتبة فرز عامة

▶ مثال: التنفيذ الكامل

GO 📖 للعرض فقط
// generic_sort.go
package main

import (
    "fmt"
    "sort"
    "golang.org/x/exp/constraints"
)

// ---------- Sorting functions ----------

// SortSlice sorts a slice of any ordered type
func SortSlice[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool {
        return slice[i] < slice[j]
    })
}

// ReverseSort sorts in descending order
func ReverseSort[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool {
        return slice[i] > slice[j]
    })
}

// ---------- Search functions ----------

// BinarySearch performs binary search (requires sorted input)
func BinarySearch[T constraints.Ordered](slice []T, target T) (int, bool) {
    low, high := 0, len(slice)-1
    for low <= high {
        mid := low + (high-low)/2
        if slice[mid] == target {
            return mid, true
        } else if slice[mid] < target {
            low = mid + 1
        } else {
            high = mid - 1
        }
    }
    return -1, false
}

// ---------- Aggregation functions ----------

// Filter filters elements
func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

// Reduce aggregates elements
func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

type Person struct {
    Name string
    Age  int
}

func main() {
    // 1. Sort integers
    ints := []int{5, 2, 8, 1, 9, 3}
    SortSlice(ints)
    fmt.Printf("Sorted ints: %v\n", ints)

    // 2. Sort in descending order
    ReverseSort(ints)
    fmt.Printf("Reverse: %v\n", ints)

    // 3. Sort strings
    strs := []string{"banana", "apple", "cherry", "date"}
    SortSlice(strs)
    fmt.Printf("Sorted strings: %v\n", strs)

    // 4. Binary search
    idx, found := BinarySearch(ints, 5)
    fmt.Printf("BinarySearch 5: idx=%d, found=%v\n", idx, found)

    // 5. Filter
    evens := Filter(ints, func(n int) bool { return n%2 == 0 })
    fmt.Printf("Evens: %v\n", evens)

    // 6. Reduce
    sum := Reduce(ints, 0, func(acc, n int) int { return acc + n })
    fmt.Printf("Sum: %d\n", sum)

    // 7. Custom type (Person doesn't implement Ordered, can't sort directly)
    // Need a custom sort function
    people := []Person{
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35},
    }

    // Use a closure for custom sorting
    sort.Slice(people, func(i, j int) bool {
        return people[i].Age < people[j].Age
    })
    fmt.Printf("Sorted by age: %v\n", people)
}
75 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)
💡 نصيحة: sort.Slice في حد ذاتها ليست دالة عامة — فهي تقبل شرائح any ([]any) وتعمل عبر الانعكاس. ومع ذلك، عند استخدامها مع الإغلاقات، فإنها تتكامل جيدًا مع العناصر العامة. يأتي الترتيب العام الحقيقي من slices.Sort (golang.org/x/exp/slices)، وهي ميزة جديدة تم تقديمها في Go 1.21، والتي تم تنفيذها بالكامل باستخدام الأنواع العامة.



❓ أسئلة شائعة

س متى تم إدخال الأنواع العامة؟
ج في إصدار Go 1.18 (الذي صدر في مارس 2022). أمضى فريق Go عشر سنوات في تصميم حل للأنواع العامة، واختار في النهاية نهج «معلمات النوع»، بدلاً من النهج القائم على القوالب في لغة C++ أو النهج القائم على «المحو» في لغة Java. ويتم توسيعها في وقت التحويل البرمجي، دون أي عبء إضافي في وقت التشغيل.
س ما هي قواعد كتابة معلمات النوع؟
ج func Name[T Constraint](param T) T. تُعلن معلمات النوع باستخدام الأقواس المربعة [] (وليس الأقواس الزاوية). ويُسمح باستخدام معلمات متعددة: func Map[T, U any](input []T, fn func(T) U) []U.
س كيف تُكتب القيود؟
ج interface { set of types }. القيود المدمجة: any (أي نوع)، comparable (قابلة للمقارنة). من جهات خارجية: constraints.Ordered (مرتبة). مخصصة: interface { ~int | ~string }.
س ما الفرق بين any وinterface{}؟
ج any هو اسم مستعار لـ interface{} (type any = interface{})، وكلاهما متكافئان تمامًا. أدخلت نسخة Go 1.18 الأنواع العامة (generics) إلى جانب any كاسم مستعار للنوع. يُنصح باستخدام any في قيود الأنواع العامة وinterface{} في الكود العادي.
س كيف يعمل استنتاج الأنواع؟
ج يقوم المُجمِّع تلقائيًّا باستنتاج معلمات الأنواع بناءً على معلمات الدالة. على سبيل المثال، Max(3, 5) → يُستنتج أن T=int. إذا فشل الاستنتاج أو إذا أردت تحديد النوع صراحةً، يمكنك كتابة: Max[int](3, 5). لا يمكن استنتاج معلمات الأنواع من نوع الإرجاع — يجب أن تتضمن معلمة واحدة على الأقل معلمة نوع.
س هل يمكن استخدام الأنواع العامة في الطرق؟
ج لا يمكن أن تحتوي الطرق على معلمات نوع إضافية — يمكن استخدام معلمات نوع البنية فقط. ولذلك، فإن func (s *Stack[T]) Push(item T) صحيحة، لكن func (s *Stack[T]) Convert[U any]() U غير صحيحة. إذا كانت هناك حاجة إلى معلمات نوع إضافية، فاستخدم دالة عادية بدلاً من الطريقة.
س كيف يمكن مقارنة أداء العناصر العامة؟
ج لا توجد أي أعباء إضافية أثناء وقت التشغيل. تُنشئ العناصر العامة في لغة Go تطبيقات محددة لكل تركيبة من معلمات الأنواع في وقت التحويل البرمجي (التوحيد الشكلي). ولذلك، فإن Stack[int] وStack[string] هما نوعان مختلفان تمامًا في وقت التشغيل، دون أي عملية تغليف أو فك تغليف. والتكلفة الوحيدة هي زيادة طفيفة في وقت التحويل البرمجي وحجم ثنائي أكبر.

📖 ملخص


📝 تمارين

  1. أساسي (مستوى الصعوبة ⭐): اكتب دالة عامة Find[T comparable](slice []T, target T) int تُرجع مؤشر target في slice؛ وإذا لم يكن target موجودًا، فقم بإرجاع -1. تحقق من أنها تعمل مع الأنواع الثلاثة: int وstring وfloat64.

  2. متقدم (درجة الصعوبة ⭐⭐): قم بتنفيذ Queue[T any] عام (طابور «أول ما يدخل، أول ما يخرج»). المتطلبات: (1) طرق الإدراج في قائمة الانتظار، والسحب من قائمة الانتظار، والاطلاع على العنصر الأول، والتحقق من الفراغ؛ (2) دعم أي نوع؛ (3) تنفيذ مخزن مؤقت حلقي لتجنب تغيير الحجم المتكرر؛ (4) التحقق من أمان التشغيل المتزامن باستخدام -race.

  3. التحدي (الصعوبة ⭐⭐⭐): تنفيذ ذاكرة تخزين مؤقت آمنة للتزامن عامة Cache[K comparable, V any]. المتطلبات: (1) طرق Get/Set/Delete/Clear؛ (2) حماية RWMutex؛ (3) آلية انتهاء صلاحية TTL؛ (4) دعم استدعاء OnEvicted (يتم استدعاؤه عند حذف مفتاح أو انتهاء صلاحيته)؛ (5) استخدام الأنواع العامة لضمان أن يكون نوع المفتاح قابلاً للمقارنة.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%