In Go, le strutture (struct) vengono passate e restituite per valore per impostazione predefinita. Questo significa che durante la chiamata di una funzione o il ritorno da essa viene eseguita una copia dell'intera struttura. Per strutture piccole questo è trasparente, ma per quelle grandi la questione è critica.
Inizialmente, Go era orientato a un'efficiente gestione di un numero ridotto di allocazioni. Tuttavia, il pericolo di copiare inconsapevolmente grandi dati è emerso quando le strutture utilizzano numerosi campi e oggetti annidati. Le prestazioni di tali operazioni possono risentirne e a volte la differenza si manifesta solo durante il profilo o quando si percepisce il carico del GC.
Se la struttura ha una grande dimensione, la sua copia ad ogni chiamata di funzione, ritorno o assegnazione diventa costosa. Questo porta a:
Per grandi strutture si raccomanda di passare e restituire un puntatore alla struttura (*T), invece dell'oggetto stesso. Questo riduce le spese generali e consente di lavorare con un'unica istanza di dati.
Esempio di codice:
package main import "fmt" type Large struct { Data [1024]int } // Passaggio per valore (errato per oggetti grandi) func ValueProcess(l Large) { l.Data[0] = 123 // cambierà solo la copia } // Passaggio per puntatore func PointerProcess(l *Large) { l.Data[0] = 456 // cambierà l'originale } func main() { a := Large{} ValueProcess(a) fmt.Println("Dopo ValueProcess:", a.Data[0]) // 0 PointerProcess(&a) fmt.Println("Dopo PointerProcess:", a.Data[0]) // 456 }
Caratteristiche chiave:
1. È possibile restituire un puntatore a una variabile locale di struttura da una funzione in Go?
Sì. Go garantisce la validità di tali puntatori, spostando automaticamente in heap quei valori a cui è restituito il puntatore (escape to heap).
func NewLarge() *Large { l := Large{} return &l }
2. Cambierà l'originale se viene passata una struttura per valore e vengono modificati i campi all'interno?
No: cambierà solo la copia, mentre l'originale rimarrà invariato al di fuori della funzione.
3. Bisogna sempre usare puntatori per le strutture?
No. Per strutture piccole (con pochi campi), il passaggio per valore è sicuro e spesso preferibile (immutable/value-semantic), risparmiando sulle allocazioni e riducendo il carico sul GC.
Nel servizio di logging, ogni evento era rappresentato come una grande struttura e restituito da funzioni per valore: ogni modifica copiava l'intera struttura.
Pro:
Contro:
Si è giunti al passaggio e alla restituzione di strutture per puntatore, modificando i dati attraverso le firme dei tipi func(l *Large) e func() *Large.
Pro:
Contro: