Menu

Punteros en Golang: & y *, new, nil y cuándo usarlos

Un puntero contiene la dirección de un valor. Aprende & y *, new, punteros a structs, por qué puedes devolver sin peligro un puntero a una variable local, cuándo usar punteros y el panic por desreferencia de un puntero nil.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

& y *

Un puntero contiene la dirección de memoria de un valor. &x te da un puntero a x. *p sigue el puntero hasta el valor.

El asterisco tiene dos papeles. En un tipo (*int) significa "puntero a int". En una expresión (*p) significa "el valor al que apunta p". Imprimir p directamente muestra una dirección como 0xc000012345, que cambia entre ejecuciones.

Por qué existen los punteros: compartir en lugar de copiar

Go lo pasa todo por valor. Una función recibe una copia de cada argumento, así que no puede cambiar la variable de quien la llama. Pasar un puntero copia la dirección, y los dos lados llegan al mismo valor.

Go no tiene referencias en el sentido de C++. "Paso por referencia" en Go significa pasar un puntero por valor.

Punteros a structs

Los punteros a structs son los más comunes en el código Go. El acceso a campos desreferencia automáticamente: p.Name significa (*p).Name.

Los métodos siguen la misma lógica: un receptor puntero permite al método cambiar el struct. La página de métodos explica la elección entre receptor por valor y por puntero.

new

new(T) reserva un T a cero y devuelve su dirección. Es lo mismo que declarar una variable y tomar su dirección.

p := new(int) // *int pointing at 0
var x int
q := &x           // same thing, two lines
a := new(Account) // same as &Account{}

En la práctica, &T{...} es más común para los structs porque permite asignar campos en la misma expresión. new viene bien para punteros a tipos básicos. No lo confundas con make, que construye slices, maps y channels y los devuelve listos para usar, no como punteros.

Devolver un puntero a una variable local es seguro

En C, devolver la dirección de una variable local es un bug. En Go es código normal.

El análisis de escape del compilador decide si un valor vive en la pila o en el heap. Tú nunca lo eliges, y nunca liberas memoria: el recolector de basura la recupera cuando nada apunta a ella. go build -gcflags=-m imprime esas decisiones si tienes curiosidad.

Punteros nil

El valor cero de cualquier tipo puntero es nil. Desreferenciar nil provoca un panic.

Salida:

1
2
true
recovered: runtime error: invalid memory address or nil pointer dereference

Sin el recover, el programa caería con ese mensaje y una traza de la pila que empieza con panic: runtime error: invalid memory address or nil pointer dereference y [signal SIGSEGV: segmentation violation ...]. Los orígenes habituales:

  • una variable puntero o un campo de struct que nunca se asignó
  • m[key] sobre un map[string]*T con una clave que no existe, que devuelve nil
  • ignorar un error: f, _ := os.Open(path) deja f a nil si falla
  • un método con receptor puntero llamado sobre un puntero nil, que luego lee un campo

Llamar a un método sobre un puntero nil es legal en sí. El panic ocurre solo cuando el método toca los campos del receptor.

Cuándo usar un puntero

Usa un puntero cuando:

  • una función o un método tiene que cambiar el valor de quien llama
  • el valor es un struct grande y se pasa de un lado a otro a menudo
  • el tipo no debe copiarse (contiene un sync.Mutex, o representa un recurso único como una conexión)
  • necesitas expresar "sin valor" con nil, por ejemplo un campo opcional en un struct JSON (*int distingue "ausente" de 0)

No uses un puntero cuando:

  • el valor es pequeño y de solo lectura (time.Time, un Point)
  • el tipo ya se comporta como una referencia: los slices, maps, channels, funciones e interfaces rara vez necesitan un * delante. Un *[]int o un *map[string]int es casi siempre un error

Los punteros no son automáticamente más rápidos. Copiar un struct pequeño es barato, y un puntero puede forzar una reserva en el heap y añadir trabajo al recolector de basura.

Punteros y variables de bucle

Tomar la dirección de una variable de bucle es seguro desde Go 1.22, porque cada iteración tiene su propia variable:

Esto imprime 0 1 2. Antes de Go 1.22 (que también introdujo range 3), el mismo bucle escrito como for i := 0; i < 3; i++ imprimía 3 3 3, porque los tres punteros compartían una sola variable. Ten en cuenta que &v en for _, v := range items apunta a una copia del elemento, no al elemento del slice. Para obtener un puntero al propio elemento, usa &items[i].

Errores comunes

  • Desreferenciar sin comprobar. Comprueba if p == nil donde nil sea posible.
  • Puntero a un valor de range. &v apunta a una copia; usa &s[i].
  • Punteros a slices y maps. Rara vez hacen falta. Devuelve el slice nuevo.
  • Guardar &s[i] a través de un append. Si append reserva un array nuevo, el puntero sigue apuntando al array antiguo y las escrituras posteriores no se ven en ningún sitio.

Preguntas frecuentes

¿Qué significan & y * en Go?

&x toma la dirección de x y da un puntero de tipo *T. *p desreferencia el puntero: lee o escribe el valor al que apunta. En un tipo, *T significa "puntero a T".

¿Qué diferencia hay entre new y make en Go?

new(T) reserva un T a cero y devuelve un *T; funciona con cualquier tipo. make solo funciona con slices, maps y channels, y devuelve un valor inicializado (no a cero, no un puntero) de ese tipo: make(map[string]int) está listo para usar, mientras que new(map[string]int) es un puntero a un map nil.

¿Puedo devolver un puntero a una variable local en Go?

Sí. A diferencia de C, es seguro. El análisis de escape del compilador ve que la variable sobrevive a la función y la reserva en el heap. func newInt() *int { x := 5; return &x } es Go correcto.

¿Qué provoca "invalid memory address or nil pointer dereference" en Go?

Leer o escribir a través de un puntero nil: una variable *T que nunca se asignó, la búsqueda de una clave que no existe en un map[K]*V, o una función que devolvió nil junto con un error que no comprobaste. Comprueba nil, o inicializa el puntero antes de usarlo.

¿Tiene Go aritmética de punteros?

No. No puedes sumar a un puntero ni indexar memoria a través de él. El paquete unsafe lo permite para código de bajo nivel, sin ninguna garantía de seguridad. El código Go normal usa slices para la memoria contigua.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR