Constantes en Go y conceptos clave

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:

Imagen guia del contenido de la página

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.

Etiquetas :