Lección 4 · Primeros pasos
Constantes e iota
Al terminar esta lección vas a poder declarar constantes, valores con nombre que no cambian, y numerar
una lista de opciones, como los días de la semana, con iota. Vas a entender por qué un número escrito
tal cual toma el tipo del otro valor de una cuenta, como viste en la lección anterior. También vas a
saber leer los errores que aparecen cuando intentas cambiar una constante y cuando su valor no cabe en
un tipo.
Declarar una constante con const
En la Papelería Central, cada caja trae siempre 12 cuadernos. Ese número no cambia, así que el programa lo guarda en una constante:
package main
import "fmt"
func main() {
const tienda = "Papelería Central"
const porCaja = 12
cajas := 4
fmt.Println(tienda)
fmt.Println("Cuadernos:", cajas*porCaja)
}Salida:
Papelería Central Cuadernos: 48
const declara una constante: un nombre para un valor que no cambia mientras el programa corre. Se
escribe como una variable con var, pero con la palabra const: const porCaja = 12. Después la usas
como cualquier otro valor: cajas*porCaja da 48.
La diferencia con una variable está en lo que puede cambiar. Los cuadernos por caja son siempre 12, así
que van en una constante. Las cajas cambian de un pedido a otro, así que van en una variable, como
cajas.
El valor de una constante se fija al compilar, antes de que el programa corra. Por eso solo puede ser un
número, un texto, true o false, o una cuenta con otras constantes. Si depende de una variable, como
en const total = cajas * porCaja, el programa no compila, y Go responde
cajas * porCaja (value of type int) is not constant (“no es constante”).
Otra diferencia con las variables: una constante que no usas no es un error. Go exige usar las variables
(declared and not used), pero no las constantes.
Una constante no cambia nunca. Si el precio de un cuaderno sube y el programa intenta darle otro valor
con =, no compila:
package main
import "fmt"
func main() {
const precio = 2.75
fmt.Println("Precio:", precio)
precio = 3.25
fmt.Println("Precio nuevo:", precio)
}Salida:
./main.go:8:2: cannot assign to precio (neither addressable nor a map index expression)
Lee el mensaje por partes:
./main.go:8:2es el lugar: archivomain.go, línea 8, columna 2, donde empiezaprecio.cannot assign to preciosignifica “no se puede asignar aprecio”: no se le puede dar otro valor.(neither addressable nor a map index expression)es la razón, en lenguaje técnico:preciono tiene un lugar en la memoria donde guardar otro valor (eso es addressable), ni es un elemento de un map, una colección que verás en la lección “Maps”. En pocas palabras: a la izquierda de=va una variable, yprecioes una constante.
Si el valor tiene que cambiar, no es una constante: declara precio como variable, con
precio := 2.75, y entonces precio = 3.25 funciona.
Si vienes de JavaScript, Python o Java…
En JavaScript, const también impide asignar otro valor, pero el valor puede calcularse al ejecutar el
programa: const total = cajas * porCaja funciona. En Java, final se comporta igual. En Go, el valor
de una constante se conoce al compilar, y solo puede ser un número, un texto o un bool.
Python no tiene constantes: por costumbre se escriben en mayúsculas (POR_CAJA = 12), pero nada impide
cambiarlas. En Go, una constante se nombra como una variable, porCaja, porque la mayúscula inicial
tiene otro significado: hace que el nombre sea exportado, como viste en la lección Qué es Go.
Constantes sin tipo
Una misma constante puede servir en una cuenta con un int y en otra con un float64. Una caja de 12
cuadernos cuesta 30:
package main
import "fmt"
func main() {
const porCaja = 12
cajas := 3
precioCaja := 30.0
fmt.Println("Cuadernos:", cajas*porCaja)
fmt.Println("Precio por cuaderno:", precioCaja/porCaja)
}Salida:
Cuadernos: 36 Precio por cuaderno: 2.5
cajas es un int y precioCaja, un float64. En la lección anterior, Go no hacía una cuenta entre
un int y un float64. Sin embargo, porCaja funciona en las dos: en cajas*porCaja como un int, y
en precioCaja/porCaja como un float64, que da 2.5.
Es que porCaja es una constante sin tipo (untyped constant): const porCaja = 12 no dice ningún
tipo. Para Go, porCaja es un número entero, pero todavía no es un int ni un float64. Toma el tipo
que necesita cada cuenta, si su valor cabe en ese tipo, y 12 cabe en los dos.
Los números escritos tal cual en el código también son constantes sin tipo. Por eso, en la lección
anterior, el 6 de precioExacto / 6 funcionaba como un float64. Y por eso 6.0 funcionaba como un
int en precioCaja / 6.0: vale exactamente seis, y seis cabe en un int. 2.75, en cambio, no cabe
en un int sin perder sus decimales, y Go respondía (truncated).
Los mensajes de Go nombran así a estas constantes:
untyped int constant: una constante entera sin tipo, como12.untyped float constant: una escrita con punto decimal, como2.75.untyped string constant: un texto sin tipo, como el"24"de la lección Variables y valores cero.
Cuando nada pide un tipo, como en n := porCaja o en fmt.Println(porCaja), Go usa el tipo por
defecto de la constante: int para un número sin punto decimal, float64 para uno con punto,
string para un texto y bool para true y false. Es la regla que ya conoces de :=: 9 da un
int, y 9.0, un float64.
También puedes darle un tipo a una constante, como a una variable: const porCaja int = 12. Una
constante con tipo se comporta como un valor de ese tipo, y ya no se adapta a la cuenta:
package main
import "fmt"
func main() {
const porCaja int = 12
precioCaja := 30.0
fmt.Println("Precio por cuaderno:", precioCaja/porCaja)
}Salida:
./main.go:8:38: invalid operation: precioCaja / porCaja (mismatched types float64 and int)
Es el error de la lección anterior: precioCaja es un float64, porCaja ahora es un int, y Go no
hace una cuenta entre dos tipos distintos. Para corregirlo, quítale el tipo a la constante:
const porCaja = 12.
Consejo
Declara tus constantes sin tipo, salvo que necesites uno en particular. Así una misma constante sirve en
cuentas con int y con float64, sin conversiones, y sus cuentas no pierden precisión, como ves en la
sección siguiente. Es lo habitual en Go.
La precisión de las constantes
Un clip cuesta 0.1 y una goma, 0.2. Esta es la suma, primero con constantes y después con variables:
package main
import "fmt"
func main() {
const clip = 0.1
const goma = 0.2
fmt.Println(clip + goma)
precioClip := 0.1
precioGoma := 0.2
fmt.Println(precioClip + precioGoma)
}Salida:
0.3 0.30000000000000004
La misma cuenta da dos resultados. Las variables son float64, y un float64 guarda cada número con
una cantidad fija de cifras, en binario, el sistema de ceros y unos de la computadora. En binario, 0.1
no tiene un valor exacto, como un tercio no lo tiene en decimal (0.333…), así que el float64 guarda
un número apenas distinto. Al sumar, esa diferencia se nota en la última cifra: 0.30000000000000004.
Las constantes sin tipo no tienen ese límite. El compilador hace las cuentas entre constantes sin tipo con
mucha más precisión que un float64, y clip + goma da 0.3 justo. Solo al final, para imprimirla, Go la
convierte a float64, y sale 0.3.
Con los enteros pasa algo parecido. Un int tiene un límite: en el navegador, y en casi cualquier
computadora actual, guarda enteros de -9223372036854775808 a 9223372036854775807, unos 9 trillones
hacia cada lado del cero. Una constante sin tipo puede ser más grande:
package main
import "fmt"
func main() {
const millon = 1000000
const billon = millon * millon
const trillon = millon * billon
fmt.Println("Un trillón:", trillon)
fmt.Println("Billones en 10 trillones:", 10*trillon/billon)
}Salida:
Un trillón: 1000000000000000000 Billones en 10 trillones: 10000000
10*trillon vale 10 trillones, más de lo que cabe en un int. Como es una cuenta entre constantes sin
tipo, el compilador la hace sin ese límite, y el resultado final, 10000000, sí cabe en un int. Por eso el
programa compila e imprime.
La regla: las cuentas entre constantes sin tipo no pierden precisión ni se desbordan. Una constante
con tipo, como const porCaja int = 12, sigue los límites de su tipo: pierde precisión como un
float64 y, si su valor no cabe, el programa no compila.
El límite aparece cuando una constante sin tipo se usa como un valor con tipo, por ejemplo al guardarla en una variable. Ahí tiene que caber en el tipo:
package main
import "fmt"
func main() {
const trillon = 1000000000000000000
var total int = 10 * trillon
fmt.Println(total)
}Salida:
./main.go:7:18: cannot use 10 * trillon (untyped int constant 10000000000000000000) as int value in variable declaration (overflows)
Lee el mensaje por partes:
./main.go:7:18es el lugar: archivomain.go, línea 7, columna 18, donde empieza10 * trillon.cannot use 10 * trillonsignifica “no se puede usar10 * trillon”.(untyped int constant 10000000000000000000)dice qué es para Go: una constante entera sin tipo, y cuánto vale.as int value in variable declarationsignifica “como valorinten la declaración de una variable”.(overflows)significa “se desborda”: el valor no cabe en el rango de unint.
Go revisa al compilar que el valor de una constante quepa en el tipo donde la usas. Si no cabe, el programa no compila: Go nunca la recorta para que quepa. Cuando aparece este error, revisa el valor; lo más común es una cifra de más.
Esa revisión es solo para constantes: una cuenta entre variables int que pasa el límite no da error, y
el resultado da la vuelta al otro extremo.
Go tiene otros tipos enteros, con rangos más pequeños. Por ejemplo, byte, que verás en la lección
“Strings, bytes y runes”, guarda enteros de 0 a 255. Con él, el desbordamiento llega mucho antes:
var asistentes byte = 300 tampoco compila, y su mensaje también termina en (overflows).
Si vienes de JavaScript, Python o Java…
En JavaScript y en Python, 0.1 + 0.2 también da 0.30000000000000004, y en Java, con double, lo
mismo: es cómo funcionan los números con decimales en binario. Las variables float64 de Go se comportan
igual. La diferencia está en las constantes sin tipo, que el compilador calcula con más precisión.
En Python, un entero no tiene límite. En Go, un int sí lo tiene, aunque las cuentas entre constantes sin
tipo no.
Un bloque de constantes
Para declarar varias constantes juntas, Go tiene el bloque const:
package main
import "fmt"
const (
tienda = "Papelería Central"
apertura = 9
cierre = 20
)
func main() {
fmt.Println(tienda)
fmt.Println("Horas abierta:", cierre-apertura)
}Salida:
Papelería Central Horas abierta: 11
const ( … ) declara varias constantes, una por línea, como el bloque import ( … ) agrupa varios
imports. Cada línea lleva un nombre, un = y su valor. Los espacios antes de los = los pone gofmt,
que los alinea para que el bloque se lea como una lista.
El bloque está fuera de func main, debajo de los imports. Una constante declarada fuera de toda función
la puede usar cualquier función del programa. Es el lugar habitual para los valores que valen en todo el
programa, como el nombre de la tienda o su horario. Las constantes de las secciones anteriores estaban
dentro de func main, y solo main podía usarlas.
Numerar con iota
La papelería abre de lunes a sábado, y el programa necesita un número para cada día:
package main
import "fmt"
const (
lunes = iota
martes
miercoles
jueves
viernes
sabado
)
func main() {
fmt.Println("Lunes:", lunes)
fmt.Println("Miércoles:", miercoles)
fmt.Println("Sábado:", sabado)
}Salida:
Lunes: 0 Miércoles: 2 Sábado: 5
iota es un contador que Go pone en cada bloque const: vale 0 en la primera línea del bloque, 1
en la segunda, 2 en la tercera, y así. Solo cuentan las líneas que declaran una constante: las líneas
en blanco y los comentarios no. lunes = iota está en la primera línea, así que lunes vale 0.
Las otras líneas no tienen = ni valor. En un bloque const, una línea sin valor repite la expresión de
la línea anterior: para martes, Go repite = iota, pero en la segunda línea, donde iota vale 1. Así,
miercoles vale 2, y sabado, 5.
¿Para qué sirve? Para darle un nombre a cada opción de una lista. Lo que importa no es el número, sino
que cada día tenga uno distinto y que el código diga miercoles en lugar de un 2 que nadie entiende.
Al imprimir la constante sale su número, 2, no su nombre.
iota vuelve a 0 en cada const nuevo: en otro bloque y también en una línea const suelta. Por eso,
para numerar una lista, todas las constantes van en el mismo bloque.
Empezar en 1
Para numerar los días del 1 al 6, como en un calendario, usa iota en una cuenta:
package main
import "fmt"
const (
lunes = iota + 1
martes
miercoles
jueves
viernes
sabado
)
func main() {
fmt.Println("Lunes:", lunes)
fmt.Println("Sábado:", sabado)
}Salida:
Lunes: 1 Sábado: 6
Las líneas sin valor repiten la cuenta completa, iota + 1, cada una con su propio iota: lunes vale
0 + 1, martes, 1 + 1, y sabado, 5 + 1, que es 6.
Otra forma de empezar en 1 es ocupar la primera línea con _ = iota. El identificador vacío _ descarta
el 0, y lunes, en la segunda línea, vale 1.
Consejo
Usa iota cuando el número en sí no importa, solo que cada opción tenga uno distinto. Si los números
tienen un significado propio, como una hora o un código que se guarda en un archivo, escríbelos a mano:
con iota, agregar una línea en medio del bloque cambia los números de todas las siguientes.
Si vienes de JavaScript, Python o Java…
Java, TypeScript y Python tienen enum para las listas de opciones. Go no tiene enum: usa un bloque
const con iota. Más adelante en el curso vas a ver cómo darles a estas constantes un tipo propio,
para que Go no confunda un día con cualquier otro número.
En resumen
constdeclara un valor con nombre que no cambia. Su valor se fija al compilar, y darle otro valor con=no compila (cannot assign to).- Una constante sin tipo, como
const porCaja = 12o un número escrito tal cual, toma el tipo que necesita la cuenta, si su valor cabe. Si nada pide un tipo, usa su tipo por defecto:int,float64,stringobool. - Las cuentas entre constantes sin tipo no pierden precisión ni se desbordan. Al usar una constante con
un tipo, su valor tiene que caber en él (
overflows); una constante con tipo sigue sus límites. - En un bloque
const ( … ),iotavale0,1,2… en cada línea, y una línea sin valor repite la expresión anterior. Coniota + 1, la lista empieza en 1.
Ejercicios
Declara las constantes lapiz, con 0.7, y goma, con 0.1, y debajo de Útiles escolares imprime Total: 0.8.
Para salir del editor con el teclado, pulsa Escape y luego Tab.
package main
import "fmt"
func main() {
fmt.Println("Útiles escolares")
// Escribe aquí: declara las constantes lapiz y goma, e imprime
// el total.
}