W Go struktury (struct) domyślnie są przekazywane i zwracane przez wartość. Oznacza to, że przy wywołaniu funkcji lub jej zwrocie następuje kopiowanie całej struktury. Dla małych struktur jest to przezroczyste, ale dla dużych — kwestia staje się krytyczna.
Początkowo Go był skoncentrowany na efektywnej pracy z małą liczbą alokacji. Jednak zagrożenie nieświadomego kopiowania dużych danych pojawiło się, gdy struktury miały wiele pól i zagnieżdżonych obiektów. Wydajność takich operacji może ucierpieć, a różnice czasami są widoczne tylko w profilowaniu lub w bólu GC.
Jeśli struktura ma dużą wielkość, jej kopiowanie przy każdym wywołaniu funkcji, zwrocie lub przypisaniu staje się kosztowne. Prowadzi to do:
Dla dużych struktur zaleca się przekazywanie i zwracanie wskaźnika do struktury (*T), a nie samego obiektu. Zmniejsza to koszty i zapewnia pracę z jedną instancją danych.
Przykład kodu:
package main import "fmt" type Large struct { Data [1024]int } // Przekazywanie przez wartość (niewłaściwe dla dużych obiektów) func ValueProcess(l Large) { l.Data[0] = 123 // zmieni tylko kopię } // Przekazywanie przez wskaźnik func PointerProcess(l *Large) { l.Data[0] = 456 // zmieni oryginał } func main() { a := Large{} ValueProcess(a) fmt.Println("Po ValueProcess:", a.Data[0]) // 0 PointerProcess(&a) fmt.Println("Po PointerProcess:", a.Data[0]) // 456 }
Kluczowe cechy:
1. Czy można zwrócić wskaźnik do lokalnej zmiennej struktury z funkcji w Go?
Tak. Go gwarantuje ważność takich wskaźników, automatycznie przenosząc te wartości, na które zwracany jest wskaźnik, do sterty (escape to heap).
func NewLarge() *Large { l := Large{} return &l }
2. Czy zmieni się oryginał, jeśli do funkcji przekażemy strukturę przez wartość i zmienimy pola wewnątrz?
Nie: zmieni się tylko kopia, a oryginał poza funkcją pozostanie taki sam.
3. Czy zawsze należy używać wskaźników dla struktur?
Nie. Dla małych (kilka pól) struktur przekazywanie przez wartość jest bezpieczne i często bardziej preferowane (immutable/value-semantic), oszczędzając na alokacjach i zmniejszając obciążenie GC.
W serwisie logowania każde zdarzenie było dużą strukturą i zwracane z funkcji przez wartość — każda zmiana kopiowała całą strukturę.
Zalety:
Wady:
Przeszliśmy do przekazywania i zwracania struktur przez wskaźnik, zmieniając dane przez sygnatury typu func(l *Large) i func() *Large.
Zalety:
Wady: