Cuando Go 1.18 introdujo Genéricos en 2022, trajo parámetros de tipo genéricos a funciones y estructuras, pero dejó fuera métodos. Con el lanzamiento de Go 1.27, esta limitación ha sido eliminada. Los métodos ahora pueden definir sus propios parámetros de tipo sin agregarlos a la estructura receptora.
¿Por qué se introdujo este cambio? Si quieres crear un nodo de gráfico que contenga un valor genérico, podrías implementarlo así:
type Node[T any] struct {
value T
}
Imagina agregar un método Map que transforme un nodo de tipo T en un nodo de otro tipo U. Antes de Go 1.27, estabas obligado a agregar U directamente a la estructura Node:
type Node[T any, U any] struct {
value T
}
func (n *Node[T, U]) Map() Node[T, U] {
// ...
}
Agregar U a la estructura es un mal diseño porque U es un parámetro de tipo específico de Map. Aunque otros métodos no usarían U, aún tenían que mantenerlo en sus declaraciones de receptor. La única solución era implementar Map como una función a nivel de paquete en lugar de un método, ya que las funciones podían definir sus propios parámetros de tipo. Pero los métodos no podían, lo que llevaba a diseños de API de código incómodos y no idiomáticos. A partir de Go 1.27, U puede definirse exclusivamente en el método Map:
func (n *Node[T]) Map[U any]() Node[U] {
// ...
}
¿Por qué tomó tanto tiempo? La respuesta está en cómo se implementan los genéricos en Go. El compilador maneja los genéricos principalmente mediante monomorfización, lo que significa que creará una copia de las estructuras o funciones genéricas para cada tipo específico con el que se usan. Porque en algún momento, el concepto abstracto de genéricos necesita traducirse a código máquina directo. Sin embargo, el sistema de interfaces de Go funciona en tiempo de ejecución. El tipo específico de un valor pasado a un parámetro de interfaz se resuelve mientras el programa se ejecuta. Esta asignación dinámica choca con los genéricos que se resuelven en tiempo de compilación. Considera lo que sucedería si quisiéramos declarar nuestro método Map en una interfaz:
type Mapper interface {
Map T any, U any U
}
Para hacer esto funcionar, el tiempo de ejecución necesitaría o un compilador Just-In-Time para generar código máquina para tipos específicos sobre la marcha, o necesitaría crear dichas copias para cada posible tipo por adelantado, resultando en un binario masivamente inflado. Finalmente, el equipo de Go decidió separar los métodos genéricos de las interfaces. Go 1.27 permite métodos genéricos en tipos concretos, pero no en interfaces. Esta distinción es la razón por la cual el ejemplo anterior no compila y por qué las discusiones alrededor de él tomaron tiempo.
Fuente: Hacker News · Resumido por HeadlinesBriefing