In Go, structures (struct) are passed and returned by value by default. This means that when a function is called or a value is returned, the entire structure is copied. For small structures, this is transparent, but for large ones, it becomes critical.
Initially, Go was geared towards efficient operation with a small number of allocations. However, the danger of unintentional copying of large data arose when structures use many fields and nested objects. The performance of such operations can suffer, and sometimes the difference is only revealed in profiling or through GC pain.
If a structure is large, copying it on every function call, return, or assignment becomes costly. This leads to:
For large structures, it is advisable to pass and return a pointer to the structure (*T) instead of the object itself. This reduces overhead and allows working with a single instance of the data.
Example code:
package main import "fmt" type Large struct { Data [1024]int } // Passing by value (incorrect for large objects) func ValueProcess(l Large) { l.Data[0] = 123 // will only change the copy } // Passing by pointer func PointerProcess(l *Large) { l.Data[0] = 456 // changes the original } func main() { a := Large{} ValueProcess(a) fmt.Println("After ValueProcess:", a.Data[0]) // 0 PointerProcess(&a) fmt.Println("After PointerProcess:", a.Data[0]) // 456 }
Key points:
1. Can you return a pointer to a local structure variable from a function in Go?
Yes. Go guarantees the validity of such pointers by automatically moving those values to the heap that the pointer returns (escape to heap).
func NewLarge() *Large { l := Large{} return &l }
2. Will the original change if a structure is passed to a function by value and the fields are modified inside?
No: only the copy will change, and the original outside the function will remain the same.
3. Should you always use pointers for structures?
No. For small (a few fields) structures, passing by value is safe and often preferable (immutable/value-semantic), saving on allocations and reducing the load on the GC.
In a logging service, each event represented a large structure and was returned from functions by value — every change copied the entire structure.
Pros:
Cons:
They switched to passing and returning structures by pointer, modifying data through signatures like func(l *Large) and func() *Large.
Pros:
Cons: