Указатели
Указатели в Go — операторы `&` и `*`, семантика передачи по значению против ссылки, нулевой указатель и когда указатель действительно нужен.
4 вопросов
JuniorТеорияОчень частоПередаёт ли Go аргументы функции по значению или по ссылке?
Передаёт ли Go аргументы функции по значению или по ссылке?
Go всегда передаёт аргументы по значению — функция получает копию, поэтому запись в параметр никогда не затрагивает переменную вызывающего. Чтобы изменить переменную вызывающего, передайте указатель *T и пишите через него. Срез, map или канал тоже копируются, но скопированный заголовок по-прежнему делит тот же базовый массив или таблицу, поэтому правки элементов видны вызывающему.
Типичные ошибки
- ✗Называть срезы и map «передачей по ссылке» вместо копий заголовка над общим хранилищем
- ✗Ожидать, что запись в поле параметра-значения дойдёт до вызывающего
- ✗Забывать, что для мутации нужен явно переданный указатель
*T
Уточняющие вопросы
- →Почему append к срезу внутри функции иногда не виден снаружи?
- →Чем метод с указательным получателем отличается здесь от метода со значением?
JuniorТеорияЧастоЧто делают операторы & и * с указателями в Go?
Что делают операторы & и * с указателями в Go?
&x берёт адрес x и возвращает указатель типа *T. *p разыменовывает указатель, чтобы прочитать или записать значение, на которое он указывает. Нулевое значение указателя — nil, то есть он пока ни на что не указывает. В Go нет арифметики указателей — нельзя прибавлять или вычитать из указателя так, как позволяет C.
Типичные ошибки
- ✗Путать операторы — думать, что
&разыменовывает, а*берёт адрес - ✗Ожидать, что арифметика указателей в стиле C работает с указателем Go
- ✗Забывать, что нулевое значение указателя —
nil, а не нулевой адрес
Уточняющие вопросы
- →Чем
*Tв типе отличается от*pв выражении? - →Почему Go намеренно не включает арифметику указателей?
JuniorТеорияЧастоЧто происходит при разыменовании nil-указателя в Go?
Что происходит при разыменовании nil-указателя в Go?
Разыменование nil-указателя вызывает runtime-панику: invalid memory address or nil pointer dereference. nil — нулевое значение любого указателя, поэтому неприсвоенный указатель хранит nil, пока вы не направите его куда-либо. Защищайтесь через if p != nil перед чтением *p или перехватывайте панику на границе. Тихого чтения нуля, как может дать C, здесь нет.
Типичные ошибки
- ✗Ожидать тихого чтения нулевого значения вместо паники
- ✗Думать, что разыменование nil ловится на этапе компиляции
- ✗Пропускать защиту
if p != nilперед чтением*p
Уточняющие вопросы
- →Как
recoverможет превратить панику nil-указателя в обработанную ошибку? - →Почему nil-указатель конкретного типа может лежать внутри ненулевого интерфейса?
MiddleТеорияЧастоКогда функция должна принимать указатель, а не значение в Go?
Когда функция должна принимать указатель, а не значение в Go?
Берите *T, когда функция должна изменить значение вызывающего или чтобы избежать копирования большой структуры при каждом вызове. Для маленьких неизменяемых данных предпочитайте значение — это держит API безопасным по копии, так что вызываемый не может изменить состояние вызывающего. Передача указателя также позволяет вызываемому видеть nil как «отсутствует», чего параметр-значение выразить не может.
Типичные ошибки
- ✗Брать указатели по умолчанию везде и терять безопасность копии у параметров-значений
- ✗Считать, что параметр-значение может изменить вызывающего без указателя
- ✗Упускать, что только параметр-указатель может нести
nilкак «отсутствует»
Уточняющие вопросы
- →Как этот принцип переносится на выбор получателя метода — значение против указателя?
- →При каком размере структуры стоимость копии начинает реально иметь значение на практике?