Control y colecciones en Go

Lección 3 de 8. Go tiene un solo bucle, un switch que no necesita break y dos estructuras de datos que sostienen prácticamente todo lo que se escribe: los slices y los maps. La parte importante de esta lección es la última: entender qué es un slice de verdad evita una clase entera de errores silenciosos.

Solo hay un bucle: for

No hay while ni do-while. Hay for, y adopta la forma que haga falta.

suma := 0
for i := 1; i <= 5; i++ {       // el clásico de tres partes
	suma += i
}

n := 0
for n < 3 {                     // esto es el while
	n++
}

for {                           // bucle infinito: hace falta break
	break
}
salidasuma: 15
n: 3

Fíjate en que la condición no lleva paréntesis y las llaves son obligatorias aunque el cuerpo tenga una sola línea. Las dos cosas son iguales en el if.

if con inicialización

Una forma muy de Go: declarar una variable justo en el if, de modo que solo exista dentro.

if resto := 10 % 3; resto != 0 {
	fmt.Println("10 no es múltiplo de 3, sobra", resto)
}
salida10 no es múltiplo de 3, sobra 1

Fuera de ese if, resto no existe. Acota la variable a donde se usa, y es el patrón que se usará constantemente con los errores.

switch, sin break

nota := 7
switch {
case nota >= 9:
	fmt.Println("sobresaliente")
case nota >= 7:
	fmt.Println("notable")
default:
	fmt.Println("a mejorar")
}
salidanotable

Dos diferencias con otros lenguajes. La primera: no hay que poner break, cada caso termina solo; si de verdad quieres que siga al siguiente, existe fallthrough, pero se usa poquísimo. La segunda: un switch sin expresión funciona como una cadena de if, que es como se usa la mitad de las veces.

Slices: la lista de Go

Un slice es una secuencia de longitud variable. Se declara con corchetes vacíos y crece con append.

ciudades := []string{"Lima", "Madrid"}
fmt.Println(ciudades, len(ciudades), cap(ciudades))

ciudades = append(ciudades, "Bogotá")
fmt.Println(ciudades, len(ciudades), cap(ciudades))
salida[Lima Madrid] 2 2
[Lima Madrid Bogotá] 3 4

Dos cosas que llaman la atención. La primera: append devuelve el slice y hay que reasignarlo; escribir append(ciudades, "Bogotá") sin el ciudades = no hace nada útil. La segunda: la capacidad saltó de 2 a 4, no a 3. Cuando se queda sin sitio, Go reserva de más para no tener que hacerlo en cada añadido.

Para recorrerlo, range da el índice y el valor:

for i, c := range ciudades {
	fmt.Printf("  %d: %s\n", i, c)
}
salida  0: Lima
  1: Madrid
  2: Bogotá

Si no necesitas el índice, for _, c := range ciudades. Si solo necesitas el índice, for i := range ciudades.

Y aquí está la trampa

Recortar un slice no copia nada. Lo que obtienes es una ventana sobre el mismo array:

base := []int{1, 2, 3, 4, 5}
parte := base[1:3]
fmt.Println("base:", base, "parte:", parte, "cap:", cap(parte))

parte[0] = 99
fmt.Println("base:", base, "parte:", parte)
salidabase: [1 2 3 4 5] parte: [2 3] cap: 4
base: [1 99 3 4 5] parte: [99 3]

Tocando parte hemos cambiado base, sin que nada lo anunciara.

Un slice es una ventana sobre un array compartidoPor qué tocar un recorte cambia el originalbase := []int{1,2,3,4,5} y luego parte := base[1:3]el array que hay debajo, uno solo1[0]99[1]3[2]4[3]5[4]base · len 5 · cap 5parte · len 2 · cap 4parte no es una copia: es una ventana sobre el mismo array. Por eso parte[0] = 99 también cambia base[1]Y su capacidad llega hasta el final del array, no hasta el final del recorte: de ahí el cap 4Si quieres una copia de verdad: copia := append([]int{}, parte...)
Cómo se evita. Si quieres una copia independiente, hay que pedirla: copia := append([]int{}, parte...), o bien copia := make([]int, len(parte)); copy(copia, parte). Los tres puntos de parte... significan «pásale los elementos uno a uno» en vez del slice entero.

Maps: pares de clave y valor

stock := map[string]int{"camiseta": 12}
stock["gorra"] = 5

fmt.Println(stock["camiseta"], stock["inexistente"])
salida12 0

Ahí hay algo peligroso: pedir una clave que no existe no da error, devuelve el valor cero del tipo. Un 0 que puede ser un 0 de verdad o un «no está». Para distinguirlo, el acceso admite un segundo valor:

if v, ok := stock["inexistente"]; !ok {
	fmt.Println("no está en el mapa, v vale", v)
}
salidano está en el mapa, v vale 0

Ese ok es una convención que verás por todo Go. Y para borrar, delete(stock, "gorra").

El orden de un map es aleatorio a propósito

Recorrer un map con range no devuelve las claves en ningún orden fijo. Y no es que «no esté garantizado»: Go lo aleatoriza activamente para que nadie dependa de él. Cuatro recorridos seguidos del mismo mapa:

salidaabcd
abcd
bcda
dabc

Si necesitas un orden, hay que ordenarlo tú. El patrón es siempre el mismo: sacar las claves a un slice, ordenarlo y recorrer ese.

precios := map[string]float64{"gorra": 12.5, "camiseta": 19.9, "bufanda": 9}

claves := make([]string, 0, len(precios))
for k := range precios {
	claves = append(claves, k)
}
sort.Strings(claves)

for _, k := range claves {
	fmt.Printf("  %-9s %.2f\n", k, precios[k])
}
salida  bufanda   9.00
  camiseta  19.90
  gorra     12.50

El make([]string, 0, len(precios)) crea el slice vacío pero ya con sitio reservado para todas las claves, para que append no tenga que ir agrandándolo. Es una costumbre barata que conviene coger.

Lo que hay que llevarse

Idea Por qué importa
Solo existe for Hace de while y de bucle infinito. Sin paréntesis y con llaves siempre
if x := …; cond Acota la variable al if. Se usará en cada comprobación de error
switch sin break Cada caso termina solo
append devuelve el slice Hay que reasignarlo o no sirve de nada
Recortar no copia El recorte comparte memoria con el original
v, ok := m[k] Lo único que distingue «vale cero» de «no está»
El orden de un map es aleatorio Si lo necesitas ordenado, ordénalo tú

Todo el código de esta lección se compiló y ejecutó con Go 1.25 antes de publicarla; las salidas están copiadas de esa ejecución. Los cuatro recorridos del map son cuatro ejecuciones reales y seguidas del mismo programa.