En Go, no existe el concepto de “basura en memoria” para variables locales o campos de structs. Cuando declaras una variable sin asignarle un valor, el runtime le asigna automáticamente un zero value (valor cero). Esta es una garantía de que cada tipo tiene un estado inicial predecible: 0 para números, false para booleanos, "" para strings y nil para tipos de referencia como punteros, slices, maps, canales, interfaces y funciones.
Esta decisión de diseño es fundamental porque elimina la clase de errores más comunes en lenguajes como C o C++, donde una variable no inicializada puede contener valores aleatorios que residen en la memoria, provocando comportamientos erráticos. En Go, si declaras un int, sabes con total seguridad que su valor es 0.
Debes confiar en este comportamiento para simplificar tu código, permitiéndote omitir inicializaciones redundantes para tipos básicos. Sin embargo, el diseño de tus estructuras debe ser consciente de este mecanismo: si diseñas un objeto complejo que requiere una configuración externa (como una conexión a una base de datos), su zero value será nil y tratar de usarlo sin una inicialización explícita provocará un panic.
package main
import (
"fmt"
"sync"
)
// Metric representa un medidor de rendimiento de un servidor.
// Fíjate cómo la estructura es totalmente funcional desde su declaración.
type Metric struct {
mu sync.Mutex // El zero value de sync.Mutex es un mutex desbloqueado y listo para usar.
name string // ""
count int // 0
active bool // false
history []string // nil, pero append funciona perfectamente con slices nil.
}
// Incrementa el contador y registra un evento.
func (m *Metric) Increment(event string) {
m.mu.Lock() // Funciona aunque 'm' no haya sido inicializado con 'new' o un literal.
defer m.mu.Unlock()
m.count++
m.name = "CPU_Usage"
m.active = true
// append maneja internamente el caso donde el slice es nil.
m.history = append(m.history, event)
}
func (m *Metric) PrintStatus() {
m.mu.Lock()
defer m.mu.Unlock()
fmt.Printf("Metrica: %s | Valor: %d | Activa: %v | Logs: %v\n",
m.name, m.count, m.active, m.history)
}
func main() {
// Caso 1: Tipos básicos declarados sin valor explícito.
var id int
var etiqueta string
var procesando bool
fmt.Printf("Básicos -> int: %d, string: %q, bool: %t\n", id, etiqueta, procesando)
// Caso 2: Uso de un struct "Zero Value Ready".
// No necesitamos hacer m := Metric{count: 0, ...}
var m Metric
fmt.Println("\n--- Estado inicial (zero values) ---")
m.PrintStatus()
fmt.Println("\n--- Después de la primera operación ---")
m.Increment("System Startup")
m.Increment("Data Processed")
m.PrintStatus()
}
Desglose del ejemplo
En el código anterior, observa cómo la variable var m Metric inicializa todos sus campos automáticamente. No necesitamos un constructor para que la estructura sea segura:
m.mu.Lock(): La estructurasync.Mutexestá diseñada para que su estado por defecto sea un mutex en estado “desbloqueado”. Esto permite que sea utilizable desde su declaración sin llamadas adicionales de configuración.m.count++: Comocountes unint, comienza en0. No hay riesgo de sumar1a un valor basura de la memoria.m.history = append(...): Aunquem.historyes un slice con valornil, la función integradaappendestá diseñada para detectar este estado y asignar memoria dinámicamente en la primera llamada. Esto hace que el manejo de listas sea mucho más limpio en Go.m.name: El string vacío""es un valor válido y seguro, lo que evita errores al concatenar o comparar.
El error frecuente
Un error muy común al trabajar con zero values ocurre con los maps. A diferencia de los slices, un map en su estado zero es nil.
Si intentas escribir en un mapa nil, el programa lanzará un panic.
// ERROR: Esto fallará en ejecución var m map[string]int m["clave"] = 10 // panic: assignment to entry in nil map
Para poder escribir en un mapa, siempre debes inicializarlo usando la función make(map[K]V) o mediante un literal. Sin embargo, curiosamente, leer de un mapa nil es seguro y simplemente te devolverá el zero value del tipo de valor del mapa.
N° 22