¿Make o New? La eterna pregunta del desarrollador Go
¿Llevas una semana o un año programando en Go, pero aún te quedas paralizado un segundo antes de elegir entre make() o new()? Es normal. La pregunta "Why would I make() or new()?" en Stack Overflow ha acumulado decenas de miles de visitas, y no pierde relevancia ni siquiera en 2025. ¿Por qué? Porque detrás de la aparente simplicidad se esconde una diferencia fundamental en la filosofía de gestión de memoria en Go.
Aclaremos esto de una vez por todas para que nunca más tengas que dudar. Iremos desde lo básico hasta casos reales, destacaremos las trampas y te daremos una lista de verificación para el trabajo diario. ¡Allá vamos!
Respuesta corta (para los que tienen prisa)
Si necesitas asignar memoria para un slice, map o channel — usa make(). En todos los demás casos — usa new() o el literal &Type{}. Pero el diablo, como siempre, está en los detalles.
¿Qué es new() y cómo funciona?
La función new(T) es una función incorporada de Go que asigna memoria para el tipo T, la pone a cero (valor cero) y devuelve un puntero a esa memoria: *T.
En pocas palabras: new() te da un objeto vacío pero válido. Obtienes un puntero, pero el objeto en sí aún no está listo para funcionar completamente si necesita inicialización de estructuras internas.
package main
import "fmt"
type Person struct { Name string Age int}
func main() { p := new(Person) fmt.Println(p) // Salida: &{ 0} fmt.Println(p.Name == "") // true}Aquí, new(Person) asignó memoria para la estructura, puso a cero los campos (cadena vacía y 0) y devolvió un puntero. Todo correcto.
¿Qué es make() y su superpoder?
make() también es una función incorporada, pero funciona solo con tres tipos: slices, maps y channels. ¿Por qué solo ellos? Porque estos tipos tienen una estructura interna (cabecera) que debe inicializarse, no solo ponerse a cero.
make() no devuelve un puntero. Devuelve un valor inicializado del tipo requerido (¡no un puntero!).
// Slice: longitud 5, capacidad 10s := make([]int, 5, 10)fmt.Println(len(s), cap(s)) // 5 10
// Map: listo para insertar elementosm := make(map[string]int)m["key"] = 42fmt.Println(m) // map[key:42]
// Channel: con búfer para 3 mensajesch := make(chan int, 3)ch <- 1fmt.Println(<-ch) // 1Ten en cuenta: si intentas escribir m := new(map[string]int), obtendrás un puntero a un map nil. Insertar un elemento en ese map provocará un pánico (panic).
¿Por qué new() no funciona para slices, maps y channels?
Veamos el ejemplo de un slice. Un slice en Go es una estructura de tres campos: puntero al array, longitud y capacidad. new([]int) asignará memoria para esta estructura y la pondrá a cero. El puntero será nil, la longitud y la capacidad serán 0. Obtendrás &[] — un puntero a un slice nil. ¿No se romperá al asignar por índice? No, fallará.
make([]int, 3) hace tres cosas:
- Asigna internamente un array real de 3 elementos (o más, si se especifica la capacidad).
- Inicializa la cabecera del slice (pointer, length, capacity).
- Devuelve un slice listo para usar.
Lo mismo con un map: make(map[string]int) crea una tabla hash y asigna memoria para los buckets. new(map[string]int) solo da un map nil.
¿Cuándo usar new() en 2025?
La práctica moderna en la comunidad Go es la siguiente: new() se usa raramente. La mayoría de las veces, los desarrolladores prefieren literales con toma de dirección: &Type{}. Esto es más legible y permite establecer campos de inmediato.