Los tipos enteros en Go representan valores numéricos sin decimales. No todos los enteros son iguales: existen los con signo (pueden ser negativos, como int) y los sin signo (solo valores de cero hacia arriba, como uint). La diferencia fundamental entre ellos radica en cuántos bits ocupan en la memoria, lo que define el rango de números que pueden almacenar.
Go utiliza una distinción clara entre tipos de tamaño variable y tipos de tamaño fijo. El tipo int es dependiente de la plataforma: en un sistema de 64 bits, es un int64, y en uno de 32 bits, es un int32. Esto se debe a que el procesador maneja de forma nativa estos tamaños, optimizando la velocidad de las operaciones lógicas y de indexación. Por el contrario, tipos como int8, int16 o int64 tienen un tamaño absoluto que no cambia, sin importar dónde corra el código.
Usarás int para la gran mayoría de la lógica de tu aplicación: contadores de bucles, índices de un slice o tamaños de listas, ya que es lo más eficiente para la CPU. Sin embargo, cuando necesitas interoperabilidad con protocolos de red (como un campo que debe ser de 16 bits), serialización binaria o quieres ahorrar memoria en un array de millones de elementos, debes usar tamaños fijos. También existen alias semánticos: byte es simplemente un uint8 usado para datos brutos, y rune es un int32 diseñado para representar un carácter Unicode completo.
Si intentas realizar operaciones entre tipos de distintos tamaños (por ejemplo, sumar un int8 con un int32), el compilador te detendrá. Si ignoras los límites de un tipo fijo y tratas de guardar un número más grande de lo que su capacidad permite, sufrirás un desbordamiento (overflow), donde el valor “da la vuelta” y se convierte en un número inesperado, a menudo un valor negativo o cero. Para casos de muy bajo nivel donde necesitas manipular direcciones de memoria como si fueran números, existe uintptr, aunque su uso se limita a operaciones con el paquete unsafe.
package main
import (
"fmt"
)
// Packet representa una estructura de datos para un protocolo de red.
// Usamos tamaños fijos para asegurar que el paquete siempre mida lo mismo.
type Packet struct {
ID uint8 // Rango 0 a 255. Ideal para identificadores pequeños.
Timestamp int64 // Un número muy grande para representar tiempo Unix.
Payload [4]byte // 4 bytes de datos crudos (alias de uint8).
}
func main() {
// 1. Uso de tipos estándar para lógica general
var contador int = 100
fmt.Printf("Contador (int): %v\n", contador)
// 2. El tipo byte (uint8) para datos brutos
var dato byte = 'A'
fmt.Printf("Dato (byte): %v (valor numérico: %d)\n", dato, dato)
// 3. El tipo rune (int32) para caracteres Unicode
// Un rune puede representar cualquier carácter, incluso emojis.
var caracter rune = '🚀'
fmt.Printf("Carácter (rune): %c\n", caracter)
// 4. Construcción de una estructura con tipos específicos
p := Packet{
ID: 255,
Timestamp: 1704067200,
Payload: []byte{'G', 'o', 'p', 'h'}, // Convertimos un slice de bytes
}
fmt.Printf("Paquete recibido: %+v\n", p)
// 5. Conversión explícita: necesaria porque Go no hace promoción de tipos
var pequeño int8 = 120
var grande int16 = 30
// El compilador no permite: resultado := pequeño + grande
// Debemos convertir el tipo más pequeño al más grande para operar.
resultado := int16(pequeño) + grande
fmt.Printf("Suma convertida (int16): %v\n", resultado)
}
Desglose del ejemplo
En la estructura Packet, hemos definido ID como uint8. Esto es una decisión de diseño: sabemos que el ID nunca será negativo y que no necesitamos que supere 255, por lo que ahorramos memoria. El Timestamp es un int64 porque el tiempo en segundos o nanosegundos requiere un rango masivo para no desbordarse en el futuro.
En la sección de main, observamos que byte y rune actúan como alias. Cuando asignamos 'A' a un byte, estamos guardando su valor numérico (65). De igual forma, el emoji 🚀 no cabe en un byte, por lo que el tipo rune (que es un int32) nos permite almacenar su valor Unicode completo.
Un punto crítico es la conversión en la variable resultado. En otros lenguajes, el compilador podría convertir automáticamente el int8 a int16 para que la suma funcione. Go no hace esto para evitar errores sutiles de precisión o desbordamiento accidentales. Tienes que ser explícito mediante int16(pequeño).
El error frecuente
Un error clásico al trabajar con tipos de tamaño fijo es el desbordamiento por falta de previsión. Si intentas incrementar un valor que ya está en su límite máximo, el programa no fallará con un error, sino que el número cambiará de forma inesperada.
// El error típico: Desbordamiento (Overflow)
var limite uint8 = 255
fmt.Println("Límite:", limite)
limite = limite + 1
// En lugar de ser 256, el valor "da la vuelta" al ciclo del tipo.
fmt.Println("Límite + 1:", limite) // Imprime: 0
Esta conducta puede causar bugs catastróficos en cálculos de memoria o lógica de negocio si no se valida que el valor resultante sea coherente.
N° 18