Go: تعميم الأنواع
آخر تحديث: 2026-08-26
أدخلت النسخة 1.18 من Go ميزة «الأنواع العامة» — مما سمح بكتابة الدوال وهياكل البيانات باستخدام معلمات الأنواع التي تعمل مع
intوstringوالأنواع المخصصة، وبالتالي ألغت تمامًا الحاجة إلى تأكيدات الأنواعinterface{}.
عندما تحتاج إلى كتابة ثلاث دوال فرز متطابقة تقريبًا لـ int وstring وfloat64، تتيح لك العناصر العامة كتابة دالة واحدة فقط.
1. ستتعلم
- بناء جملة الدالة العامة
- هياكل البيانات العامة
- قيود
anyوcomparable constraints: قيود مخصصة- استدلال الأنواع
- مقارنة بين الأدوية الجنيسة و
interface{}
2. قصة حقيقية لمهندس خوارزميات
(1) المشكلة: كتابة دالة فرز لكل نوع
يحتاج تشارلي إلى تنفيذ مكتبة فرز عامة تدعم ثلاثة أنواع: int، و float64، و string:
«لم تكن هناك أنواع عامة في لغة Go قبل الإصدار 1.18. كنت أكتب ثلاث دوال متطابقة — وكان الاختلاف الوحيد بينها هو النوع. وفي كل مرة أضيف فيها نوعًا جديدًا، كان عليّ نسخ الكود ولصقه. وكانت أعباء الصيانة باهظة للغاية.»
// 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: الأنواع العامة
// 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
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) بناء جملة الدالة العامة
// 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. قيود الأنواع
▶ مثال: القيود المضمنة
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
}
▶ مثال: القيود المخصصة
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)
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
}
▶ مثال: مجموعة عامة
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())
}
Union[T comparable](a, b *Set[T])). بالإضافة إلى ذلك، لا يمكن استخدام الأنواع العامة مباشرةً في إعلانات const.
6. استدلال الأنواع وتحديد المثيلات
▶ مثال: استدلال النوع
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)
}
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. مثال كامل: مكتبة فرز عامة
▶ مثال: التنفيذ الكامل
// 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)
}
sort.Slice في حد ذاتها ليست دالة عامة — فهي تقبل شرائح any ([]any) وتعمل عبر الانعكاس. ومع ذلك، عند استخدامها مع الإغلاقات، فإنها تتكامل جيدًا مع العناصر العامة. يأتي الترتيب العام الحقيقي من slices.Sort (golang.org/x/exp/slices)، وهي ميزة جديدة تم تقديمها في Go 1.21، والتي تم تنفيذها بالكامل باستخدام الأنواع العامة.
❓ أسئلة شائعة
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 غير صحيحة. إذا كانت هناك حاجة إلى معلمات نوع إضافية، فاستخدم دالة عادية بدلاً من الطريقة.Stack[int] وStack[string] هما نوعان مختلفان تمامًا في وقت التشغيل، دون أي عملية تغليف أو فك تغليف. والتكلفة الوحيدة هي زيادة طفيفة في وقت التحويل البرمجي وحجم ثنائي أكبر.📖 ملخص
- وظيفة عامة:
func Name[T Constraint](param T) T - قيود النوع:
any/comparable/Ordered/ مخصص - قيد مخصص:
interface { ~int | ~string } - استنتاج النوع: يتم استنتاجه تلقائيًا بواسطة المُجمِّع أو يُحدد صراحةً
- هياكل البيانات العامة:
Stack[T]/Set[T]/List[T] - لا يمكن أن تحتوي الطرق على معلمات نوع إضافية
- لا توجد أي أعباء إضافية أثناء التشغيل (التوسيع يتم في مرحلة التحويل البرمجي)
📝 تمارين
-
أساسي (مستوى الصعوبة ⭐): اكتب دالة عامة
Find[T comparable](slice []T, target T) intتُرجع مؤشرtargetفيslice؛ وإذا لم يكنtargetموجودًا، فقم بإرجاع -1. تحقق من أنها تعمل مع الأنواع الثلاثة:intوstringوfloat64. -
متقدم (درجة الصعوبة ⭐⭐): قم بتنفيذ
Queue[T any]عام (طابور «أول ما يدخل، أول ما يخرج»). المتطلبات: (1) طرق الإدراج في قائمة الانتظار، والسحب من قائمة الانتظار، والاطلاع على العنصر الأول، والتحقق من الفراغ؛ (2) دعم أي نوع؛ (3) تنفيذ مخزن مؤقت حلقي لتجنب تغيير الحجم المتكرر؛ (4) التحقق من أمان التشغيل المتزامن باستخدام-race. -
التحدي (الصعوبة ⭐⭐⭐): تنفيذ ذاكرة تخزين مؤقت آمنة للتزامن عامة
Cache[K comparable, V any]. المتطلبات: (1) طرق Get/Set/Delete/Clear؛ (2) حماية RWMutex؛ (3) آلية انتهاء صلاحية TTL؛ (4) دعم استدعاءOnEvicted(يتم استدعاؤه عند حذف مفتاح أو انتهاء صلاحيته)؛ (5) استخدام الأنواع العامة لضمان أن يكون نوع المفتاح قابلاً للمقارنة.