۶.۳.۱ تعریف تابع ژنریک (Generic Functions) #
در Go از نسخه ۱.۱۸، میتوانید توابعی بنویسید که بهجای نوع خاص، با نوع پارامتری کار میکنند. پارامترهای نوعی (type parameters) در کروشه []
بعد از نام تابع قرار میگیرند.
نمونه سینتکس: #
T پارامتر نوعی است که میتواند هر نوعی را بپذیرد (در اینجا با constraint
any
).تابع بالا میتواند برای هر نوعی (
int
،string
، ساختار دلخواه و …) فراخوانی شود:Type Inference: معمولاً Go نوع را به طور خودکار تشخیص میدهد و نیازی به ذکر
[int]
نیست:1s, t := Swap("hello", "world")
با constraint (محدودیت نوع): #
در اینجا فقط انواع مرتبشونده (int
, float64
, string
, …) مجاز هستند.
۶.۳.۲ تعریف نوع (Type) ژنریک (Generic Types) #
شما میتوانید struct، slice، map یا هر نوع داده دلخواه را به صورت ژنریک تعریف کنید تا برای انواع مختلف قابل استفاده باشد.
مثال Struct ژنریک: #
اکنون میتوانید Box را برای هر نوعی استفاده کنید:
مثال عملی – Stack ژنریک: #
1type Stack[T any] struct {
2 items []T
3}
4
5func (s *Stack[T]) Push(item T) {
6 s.items = append(s.items, item)
7}
8
9func (s *Stack[T]) Pop() (T, bool) {
10 if len(s.items) == 0 {
11 var zero T
12 return zero, false
13 }
14 idx := len(s.items) - 1
15 item := s.items[idx]
16 s.items = s.items[:idx]
17 return item, true
18}
این ساختار را میتوانید برای int
، string
، struct دلخواه و … به کار ببرید.
۶.۳.۳ تعریف اینترفیس ژنریک (Generic Interfaces) #
از Go 1.18+، میتوانید interfaceهایی با پارامتر نوع بنویسید. این امکان بسیار قدرتمند است و اجازه abstraction و تعریف constraintهای پیچیده را میدهد.
نمونه سینتکس: #
- هر نوعی که متد
Equal(T) bool
داشته باشد میتواند پیادهساز این اینترفیس باشد.
مثال – Set ژنریک: #
- اکنون میتوانید انواع مختلف Set برای انواع داده متفاوت پیادهسازی کنید.
Constraint ژنریک مبتنی بر متد: #
- این الگو به شما اجازه میدهد فقط با انواعی کار کنید که متد
Compare
دارند.
نکته کاربردی: تعریف constraintها و abstractionهای پیشرفته با استفاده از generic interface در نسخههای جدید Go (به ویژه Go 1.21+ و Go 1.24) بسیار ساده و قدرتمند شده است.
۶.۳.۴ استفاده همزمان از چند پارامتر نوع (Multiple Type Parameters) #
گاهی لازم است تابع یا نوعی بنویسید که با چند نوع مختلف سر و کار دارد.
نمونه سینتکس: #
1func CopyMap[K comparable, V any](m map[K]V) map[K]V {
2 newMap := make(map[K]V)
3 for k, v := range m {
4 newMap[k] = v
5 }
6 return newMap
7}
- اینجا K کلیدهایی است که باید قابل مقایسه باشند (
comparable
)، و V میتواند هر نوعی باشد.
مثال Struct با چند پارامتر نوع: #
میتوانید انواع مختلف را جفت کنید:
1p := Pair[int, string]{First: 1, Second: "Go"}
۶.۳.۵ معرفی و کاربرد Generic Type Alias (جدید در Go 1.24) #
در نسخه ۱.۲۴ Go، امکان تعریف type alias برای انواع ژنریک اضافه شد که نقش بسیار مهمی در خوانایی، بازاستفاده و ماژولار کردن کد دارد.
۶.۳.۵.۱ تفاوت Type Alias معمولی و ژنریک #
تا قبل از Go 1.24، فقط میتوانستید برای انواع غیرژنریک alias تعریف کنید:
1type MyInt = int
از Go 1.24، میتوانید برای انواع ژنریک هم alias بسازید:
۶.۳.۵.۲ کاربردهای عملی Generic Type Alias #
کاهش تکرار کد و سادهتر شدن refactoring
تعریف alias برای توابع ژنریک، constraintها، و حتی mapها و channelهای ژنریک:
تمیز و خواناتر شدن APIها و لایه abstraction:
1type ConfigMap = map[string]string
۶.۳.۵.۳ بهترین شیوههای نامگذاری و ضدالگوها #
نامهای گویا و معنادار انتخاب کنید؛
❌ بد:
1 type X = Box[int]
✅ خوب:
1type 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
باشد).