Saat Go 1.18 memperkenalkan Generics pada 2022, membawa parameter tipe generik ke fungsi dan struct, tetapi meninggalkan metode. Dengan rilis Go 1.27, keterbatasan ini telah dihapus. Metode sekarang dapat mendefinisikan parameter tipe mereka sendiri tanpa menambahkannya ke struct penerima.
Mengapa perubahan ini diperkenalkan? Jika Anda ingin membuat simpul grafis yang menyimpan nilai generik, Anda mungkin mengimplementasikannya seperti ini:
type Node[T any] struct {
value T
}
Bayangkan menambahkan metode Map yang mengubah simpul tipe T menjadi simpul tipe lain U. Sebelum Go 1.27, Anda dipaksa untuk menambahkan U langsung ke struct Node itu sendiri:
type Node[T any, U any] struct {
value T
}
func (n *Node[T, U]) Map() Node[T, U] {
// ...
}
Menambahkan U ke struct sendiri adalah desain yang buruk karena U adalah parameter tipe yang spesifik untuk Map. Meskipun metode lain tidak akan menggunakan U, mereka masih harus mempertahankannya dalam deklarasi penerima mereka. Solusi satu-satunya adalah menerapkan Map sebagai fungsi tingkat paket, bukan metode, karena fungsi dapat mendefinisikan parameter tipe mereka sendiri. Namun, metode tidak bisa, yang menyebabkan desain API kode yang tidak nyaman dan tidak idiomatik. Mulai dari Go 1.27, U dapat didefinisikan secara eksklusif pada metode Map:
func (n *Node[T]) Map[U any]() Node[U] {
// ...
}
Mengapa ini memakan waktu begitu lama? Jawabannya terletak pada bagaimana generics diimplementasikan dalam Go. Kompiler menangani generics terutama menggunakan monomorfisasi, yang berarti ia akan membuat salinan dari struct atau fungsi generik untuk setiap tipe spesifik yang digunakan. Karena pada suatu titik, konsep abstrak dari generics perlu diterjemahkan ke kode mesin yang langsung. Namun, sistem antarmuka Go bekerja pada waktu eksekusi. Tipe spesifik dari nilai yang diteruskan ke parameter antarmuka diselesaikan saat program berjalan. Penempatan dinamis ini bertentangan dengan generics yang diselesaikan pada waktu kompilasi. Pertimbangkan apa yang akan terjadi jika kita ingin mendeklarasikan metode Map kita dalam sebuah antarmuka:
type Mapper interface {
Map T any, U any U
}
Untuk membuat ini berfungsi, runtime perlu compiler Just-In-Time untuk menghasilkan kode mesin untuk tipe spesifik secara dinamis, atau perlu membuat salinan tersebut untuk setiap tipe yang mungkin dihindari, yang akan menghasilkan biner yang sangat besar. Akhirnya, tim Go memutuskan untuk memisahkan metode generik dari antarmuka. Go 1.27 memungkinkan metode generik pada tipe konkret, tetapi tidak pada antarmuka. Perbedaan ini adalah mengapa contoh di atas tidak akan dikompilasi dan mengapa diskusi di sekitarnya memakan waktu.
Sumber: Hacker News · Diringkas oleh HeadlinesBriefing