ПрограммированиеBackend разработчик

Чем отличаются передача по ссылке и по значению в Go? Почему этот момент критичен для структуры и срезов?

Проходите собеседования с ИИ помощником Hintsage

Ответ

В Go по умолчанию все аргументы функций передаются по значению: копируется значение переменной. Но некоторые типы (например, срезы, карты, каналы) являются «обертками» над внутренними структурами (указателями). Передача среза по значению копирует только дескриптор среза, а не данные; обе переменные ссылаются на один и тот же массив. В случае структур — копируется вся структура.

Если требуется избежать копирования и работать с оригинальной структурой, используют передачу по указателю (*Struct).

Пример:

type User struct { Name string Age int } func updateUser(u User) { u.Age = 30 // изменит только копию } func updateUserPtr(u *User) { u.Age = 30 // изменит оригинал } func main() { u := User{"Ivan", 25} updateUser(u) fmt.Println(u.Age) // 25 updateUserPtr(&u) fmt.Println(u.Age) // 30 }

Вопрос с подвохом

Являются ли изменения в слайсе, переданном в функцию, всегда видны снаружи функции?

Нет!

  • Если изменяется содержимое слайса (slice[i] = ...), оно видно снаружи.
  • Если изменяется сам слайс (например, slice = append(slice, ...)), а результат не возвращается из функции — новые элементы будут в локальной копии, и вы их потеряете.

Пример:

func addElem(s []int) { s = append(s, 100) } func main() { arr := []int{1,2,3} addElem(arr) fmt.Println(arr) // [1 2 3] — 100 не добавился }

Примеры реальных ошибок из-за незнания тонкостей темы


История

В одном из проектов дата структуры с большим полем struct (200+ байт) передавались по значению через каналы между горутинами, что вызывало огромные накладные расходы на копирование и потери производительности. После перехода на передачу по указателю latency уменьшилось на порядок.


История

В сервисе лога аудита разработчик передавал карту (map) между функциями без явного клонирования (copy). Изменения, внесённые одной функцией, неожиданно изменяли данные в другой части программы, вызывая путаницу в логе.


История

В функции динамического увеличения среза внутри функции забыли вернуть новый слайс обратно. В результате, изменения не были отражены в вызывающем коде, что привело к потери части транзакций. Было решено возвращать новый срез из функции.