En esta página

Variables y tipos de datos

14 min lectura TextoCap. 1 — Fundamentos de Go

El sistema de tipos de Go

Go es un lenguaje de tipado estático fuerte. Esto significa dos cosas: primero, cada variable tiene un tipo fijo que el compilador conoce en tiempo de compilación; segundo, las conversiones entre tipos nunca ocurren de forma implícita — siempre debes ser explícito. Esta combinación elimina toda una categoría de bugs que son comunes en JavaScript o Python.

Entender el sistema de tipos de Go es fundamental porque todo lo demás en el lenguaje se construye sobre él.

Declarando variables

Go ofrece varias formas de declarar variables, cada una con su contexto de uso ideal:

1. Declaración explícita con `var`

var nombre string = "Go"
var edad int = 13  // Go cumplió 13 años en 2022
var pi float64 = 3.14159
var activo bool = true

2. Declaración con inferencia de tipo

Si proporcionas un valor inicial, Go puede inferir el tipo:

var nombre = "Go"    // string inferido
var pi = 3.14159     // float64 inferido
var activo = true    // bool inferido

3. Declaración corta con `:=`

La forma más común dentro de funciones. Solo funciona dentro de funciones (no a nivel de paquete):

func main() {
    nombre := "Gopher"     // string
    versión := 1.26        // float64
    activo := true         // bool
    bytes := byte('A')     // byte (uint8)
}

4. Bloque `var` para múltiples variables

Cuando declaras varias variables relacionadas, un bloque var mejora la legibilidad:

var (
    host     string = "localhost"
    puerto   int    = 8080
    debug    bool   = false
    timeout  float64 = 30.0
)

Tipos numéricos

Go tiene un conjunto específico de tipos numéricos. La elección correcta importa para rendimiento y semántica:

Enteros con signo

Tipo Tamaño Rango
int8 8 bits -128 a 127
int16 16 bits -32,768 a 32,767
int32 32 bits -2,147,483,648 a 2,147,483,647
int64 64 bits ±9.2 × 10¹⁸
int 32 o 64 bits* depende de la plataforma

*int es 64 bits en sistemas de 64 bits (prácticamente todos los sistemas modernos). Es el tipo entero por defecto.

Enteros sin signo

var b uint8  = 255     // byte — alias de uint8
var s uint16 = 65535
var u uint32 = 4294967295
var g uint64 = 18446744073709551615
var n uint   = 42      // sin signo, tamaño de plataforma

Punto flotante

var f32 float32 = 3.14      // precisión simple (~7 dígitos decimales)
var f64 float64 = 3.14159265358979  // precisión doble (~15 dígitos)
// float64 es el tipo por defecto para literales flotantes
pi := 3.14159  // float64

Números complejos

var c64  complex64  = 1 + 2i
var c128 complex128 = 3.14 + 2.71i

El tipo `string`

En Go, un string es una secuencia inmutable de bytes codificada en UTF-8. No puedes modificar un carácter de un string directamente — debes crear uno nuevo:

saludo := "¡Hola, Go!"

// Longitud en bytes (no en caracteres Unicode)
fmt.Println(len(saludo))  // puede ser > número de caracteres si hay UTF-8 multi-byte

// Concatenación
completo := saludo + " ¿Cómo estás?"

// String multilínea con backticks (raw string literal)
json := `{
    "nombre": "Go",
    "versión": 1.26
}`

// Interpolación (requiere fmt.Sprintf)
mensaje := fmt.Sprintf("Versión: %.2f", 1.26)

`byte` y `rune`

Aquí está una distinción crucial en Go:

  • byte (alias de uint8): representa un byte individual. Útil para trabajar con datos binarios.
  • rune (alias de int32): representa un punto de código Unicode. Un solo carácter como ñ, é o puede ocupar múltiples bytes en UTF-8, pero es siempre un solo rune.
// Iterar sobre bytes
s := "hola"
for i := 0; i < len(s); i++ {
    fmt.Printf("byte[%d] = %d\n", i, s[i])
}

// Iterar sobre runes (caracteres Unicode correctamente)
for i, r := range "¡Hola!" {
    fmt.Printf("rune[%d] = %c (%d)\n", i, r, r)
}

// Convertir string a slice de runes para acceso por índice
runes := []rune("¡Hola!")
fmt.Println(len(runes))  // 6 (caracteres), no bytes

El tipo `bool`

activo := true
inactivo := false

// Operadores lógicos
resultado := activo && !inactivo  // true
cualquiera := activo || inactivo  // true

Valores cero (zero values)

Esta es una de las características más elegantes de Go: toda variable declarada sin valor inicial recibe automáticamente su valor cero. No hay variables sin inicializar, no hay comportamiento indefinido:

var i int        // 0
var f float64    // 0.0
var b bool       // false
var s string     // "" (cadena vacía)
var p *int       // nil
var slice []int  // nil
var m map[string]int  // nil

Los valores cero hacen que el código sea predecible. En otros lenguajes, leer una variable no inicializada puede dar resultados aleatorios o lanzar excepciones. En Go, siempre obtienes el valor cero definido por el tipo.

Constantes y `const`

Las constantes en Go se evalúan en tiempo de compilación y no ocupan memoria en tiempo de ejecución:

const Pi = 3.14159265358979323846
const Empresa = "Bemorex"
const MaxConexiones = 100

// Constantes tipadas
const Timeout time.Duration = 30 * time.Second

// Bloque de constantes
const (
    KB = 1024
    MB = 1024 * KB
    GB = 1024 * MB
)

`iota`: enumeraciones idiomáticas

iota es un identificador especial de Go que se usa dentro de bloques const. Empieza en 0 y se incrementa en 1 por cada constante en el bloque:

type Nivel int

const (
    Principiante Nivel = iota  // 0
    Intermedio                  // 1
    Avanzado                    // 2
    Experto                     // 3
)

// Con expresiones
type ByteSize float64

const (
    _           = iota // Ignorar el primer valor con _
    KB ByteSize = 1 << (10 * iota)  // 1024
    MB                               // 1048576
    GB                               // 1073741824
    TB                               // 1099511627776
)

Conversión de tipos explícita

En Go, nunca hay conversión implícita entre tipos numéricos. Debes ser explícito:

var i int = 42
var f float64 = float64(i)   // int → float64
var u uint = uint(f)          // float64 → uint

// De string a número
import "strconv"

n, err := strconv.Atoi("42")          // string → int
if err != nil {
    fmt.Println("Error de conversión")
}

f, err := strconv.ParseFloat("3.14", 64)  // string → float64

// De número a string
s := strconv.Itoa(42)                  // int → string (más eficiente que fmt.Sprintf)
s2 := fmt.Sprintf("%.2f", 3.14)       // float64 → string formateado

Múltiple asignación

Go permite asignar múltiples variables en una sola línea:

x, y := 10, 20
a, b := "hola", true

// Intercambiar valores sin variable temporal
x, y = y, x
fmt.Println(x, y)  // 20 10

La variable en blanco `_`

El guion bajo _ descarta valores. Es el "agujero negro" de Go:

// Ignorar el índice en un range
for _, valor := range []int{1, 2, 3} {
    fmt.Println(valor)
}

// Ignorar un valor de retorno
resultado, _ := strconv.Atoi("42")  // ignorar el error (no recomendado en producción)

Con este conocimiento sólido del sistema de tipos, en la siguiente lección exploraremos cómo controlar el flujo de ejecución con if, switch y el único bucle de Go: for.

Go no tiene conversión implícita de tipos
A diferencia de JavaScript o Python, Go nunca convierte tipos automáticamente. Si tienes un int y necesitas un float64, debes escribir float64(miInt) explícitamente. Esto previene errores sutiles de precisión.
iota se reinicia en cada bloque const
iota es un contador que empieza en 0 y se incrementa en cada línea de un bloque const. Es perfecto para crear enumeraciones sin tener que asignar valores manualmente. Puedes combinarlo con expresiones: iota * 2, 1 << iota, etc.