Saltar al contenido principal

Go desde cero, parte 2: tipos, variables, constantes y el misterioso iota

Curso: Go — Lección 3 de 3

> serie: aprende Go de 0 a 100 | artículo 2 de 19

En el artículo anterior instalamos Go y compilamos el primer binario. Hoy toca el material del que está hecho todo lo demás: los tipos de datos en Go, las variables y las constantes. Y al final, iota, esa palabra que aparece en todo código Go y que nadie explica bien a la primera.


Tipado estático, ceremonia mínima

Go es de tipado estático: cada variable tiene un tipo clavado en compilación. Lo que no tiene es la verborrea que asocias a esa frase. Hay tres formas de declarar:

// Forma completa: var, nombre, tipo, valor.
var edad int = 30

// El compilador infiere el tipo. Sigue siendo int.
var edad = 30

// Forma corta: declara e inicializa de una.
// Solo funciona dentro de funciones.
edad := 30

El operador := es el que vas a usar el 90% del tiempo. A nivel de paquete (fuera de funciones) toca usar var. Fuente: A Tour of Go, declaraciones cortas.

Si vienes de Python o JS, edad := 30 te parecerá tu asignación de siempre. La diferencia: el tipo queda fijado para siempre. Un edad = «treinta» después no es un rebind, es un error de compilación. Si vienes de Java, esto es tu var de Java 10, solo que Go lo trae de serie desde 2009 (JEP 286 llegó en 2018, sin acritud).

Detalle importante: una variable declarada y no usada es un error de compilación, no un warning que ignorar. Es una decisión de diseño para que el código no acumule trastos (FAQ oficial).

Los tipos básicos: el catálogo de tipos de datos en Go

El catálogo completo de tipos de datos en Go, según la especificación:

  • bool
  • string (inmutable, UTF-8 por dentro, la destripamos en el artículo 8)
  • Enteros con signo: int, int8, int16, int32, int64
  • Enteros sin signo: uint, uint8, uint16, uint32, uint64, uintptr
  • byte (alias de uint8) y rune (alias de int32, representa un punto de código Unicode)
  • Decimales: float32, float64
  • Complejos: complex64, complex128

El tipo int a secas depende de la plataforma: 64 bits en sistemas de 64 bits, 32 en los de 32 (A Tour of Go, tipos básicos). Salvo que tengas un motivo concreto, usa int para enteros y float64 para decimales.

Zero values: nada queda sin inicializar

En Go no existe el valor basura ni el undefined. Una variable declarada sin valor recibe su zero value: 0 para números, false para bool y cadena vacía para strings (A Tour of Go, zero values).

var contador int // 0
var activo bool // false
var nombre string // ""

// %v imprime el valor, %q el string entre comillas.
fmt.Printf("%v %v %q\n", contador, activo, nombre)

Comparación rápida: en JS una variable sin asignar es undefined y en Java un campo sin inicializar puede sorprenderte con null. En Go el comportamiento está definido en la especificación y punto.

Conversiones: siempre explícitas

Go no convierte tipos por ti. Nunca. Ni siquiera entre int e int64:

i := 42
f := float64(i) // conversión explícita
u := uint(f)

// Esto NO compila: mismatched types
// suma := i + f

La sintaxis es Tipo(valor) y no hay excepciones (A Tour of Go, conversiones).

Truco de depuración: fmt.Printf(«%T», x) te dice el tipo de cualquier cosa.

Constantes

Se declaran con const y no admiten :=:

const Pi = 3.14159
const Saludo = "hola"
const Max int = 100

Aquí hay un superpoder poco conocido: las constantes sin tipo (untyped) viven con precisión arbitraria hasta que se usan. Puedes escribir const Enorme = 1 << 100 y compila sin problema, siempre que al usarla quepa en el tipo de destino (especificación, constantes).

iota: el generador de enums que no es un enum

Go no tiene enum como palabra clave. Tiene algo más simple y más raro: iota, un contador que vale 0 en la primera línea de un bloque const y suma 1 por cada línea siguiente (especificación, iota).

type Nivel int

const (
  Debug Nivel = iota // 0
  Info // 1 (la expresión se repite sola)
  Warn // 2
  Error // 3
)

Fíjate en el truco: las líneas siguientes no escriben nada y heredan la expresión de la primera, con iota ya incrementado. Comparado con la ceremonia de un enum de Java o una clase Enum de Python, esto es casi ascii art.

Y el ejemplo canónico de Effective Go, tamaños de bytes con desplazamiento de bits:

type ByteSize float64

const (
  _ = iota // descartamos el 0
  KB ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
  MB // 1 << 20
  GB // 1 << 30
  TB // 1 << 40
)

El guion bajo _ es el blank identifier: le asignas algo y lo tira a la basura sin quejarse.

Honestidad técnica: esto no es un enum de verdad. Nada impide que alguien escriba Nivel(42) y compile. Go prioriza la simplicidad y deja la validación en tu mano. Tendremos más herramientas cuando lleguemos a interfaces (artículo 6).

Tipos de datos en Go: Diagrama pixelart de un bloque const en Go donde iota asigna 0, 1, 2 y 3 a Debug, Info, Warn y Error
[Iota cuenta solo: tú escribes la primera línea y él rellena el resto.]

Reto de la semana: tipos de datos en Go

  • Declara una variable de cada tipo básico sin inicializar e imprime valor y tipo con fmt.Printf(«%v %T\n», …). Confirma los zero values.
  • Reproduce el bloque ByteSize y calcula cuántos MB son 3 GB usando solo las constantes.
  • Bonus: declara una variable, no la uses, y disfruta del mensaje del compilador. Luego intenta asignar un string a un int. Conocer los errores también es aprender el idioma.

En el próximo artículo: if, for y switch. Spoiler: solo existe un bucle en todo el lenguaje.

> exit
Retrato pixel art de Jenniffer Cubillos

gracias por leer