۶.۳.۱ تعریف تابع ژنریک (Generic Functions) #
در Go از نسخه ۱.۱۸، میتوانید توابعی بنویسید که بهجای نوع خاص، با نوع پارامتری کار میکنند. پارامترهای نوعی (type parameters) در کروشه []
بعد از نام تابع قرار میگیرند.
نمونه سینتکس: #
func Swap[T any](a, b T) (T, T) {
return b, a
}
T پارامتر نوعی است که میتواند هر نوعی را بپذیرد (در اینجا با constraint
any
).تابع بالا میتواند برای هر نوعی (
int
،string
، ساختار دلخواه و …) فراخوانی شود:a, b := Swap[int](1, 2) // خروجی: 2, 1 x, y := Swap[string]("a", "b") // خروجی: "b", "a"
Type Inference: معمولاً Go نوع را به طور خودکار تشخیص میدهد و نیازی به ذکر
[int]
نیست:s, t := Swap("hello", "world")
با constraint (محدودیت نوع): #
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
در اینجا فقط انواع مرتبشونده (int
, float64
, string
, …) مجاز هستند.
۶.۳.۲ تعریف نوع (Type) ژنریک (Generic Types) #
شما میتوانید struct، slice، map یا هر نوع داده دلخواه را به صورت ژنریک تعریف کنید تا برای انواع مختلف قابل استفاده باشد.
مثال Struct ژنریک: #
type Box[T any] struct {
Value T
}
اکنون میتوانید Box را برای هر نوعی استفاده کنید:
var intBox Box[int] intBox.Value = 42 var strBox Box[string] strBox.Value = "Go!"
مثال عملی – 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
}
idx := len(s.items) - 1
item := s.items[idx]
s.items = s.items[:idx]
return item, true
}
این ساختار را میتوانید برای int
، string
، struct دلخواه و … به کار ببرید.
۶.۳.۳ تعریف اینترفیس ژنریک (Generic Interfaces) #
از Go 1.18+، میتوانید interfaceهایی با پارامتر نوع بنویسید. این امکان بسیار قدرتمند است و اجازه abstraction و تعریف constraintهای پیچیده را میدهد.
نمونه سینتکس: #
type Equaler[T any] interface {
Equal(T) bool
}
- هر نوعی که متد
Equal(T) bool
داشته باشد میتواند پیادهساز این اینترفیس باشد.
مثال – Set ژنریک: #
type Set[E any] interface {
Insert(E)
Delete(E)
Has(E) bool
}
- اکنون میتوانید انواع مختلف Set برای انواع داده متفاوت پیادهسازی کنید.
Constraint ژنریک مبتنی بر متد: #
type Comparer[T any] interface {
Compare(T) int
}
- این الگو به شما اجازه میدهد فقط با انواعی کار کنید که متد
Compare
دارند.
نکته کاربردی: تعریف constraintها و abstractionهای پیشرفته با استفاده از generic interface در نسخههای جدید Go (به ویژه Go 1.21+ و Go 1.24) بسیار ساده و قدرتمند شده است.
۶.۳.۴ استفاده همزمان از چند پارامتر نوع (Multiple Type Parameters) #
گاهی لازم است تابع یا نوعی بنویسید که با چند نوع مختلف سر و کار دارد.
نمونه سینتکس: #
func CopyMap[K comparable, V any](m map[K]V) map[K]V {
newMap := make(map[K]V)
for k, v := range m {
newMap[k] = v
}
return newMap
}
- اینجا K کلیدهایی است که باید قابل مقایسه باشند (
comparable
)، و V میتواند هر نوعی باشد.
مثال Struct با چند پارامتر نوع: #
type Pair[A, B any] struct {
First A
Second B
}
میتوانید انواع مختلف را جفت کنید:
p := Pair[int, string]{First: 1, Second: "Go"}
۶.۳.۵ معرفی و کاربرد Generic Type Alias (جدید در Go 1.24) #
در نسخه ۱.۲۴ Go، امکان تعریف type alias برای انواع ژنریک اضافه شد که نقش بسیار مهمی در خوانایی، بازاستفاده و ماژولار کردن کد دارد.
۶.۳.۵.۱ تفاوت Type Alias معمولی و ژنریک #
تا قبل از Go 1.24، فقط میتوانستید برای انواع غیرژنریک alias تعریف کنید:
type MyInt = int
از Go 1.24، میتوانید برای انواع ژنریک هم alias بسازید:
type Box[T any] struct{ Value T } type IntBox = Box[int] // نوع IntBox معادل Box[int] است
۶.۳.۵.۲ کاربردهای عملی Generic Type Alias #
کاهش تکرار کد و سادهتر شدن refactoring
تعریف alias برای توابع ژنریک، constraintها، و حتی mapها و channelهای ژنریک:
type StringMap[V any] = map[string]V type UserChan = chan User
تمیز و خواناتر شدن APIها و لایه abstraction:
type ConfigMap = map[string]string
۶.۳.۵.۳ بهترین شیوههای نامگذاری و ضدالگوها #
نامهای گویا و معنادار انتخاب کنید؛
❌ بد:
type X = Box[int]
✅ خوب:
type UserIDBox = Box[int]
- از aliasهای تو در تو و بیش از حد بپرهیزید؛
- فقط برای کدهایی که واقعاً تکرار میشوند و نیاز به abstraction دارند استفاده کنید.
۶.۳.۶ حذف Core Type در Go 1.25 و تأثیر آن بر ژنریکها #
در نسخههای اولیه ژنریک Go، مفهومی به نام core type وجود داشت که تعیین میکرد یک type parameter در زمان compile-time به چه underlying typeای ارجاع داده میشود. این مفهوم باعث پیچیدگی و محدودیت در برخی عملیاتها (مانند index، slice و …) شده بود.
از Go 1.25 به بعد:
- core type از استاندارد حذف شد و قواعد هر عملیات به صورت واضحتر و مستقل بیان شد.
- خوانایی و سادگی زبان افزایش یافت و پیامهای خطا شفافتر شدند.
- حالا فقط کافی است برای عملیات مورد نظر constraint درست تعریف شود (مثلاً اگر با map کار میکنید باید
comparable
باشد).