HeadlinesBriefing HeadlinesBriefing.com

Métodos Genéricos em Go 1.27

Hacker News •
×

Quando Go 1.18 introduziu Genéricos em 2022, trouxe parâmetros de tipo genéricos para funções e structs, mas deixou de lado métodos. Com o lançamento de Go 1.27, essa limitação foi removida. Os métodos agora podem definir seus próprios parâmetros de tipo sem adicioná-los à struct receptora.

Por que essa mudança foi introduzida? Se você quiser criar um nó de gráfico que contenha um valor genérico, você pode implementá-lo assim:

type Node[T any] struct {

value T

}

Imagine adicionar um método Map que transforma um nó de tipo T em um nó de outro tipo U. Antes de Go 1.27, você era forçado a adicionar U diretamente à própria struct Node:

type Node[T any, U any] struct {

value T

}

func (n *Node[T, U]) Map() Node[T, U] {

// ...

}

Adicionar U à struct é um mau design porque U é um parâmetro de tipo específico do Map. Mesmo que outros métodos não usariam U, ainda precisavam mantê-lo em suas declarações de receptor. A única solução alternativa era implementar Map como uma função no nível do pacote em vez de um método, já que funções podiam definir seus próprios parâmetros de tipo. Mas métodos não podiam, levando a designs de API de código desajeitados e não idiomáticos. A partir de Go 1.27, U pode ser definido exclusivamente no método Map:

func (n *Node[T]) Map[U any]() Node[U] {

// ...

}

Por que levou tanto tempo? A resposta está em como os genéricos são implementados em Go. O compilador lida com os genéricos principalmente usando monomorfização, o que significa que ele criará uma cópia de structs ou funções genéricas para cada tipo específico com o qual são usados. Porque em algum momento, o conceito abstrato de genéricos precisa ser traduzido para código de máquina direto. No entanto, o sistema de interfaces de Go funciona em tempo de execução. O tipo específico de um valor passado para um parâmetro de interface é resolvido enquanto o programa está em execução. Esta distribuição dinâmica entra em conflito com os genéricos que são resolvidos em tempo de compilação. Considere o que aconteceria se quiséssemos declarar nosso método Map em uma interface:

type Mapper interface {

Map T any, U any U

}

Para que isso funcione, o tempo de execução precisaria de um compilador Just-In-Time para gerar código de máquina para tipos específicos sobre a marcha, ou precisaria criar essas cópias para cada tipo possível antecipadamente, resultando em um binário massivamente inchado. Finalmente, a equipe do Go decidiu separar os métodos genéricos das interfaces. Go 1.27 permite métodos genéricos em tipos concretos, mas não em interfaces. Essa distinção é por que o exemplo acima não compila e por que as discussões sobre ele levaram tempo.

Fonte: Hacker News · Resumido por HeadlinesBriefing