
Constantes en Go y conceptos clave
Tabla de Contenido
Note
En el siguiente artículo aprenderás conceptos clave para entender el funcionamiento de las constantes en Go y algunos conceptos de su funcionamiento que te ayudarán a escribir mejor código.
Constantes en Go
una constante es un valor que no cambia en el tiempo y que puede ser referenciado para efectuar operaciones en un programa. Algo particular de Go es que las constantes solo existen en tiempo de compilación. Como sabrás, Go es un lenguaje de programación compilado, por lo que para obtener el código final del programa debemos construirlo. Es aquí donde las constantes son transformadas en valores literales dentro del programa.
Esto implica que durante la ejecución del código, las constantes no son alojadas en un espacio de memoria como ocurre con otros lenguajes como JavaScript, sino que son valores quemados en el binario final. Esto optimiza el rendimiento del programa y evita que se alojen valores en memoria que no son necesarios.
Declaración de constantes
En Go, la forma de declarar constantes es muy simple y similar a otros lenguajes de programación. Tenemos varias maneras de hacerlo. Veamos cada uno de los casos:
Note
Las constantes en Go solo pueden hacer referencia a tipos de dato built-in del lenguaje.
// single constant
const miConstante = 1
// multi-assignment in one line
const a, b, c = 1, 2, 3
// multi-assignment in block
const (
anotherConstant = 1
stringConstant = "Hello, World!"
finalConstant = true
)
Conceptos clave de las Constantes
Kind vs Type
En Go, las constantes pueden ser de dos tipos, conocidos como kind
y type
. La principal diferencia es que en una constante de tipo kind
no se necesita especificar el tipo de dato que se está utilizando, mientras que en una constante de tipo type
sí es necesario especificarlo. Esto lo podemos observar en el siguiente ejemplo:
// kind
const name = "John"
const a = 10
// type
const c int64 = 10
const d float32 = 10
La principal ventaja del uso de kind
vs type
se ve en tiempo de compilación. Este comportamiento se definirá en base al tipo de constante usado:
- kind: El compilador hará un análisis para determinar cuál es el tipo de dato más óptimo para efectuar la operación definida.
- type: El compilador no hará ninguna inferencia o análisis sobre cuál debe ser la operación a realizar, sino que siempre usará el mismo tipo de valor (tipo) en la constante.
Esto es importante porque, recuerda, Go no convierte valores automáticamente. Si vas a usar la constante para operar con otros tipos de datos, deberás efectuar la transformación requerida explícitamente.
Veamos algunos ejemplos con constantes de tipo kind
:
package main
import "fmt"
func main() {
const age = 20
var bigNumber int32 = 1000
agePlusNumber := age + bigNumber
fmt.Printf("El resultado es: %d\n", agePlusNumber)
fmt.Println("El tipo de dato de cada variable es:")
fmt.Printf("age: %T\n", age)
fmt.Printf("bigNumber: %T\n", bigNumber)
fmt.Printf("agePlusNumber: %T\n", agePlusNumber)
}
El resultado del anterior ejemplo es:
[Running] go run "/home/dacacode/dacadev/test/tempCodeRunnerFile.go"
El resultado es: 1020
El tipo de dato de cada variable es:
age: int
bigNumber: int32
agePlusNumber: int32
Como puedes observar, el tipo de age
es int
, mientras que el de bigNumber
y agePlusNumber
es int32
, debido a que el compilador define cuál es el tipo más óptimo.
Ahora veamos el mismo caso usando constantes de tipo type
:
package main
import "fmt"
func main() {
const age int8 = 20
var bigNumber int32 = 1000
agePlusNumber := age + bigNumber
fmt.Printf("El resultado es: %d\n", agePlusNumber)
fmt.Println("El tipo de dato de cada variable es:")
fmt.Printf("age: %T\n", age)
fmt.Printf("bigNumber: %T\n", bigNumber)
fmt.Printf("agePlusNumber: %T\n", agePlusNumber)
}
El código no se podrá compilar e incluso el IDE nos debería mostrar un error ya que Go nos dirá que la operación enter un tipo de dato y otro no esta permitido, el erro deberá lucir como el siguiente:

Pseudo-enumerados
Una funcionalidad que podemos aprovechar de las constantes y tipos definidos por el usuario es la creación de lo que podemos denominar pseudo-enumerados. En Go no disponemos de una estructura de datos de tipo Enum
. sin embargo, podemos emularlas (ojo, no es que sean Enums
en la práctica, pero pueden cumplir funciones similares).
La creación de estos enumerados es sencilla y consiste en definir un tipo de dato personalizado y posteriormente declarar las constantes con este tipo de dato. Para entenderlo mejor, veamos un ejemplo:
type UserPermissions int
const (
Admin UserPermissions = 1
Read UserPermissions = 2
Write UserPermissions = 3
)
func validateUserPermissions(permissions UserPermissions) bool {
// hacer algo con el código
}
En el ejemplo podemos ver que al crear el tipo UserPermissions
y luego constantes de este mismo tipo, estamos usando una constante de tipo type
. De esta manera podríamos controlar y exportar estas constantes del paquete. Sin embargo, no olvides que es una emulación de enumeración.
iota
Quiero hacer énfasis en la declaración de las constantes en el bloque del anterior ejemplo. En ellas podemos observar el uso de un valor numérico secuencial, es decir, que empieza desde 0, 1, 2, … Esto es importante porque Go nos ofrece una forma rápida y sencilla de realizar este mismo proceso de manera automática. Para ello tenemos el concepto de iota
, que nos permite declarar la secuencia de constantes en bloque empezando desde cero.
Veamos algunos ejemplos:
const (
A = iota // 0
B // 1
C // 2
)
func main() {
fmt.Println(A, B, C) // 0 1 2
}
En el ejemplo anterior, podemos observar que estamos declarando las constantes A
, B
, y C
y estas tendrán el valor de 0, 1 y 2 respectivamente. Lo que debemos destacar es el uso de la sintaxis iota
. Si ejecutamos el código, veremos el siguiente resultado:
[Running] go run "/home/dacacode/dacadev/test/main.go"
0 1 2
De igual manera, podemos agregar operadores al valor de iota
, siempre teniendo claro que el valor inicia en cero y es incremental de a 1 unidad. Veamos unos ejemplos:
const (
KB = 1 << (10 * iota) // 1 << (10*0) = 1
MB // 1 << (10*1) = 1024
GB // 1 << (10*2) = 1048576
TB // 1 << (10*3) = 1073741824
)
const (
A = iota // 0
B // 1
C // 2
)
const (
D = iota // 0
E // 1
F // 2
)
También podemos saltarnos los valores secuenciales de iota
si estos no fueran necesarios empleando el caracter _
para ignorar el valor. Veamos un ejemplo:
const (
_ = iota // Ignora el valor 0
One // 1
Two // 2
Three // 3
_ // Ignora el valor 4
Five // 5
Ten = iota * 2 // 6 * 2 = 12
Eleven // 7 * 2 = 14
)
Con el concepto de iota
ya claro, podemos facilitar la escritura de pseudo-enumeraciones de la siguiente manera:
type UserPermissions int
const (
Admin UserPermissions = iota + 1 // 1
Read UserPermissions // 2
Write UserPermissions // 3
)
func validateUserPermissions(permissions UserPermissions) bool {
// hacer algo con el código
}
Note
El término proviene de la letra griega ι
(iota), que es la novena letra del alfabeto griego. Históricamente, la letra iota se usa en matemáticas y lógica para representar el elemento más pequeño en un conjunto o una secuencia, lo cual es apropiado dada su función en Go.