Зачем указатели
Правка Ани прижилась: хендлеры отдавали записи по адресу, а не целиком. Позже понадобилось отличать «файла нет» от пустой записи, и указатель решил и эту задачу.
указатели нужны для трёх вещей — изменить данные у вызывающего, не таскать большие копии и отличить «есть значение» от «нет значения».
Причина первая: в Go аргументы всегда передаются по значению. Когда ты вызываешь f(x), в функцию попадает копия x. Это верно для любого типа — числа, строки, структуры, слайса, указателя. Указатель не исключение: копируется сам адрес, а данные, на которые он смотрит, остаются на месте.
Копия против адреса
func inc(n int) { n++ }
func incPtr(n *int) { *n++ }
n := 1
inc(n)
fmt.Println(n) // 1 — копия, не изменилась
incPtr(&n)
fmt.Println(n) // 2 — прошли по адресу, изменили оригинал
incPtr получила свою собственную копию указателя, но эта копия смотрит на тот же n, поэтому запись через неё доходит до оригинала. Кстати, *n++ — это (*n)++: инкрементируется значение по адресу, а не адрес.
«значения может не быть»
Эту роль указателей редко проговаривают вслух, но для Go она одна из самых полезных:
func find(files map[string]FileMeta, name string) *FileMeta {
f, ok := files[name]
if !ok {
return nil
}
return &f
}
Если бы функция возвращала FileMeta, отсутствие записи пришлось бы выражать вторым значением или условным нулевым FileMeta{Name: ""} — ненадёжно, потому что пустое имя может быть легитимным. *FileMeta позволяет вернуть nil как честный ответ «такого файла нет». Вызывающий обязан это проверить, и это хорошо.
Адрес локальной переменной — безопасен
Функция возвращает &f, адрес локальной переменной. В C это была бы классическая висячая ссылка — переменная умерла бы вместе со стековым фреймом. В Go такого бага нет: компилятор видит, что адрес убегает из функции, и размещает f в куче, где её приберёт сборщик мусора. Это называется escape analysis — подробно в уроке, в модуле про память. Сейчас важно одно: возвращать адрес локальной переменной — нормально и безопасно.
Осталась третья причина — не таскать копии. Структуру на мегабайт неприятно гонять через аргументы и возвращаемые значения: каждый переход границы функции означает копию. Передаёшь указатель — копируются восемь байтов адреса (на 64-битных платформах; на 32-битных — четыре). Именно это сейчас тормозит «Полку»: FileMeta с метаданными и превью копируется на каждом шагу.
Вы узнаете:
- как выбрать value или pointer receiver для метода
- как изменить сам указатель у вызывающего через
**T
Указатель — это про три вещи: изменить данные у вызывающего, не таскать большие копии и отличать «есть значение» от «нет значения».
10 000 ₽ · 60 минут · онлайн