Материал проходит вычитку — возможны неточности.
Урок 39 из 118

Зачем указатели

Правка Ани прижилась: хендлеры отдавали записи по адресу, а не целиком. Позже понадобилось отличать «файла нет» от пустой записи, и указатель решил и эту задачу.

Главная мысль

указатели нужны для трёх вещей — изменить данные у вызывающего, не таскать большие копии и отличить «есть значение» от «нет значения».

Причина первая: в 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
<!-- wp:course-hub/quiz {"title":"Проверьте себя","questions":[{"text":"Что напечатает unsafe.Sizeof(BigData{}) и unsafe.Sizeof((*BigData)(nil)) для структуры с Items [1<
Итоги

Указатель — это про три вещи: изменить данные у вызывающего, не таскать большие копии и отличать «есть значение» от «нет значения».

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки