programowanieProgramista Backendowy

Czym różni się przekazywanie przez referencję i przez wartość w Go? Dlaczego ten moment jest krytyczny dla struktur i slice'ów?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

W Go domyślnie wszystkie argumenty funkcji są przekazywane przez wartość: kopiowana jest wartość zmiennej. Jednak niektóre typy (np. slice'y, mapy, kanały) są „opakowaniami” nad wewnętrznymi strukturami (wskaźnikami). Przekazywanie slice'a przez wartość kopiuje tylko deskriptor slice'a, a nie dane; obie zmienne odnoszą się do tej samej tablicy. W przypadku struktur — kopiowana jest cała struktura.

Jeśli należy uniknąć kopiowania i pracować z oryginalną strukturą, używa się przekazywania przez wskaźnik (*Struct).

Przykład:

type User struct { Name string Age int } func updateUser(u User) { u.Age = 30 // zmieni tylko kopię } func updateUserPtr(u *User) { u.Age = 30 // zmieni oryginał } func main() { u := User{"Ivan", 25} updateUser(u) fmt.Println(u.Age) // 25 updateUserPtr(&u) fmt.Println(u.Age) // 30 }

Pytanie z pułapką

Czy zmiany w slice'ie przekazanym do funkcji są zawsze widoczne poza funkcją?

Nie!

  • Jeśli zmienia się zawartość slice'a (slice[i] = ...), jest to widoczne na zewnątrz.
  • Jeśli zmienia się sam slice (np. slice = append(slice, ...)), a wynik nie jest zwracany z funkcji — nowe elementy będą w lokalnej kopii, a ty je stracisz.

Przykład:

func addElem(s []int) { s = append(s, 100) } func main() { arr := []int{1,2,3} addElem(arr) fmt.Println(arr) // [1 2 3] — 100 nie zostało dodane }

Przykłady rzeczywistych błędów wynikających z nieznajomości szczegółów tematu


Historia

W jednym z projektów struktury danych z dużym polem struct (200+ bajtów) były przekazywane przez wartość przez kanały między gorutinami, co powodowało ogromne koszty kopiowania i utraty wydajności. Po przejściu na przekazywanie przez wskaźnik latency zmniejszyło się o rząd wielkości.


Historia

W serwisie logów audytu programista przekazywał mapę (map) między funkcjami bez jawnego klonowania (copy). Zmiany wprowadzone przez jedną funkcję nieoczekiwanie zmieniały dane w innej części programu, co powodowało zamieszanie w logach.


Historia

W funkcji dynamicznego zwiększania slice'a wewnątrz funkcji zapomniano zwrócić nowy slice z powrotem. W rezultacie, zmiany nie były odzwierciedlone w kodzie wywołującym, co doprowadziło do utraty części transakcji. Postanowiono zwracać nowy slice z funkcji.