В Go структуры (struct) по умолчанию передаются и возвращаются по значению. Это значит, что при вызове функции или возврате из неё происходит копирование всей структуры. Для небольших структур это прозрачно, но для больших — вопрос критичен.
Первоначально Go был ориентирован на эффективную работу с малым числом аллокаций. Однако опасность неосознанного копирования больших данных появилась тогда, когда структуры используют множество полей и вложенные объекты. Производительность таких операций может страдать, и иногда разница выявляется только в профилировании или в боли GC.
Если структура имеет большой размер, её копирование при каждом вызове функции, возврате или присваивании оказывается накладным. Это приводит к:
Для больших структур рекомендуется передавать и возвращать указатель на структуру (*T), а не сам объект. Это снижает накладные расходы и обеспечивает работу с одним экземпляром данных.
Пример кода:
package main import "fmt" type Large struct { Data [1024]int } // Передача по значению (некорректно для больших объектов) func ValueProcess(l Large) { l.Data[0] = 123 // изменит только копию } // Передача по указателю func PointerProcess(l *Large) { l.Data[0] = 456 // изменит оригинал } func main() { a := Large{} ValueProcess(a) fmt.Println("After ValueProcess:", a.Data[0]) // 0 PointerProcess(&a) fmt.Println("After PointerProcess:", a.Data[0]) // 456 }
Ключевые особенности:
1. Можно ли вернуть указатель на локальную переменную структуры из функции в Go?
Да. Go гарантирует валидность таких указателей, автоматически перемещая в кучу те значения, на которые возвращён указатель (escape to heap).
func NewLarge() *Large { l := Large{} return &l }
2. Изменится ли оригинал, если в функцию передать структуру по значению и поменять поля внутри?
Нет: изменится только копия, а оригинал вне функции останется прежним.
3. Всегда ли использовать указатели для структур?</
Нет. Для небольших (несколько полей) структур передача по значению безопасна и часто предпочтительнее (immutable/value-semantic), экономя на аллокациях и снижая нагрузку на GC.
В сервисе логирования каждое событие представляло собой большую структуру и возвращалось из функций по значению — каждое изменение копировало структуру целиком.
Плюсы:
Минусы:
Пришли к передаче и возврату структур по указателю, изменяя данные через сигнатуры типа func(l *Large) и func() *Large.
Плюсы:
Минусы: