Tipos, variables y funciones en Go

Lección 2 de 8. Las piezas básicas: cómo se declara una variable, qué vale antes de que le des nada y por qué en Go una función puede devolver dos cosas a la vez. Esto último no es un capricho de sintaxis: es la base de cómo se manejan los errores, que veremos en la lección 5.

Tres formas de declarar

Las tres formas de declarar una variable en GoTres maneras de declarar, y cuándo vale cada unavar nombre string = «Ana»dices el tipo y el valoren cualquier sitiovar edad = 34el tipo lo deduce Goen cualquier sitiociudad := «Lima»lo deduce y es más cortoSOLO dentro de una funciónLa tercera es la que vas a escribir el 90 % de las veces. Las otras dos, cuando el tipo no se deduce solo o no estás dentro de una funciónY una variable declarada nunca contiene basura: si no le das valor, recibe el valor cero de su tipo
var nombre string = "Ana"   // tipo y valor, explícitos
var edad = 34               // el tipo se deduce del valor
ciudad := "Lima"            // lo mismo, más corto
salidaAna 34 Lima

Las tres declaran lo mismo. En la práctica vas a usar := casi siempre, y var cuando necesites fijar el tipo a mano o declarar algo fuera de una función.

Porque esa es la limitación del atajo: := solo funciona dentro de una función.

package main

x := 10      // fuera de cualquier función
no compila./main.go:3:1: syntax error: non-declaration statement outside function body
Y un detalle que confunde al principio. := declara, así que exige que al menos una de las variables de la izquierda sea nueva. Repetirlo sobre la misma variable no funciona: no new variables on left side of :=. Para reasignar se usa el = de toda la vida.

El valor cero: nunca hay basura

En Go una variable declarada siempre tiene un valor definido, aunque no le hayas dado ninguno. No existe el «dato sin inicializar» de C ni hace falta un null por defecto. A ese valor se le llama el valor cero del tipo.

var s string
var n int
var f float64
var b bool
var p *int

fmt.Printf("cadena=%q entero=%d decimal=%v booleano=%v puntero=%v\n", s, n, f, b, p)
salidacadena="" entero=0 decimal=0 booleano=false puntero=<nil>
Tipo Valor cero
Números: int, float64… 0
string La cadena vacía, ""
bool false
Punteros, slices, maps, interfaces, funciones nil
struct Cada campo con su propio valor cero

Esto tiene una consecuencia práctica que se agradece: un contador recién declarado ya vale cero y un booleano ya vale false, así que no hay que inicializarlos a mano.

Los tipos no se mezclan solos

Go no convierte números por su cuenta, ni siquiera entre un entero y un decimal. Esto no compila:

var entero int = 7
var decimal float64 = entero / 2
no compila./main.go:7:24: cannot use entero / 2 (value of type int) as float64 value in variable declaration

Hay que pedir la conversión:

var decimal float64 = float64(entero) / 2
fmt.Println(decimal)
salida3.5

Fíjate en dónde va la conversión. Si escribieras float64(entero / 2), la división se haría primero entre enteros y daría 3, y convertir después no recupera el medio perdido. Es el error de cálculo más común en Go y en casi cualquier lenguaje con tipos estrictos.

Funciones, y el retorno doble

Una función declara los tipos de sus parámetros y de lo que devuelve. Si varios parámetros comparten tipo, basta ponerlo una vez:

func dividir(a, b float64) (float64, error) {
	if b == 0 {
		return 0, fmt.Errorf("no se puede dividir %v entre cero", a)
	}
	return a / b, nil
}

Eso de devolver dos valores es lo más característico de Go. No hay excepciones: una función que puede fallar devuelve el resultado y, al lado, un error. Cuando todo va bien, el error vale nil.

r, err := dividir(10, 4)
fmt.Println(r, err)

r, err = dividir(10, 0)
fmt.Println(r, err)
salida2.5 <nil>
0 no se puede dividir 10 entre cero

Lo que harías de verdad no es imprimir el error sino comprobarlo, y eso tiene una forma fija que verás en todo el código de Go. La dejamos planteada aquí y la desarrollamos en la lección 5:

r, err := dividir(10, 0)
if err != nil {
	// algo ha ido mal: decide qué hacer y sal
	return
}
// a partir de aquí r es de fiar

Resultados con nombre

Los valores de retorno pueden llevar nombre, y entonces ya están declarados dentro de la función:

func minMax(nums []int) (menor, mayor int) {
	menor, mayor = nums[0], nums[0]
	for _, n := range nums {
		if n < menor {
			menor = n
		}
		if n > mayor {
			mayor = n
		}
	}
	return          // devuelve menor y mayor, sin repetirlos
}
salidamenor: 2 mayor: 9

Ese return a secas se llama naked return. Es cómodo en funciones cortas como esta y se desaconseja en las largas, porque obliga a buscar arriba qué se está devolviendo. El valor real de ponerles nombre es otro: documentan la firma. (menor, mayor int) dice mucho más que (int, int).

El guion bajo, cuando no quieres algo. Como las variables sin usar no compilan, Go necesitaba una salida para los valores que te devuelven y no te interesan. Es _: _, err := dividir(10, 2) descarta el resultado y se queda solo con el error. Lo verás por todas partes.

Lo que hay que llevarse

:= dentro de funciones y var fuera. Toda variable nace con un valor cero conocido. Las conversiones entre tipos numéricos son siempre explícitas. Y lo que de verdad hay que interiorizar: en Go las funciones devuelven el resultado y el error juntos, y esa pareja va a aparecer en cada línea que escribas a partir de ahora.

Todo el código de esta lección se compiló y ejecutó con Go 1.25 antes de publicarla. Los tres errores están provocados a propósito y los mensajes son los que devuelve el compilador, copiados literalmente.