Introdução: por que Go não diz "enum"?
\n\nSe você veio para Go vindo de Python, Java ou C#, provavelmente teve um leve choque cultural: em Go não existe a palavra-chave enum. Sem enumerações embutidas com numeração automática, sem métodos .name() ou .valueOf(). Apenas você, o compilador e… constantes.
A pergunta "What is an idiomatic way of representing enums in Go?" é uma das mais populares no Stack Overflow na seção Golang (milhares de visualizações). E isso não é por acaso. Todo iniciante mais cedo ou mais tarde se depara com um obstáculo: "Como faço um enum de forma que seja ao estilo Go?".
\n\nNeste artigo, não vamos apenas dar a resposta. Vamos olhar por baixo dos panos: como Go lida com constantes, o que é iota e por que o compilador não considera dois valores int diferentes como tipos diferentes. E no final, você aprenderá como tornar um enum seguro, conveniente e elegante.
O problema: por que constantes simples são ruins?
\n\nA primeira coisa que vem à mente é declarar algumas constantes:
\n\nconst (\n StatusActive = 1\n StatusInactive = 2\n StatusPending = 3\n)\n\nAté funciona. Mas há um detalhe: qualquer função que aceite int engolirá o valor 999 ou -42. O compilador não dirá uma palavra. Isso não é uma enumeração — são apenas números com nomes.
Um enum de verdade deve:
\n- \n
- Limitar o conjunto de valores permitidos; \n
- Ser type-safe (não se pode passar um
intonde se espera um status); \n - Ser legível e facilmente extensível. \n
Go resolve esse problema de forma idiomática: através de tipos personalizados e iota.
Forma idiomática: tipo + iota
\n\nAqui está como se parece a resposta canônica daquele Stack Overflow:
\n\ntype Status int\n\nconst (\n StatusActive Status = iota // 0\n StatusInactive // 1\n StatusPending // 2\n StatusDeleted // 3\n)\n\nO que aconteceu aqui?
\n- \n
type Status int— criamos um novo tipo nomeado. Sim, por baixo dos panos é umint, mas para o compilador,Statuseintsão coisas diferentes. \n iota— é um gerador de inteiros sequenciais que funciona apenas dentro de um blococonst. Cada nova linha incrementa o valor em 1. \n
Agora, uma função que aceita Status não compilará se você tentar passar um int comum:
func Process(s Status) {\n // ...\n}\n\nfunc main() {\n Process(StatusActive) // OK\n Process(1) // Erro de compilação! Cannot use 1 (type int) as type Status\n}\n\nIsso é type-safety — a primeira e principal razão para usar tipos personalizados para enums.
\n\nPor que iota em vez de numeração manual?
\n\nImagine que você adicionou um novo status no meio da lista. Com numeração manual, você teria que reescrever todos os valores subsequentes. iota faz isso automaticamente. Isso não é apenas conveniente — é idiomático.
Por baixo dos panos: como Go vê o enum
\n\nVamos entender o que realmente acontece na fase de compilação.
\n\n1. iota não é runtime
\n\niota não existe durante a execução do programa. É uma ferramenta de pré-processamento (mais precisamente, parte do compilador de pacotes). Na fase de compilação, todos os iota são substituídos por números específicos. No binário resultante, você verá apenas 0, 1, 2.
Você pode verificar isso com go tool compile ou apenas olhando o código assembly:
// Código fonte\nconst (\n A