Introducción: ¿por qué Go no dice «enum»?
\n\nSi vienes a Go desde Python, Java o C#, seguramente has experimentado un pequeño choque cultural: en Go no existe la palabra clave enum. No hay enumeraciones integradas con numeración automática, ni métodos .name() o .valueOf(). Solo tú, el compilador y… constantes.
La pregunta «What is an idiomatic way of representing enums in Go?» es una de las más populares en Stack Overflow en la sección de Golang (miles de visitas). Y no es casualidad. Todo principiante tarde o temprano se topa con un muro: «¿Cómo hago un enum de manera que sea propio de Go?».
\n\nEn este artículo no solo daremos la respuesta. Nos adentraremos bajo el capó: cómo Go maneja las constantes, qué es iota y por qué el compilador no considera dos valores int diferentes como tipos distintos. Y al final, aprenderás cómo hacer un enum seguro, práctico y elegante.
El problema: ¿por qué las constantes simples son malas?
\n\nLo primero que viene a la mente es declarar varias constantes:
\n\nconst (\n StatusActive = 1\n StatusInactive = 2\n StatusPending = 3\n)\n\nParece que funciona. Pero hay un detalle: cualquier función que acepte int aceptará el valor 999 o -42. El compilador no dirá ni una palabra. Esto no es una enumeración, son solo números con nombre.
Un verdadero enum debe:
\n- \n
- Limitar el conjunto de valores permitidos; \n
- Ser seguro en cuanto a tipos (no se puede pasar un
intdonde se espera un estado); \n - Ser legible y fácilmente extensible. \n
Go resuelve esta tarea de forma idiomática: mediante tipos personalizados e iota.
La forma idiomática: tipo + iota
\n\nAsí es como se ve la respuesta canónica de ese mismo Stack Overflow:
\n\ntype Status int\n\nconst (\n StatusActive Status = iota // 0\n StatusInactive // 1\n StatusPending // 2\n StatusDeleted // 3\n)\n\n¿Qué ocurrió aquí?
\n- \n
type Status int— creamos un nuevo tipo con nombre. Sí, bajo el capó esint, pero para el compiladorStatuseintson cosas diferentes. \n iotaes un generador de enteros secuenciales que solo funciona dentro de un bloqueconst. Cada nueva línea incrementa el valor en 1. \n
Ahora, una función que acepte Status no se compilará si intentas pasarle un int normal:
func Process(s Status) {\n // ...\n}\n\nfunc main() {\n Process(StatusActive) // OK\n Process(1) // ¡Error de compilación! Cannot use 1 (type int) as type Status\n}\n\nEsto es la seguridad de tipos: la primera y principal razón para usar tipos personalizados para los enums.
\n\n¿Por qué iota y no numeración manual?
\n\nImagina que agregas un nuevo estado en medio de la lista. Con numeración manual, tendrías que reescribir todos los valores siguientes. iota lo hace automáticamente. No solo es conveniente, es idiomático.
Bajo el capó: cómo ve Go el enum
\n\nAnalicemos qué sucede realmente en la etapa de compilación.
\n\n1. iota no es runtime
\n\niota no existe durante la ejecución del programa. Es una herramienta de preprocesamiento (más precisamente, parte del compilador de paquetes). En la etapa de compilación, todos los iota se reemplazan por números concretos. En el binario resultante, solo verás 0, 1, 2.
Puedes comprobarlo con go tool compile o simplemente mirando el código ensamblador:
// Código fuente\nconst (\n A