Структуры в Go
Теория: Указатели на структуры
В языке Go структуры часто используют для описания сущностей: пользователя, заказ, товар, сообщение. Но при работе с такими объектами нам важно не только хранить данные, но и управлять тем, как они передаются между функциями и сохраняются в памяти. Здесь нам помогают указатели.
Указатель — это переменная, которая хранит не само значение, а адрес этого значения в памяти. Когда программа работает с указателем, она получает доступ к тому же самому объекту, а не к его копии.
Зачем они нам нужны:
- Большие структуры. Если структура состоит из десятков или сотен полей, ее копирование при каждом вызове функции становится затратным по времени и памяти. Указатель позволяет передавать только адрес, работая с объектом напрямую.
- Изменение данных. Когда функция должна менять объект (например, обновить статус заказа или пересчитать баланс счета), удобнее передавать указатель. Так изменения отразятся на оригинальном объекте, а не на копии.
- Хранение в коллекциях. В срезах и словарях часто сохраняют именно указатели. Это экономит память и позволяет обновлять данные в одном месте, а использовать — в нескольких. Например, при загрузке профилей из базы данных ORM-библиотеки почти всегда возвращают указатели на структуры.
Указатели — это инструмент управления эффективностью и целостностью данных: с ними мы избегаем лишнего копирования и работаем с одним и тем же объектом в разных частях программы.
Специальный синтаксис работы с указателями
В Go указатели устроены проще, чем в C/C++: здесь нет арифметики указателей, нельзя «гулять» по памяти, все строго типизировано. Но есть несколько ключевых операторов, которые нам нужно знать.
-
&— взять адрес значения. Оператор&позволяет получить указатель на существующую переменную:Теперь
ptrне содержит сам заказ, а указывает на участок памяти, где он хранится. Такой прием используют, когда хотят:- передать объект в функцию без копирования,
- хранить объект в коллекции и работать с ним по ссылке,
- экономить ресурсы при работе с большими структурами.
-
*— разыменование (получение значения по адресу). Оператор*извлекает значение, на которое указывает указатель:Важно:
*ptr— это сама структураOrder, а(*ptr).Status— доступ к полю. -
Авторазыменование для структур. Go избавляет нас от лишнего синтаксиса: для структур можно писать проще. Вместо
(*ptr).Statusразрешено писатьptr.Status:Таким образом, запись через
*и без нее абсолютно эквивалентны. В реальных проектах всегда используют короткую формуptr.Field, потому что она чище и понятнее. -
Методы с указателями. Чтобы метод мог изменять структуру, его получатель должен быть указателем (*Type). Это стандартный паттерн, например, в моделях данных:
Применение:
Здесь order автоматически передается как указатель, даже если мы написали
order.MarkAsPaid(), а не(&order).MarkAsPaid(). Go делает это сам. -
Нулевой указатель (nil). Указатель может быть пустым, то есть не указывать никуда. По умолчанию любая переменная-указатель инициализируется nil:
Если попробовать обратиться к полю через
o.Status, будет паника (runtime error: invalid memory address). Такие ситуации часто используют как признак отсутствия данных: например, функция может вернуть*Orderилиnil, если заказ не найден.
Краткая шпаргалка:
&— взять адрес значения.*— получить значение по адресу.- Авторазыменование — обращаться к полям структуры через указатель можно без
*. - Методы с указателями позволяют менять объект.
nilозначает, что указатель пуст, работать с ним как с объектом нельзя.
Проблема без указателей
Представим, что мы пишем систему заказов. У нас есть структура Order:
Мы пишем функцию, которая меняет статус заказа на "paid":
Пробуем вызвать:
Результат — new. Почему? Потому что в функцию передалась копия структуры. Изменения произошли только внутри функции, а оригинал остался без изменений.
Решение с указателями
Исправим функцию:
Теперь вызываем так:
Мы передали адрес структуры, а внутри функции меняем данные по этому адресу.
Удобство Go: автоматическая разыменовка
Go делает работу с указателями удобной. Когда у нас есть указатель на структуру, можно обращаться к ее полям напрямую без явного (*o).Status. Компилятор сам понимает, что нужно разыменовать:
Важный нюанс: nil-указатели
Так как указатель может не указывать ни на что, возможна ситуация nil. Это часто используется, чтобы показать, что данных нет.
Если попробовать обратиться к order.Status, будет паника (runtime error: invalid memory address). Поэтому перед использованием указателей всегда нужно проверять, что они не nil, если это не гарантировано логикой программы.


