Структуры и методы
Структуры и методы в Go — объявление структур, методы, value- и pointer-получатели, встраивание как композиция и идиома конструктора.
6 вопросов
JuniorТеорияОчень частоЧто такое struct в Go?
Что такое struct в Go?
struct — это составной тип, группирующий именованные поля, каждое со своим типом. Это значимый тип: присваивание или передача копирует все поля. struct поддерживает встраивание (композицию), методы и теги полей. Его нулевое значение имеет каждое поле в собственном нулевом значении, а две struct сравнимы через ==, когда все их поля сравнимы.
Типичные ошибки
- ✗Думать, что struct — ссылочный тип: присваивание или передача копирует все поля
- ✗Считать, что struct не может иметь методы или встраивать другие типы
- ✗Полагать, что любая struct сравнима через
==независимо от типов её полей
Уточняющие вопросы
- →Чем встраивание отличается от наследования в других языках?
- →Какие типы полей делают struct несравнимой и почему?
JuniorТеорияЧастоКак Go инициализирует struct без конструкторов?
Как Go инициализирует struct без конструкторов?
В Go нет конструкторов. Идиома — это обычная фабричная функция, по соглашению NewT(...), которая принимает входные данные, проверяет их и возвращает инициализированный *T или T. Это рядовая функция, а не особый синтаксис — ничто не заставляет вызывающих её использовать. Часто нулевое значение struct уже пригодно к работе, так что конструктор вообще не нужен; NewT пишут лишь тогда, когда при создании надо проверить входы или задать ненулевые умолчания.
Типичные ошибки
- ✗Считать, что Go автоматически запускает метод-конструктор
New/Init - ✗Думать, что нулевое значение никогда не пригодно и всегда нужна фабрика
- ✗Полагать, что вызывающих заставляют использовать
NewT, а не голый литерал
Уточняющие вопросы
- →Когда нулевое значение struct достаточно пригодно, чтобы обойтись без конструктора?
- →Почему фабрика
NewTвозвращает*T, а неT?
JuniorТеорияЧастоЧто делает встраивание struct в Go, и это ли наследование?
Что делает встраивание struct в Go, и это ли наследование?
Встраивание помещает тип как безымянное поле, например struct{ A }, что продвигает поля и методы A на внешнюю struct, так что к ним можно обращаться напрямую. Это композиция, А НЕ наследование: нет полиморфизма и нет виртуальной диспетчеризации — внешний тип не является подтипом A. Собственный член с тем же именем затеняет встроенный, а до встроенного значения всё ещё можно добраться явно через имя его типа, например outer.A.
Типичные ошибки
- ✗Называть встраивание наследованием и ждать полиморфизма или виртуальной диспетчеризации
- ✗Считать внешний тип подтипом, присваиваемым там, где ждут встроенный тип
- ✗Забывать, что собственный член затеняет продвинутый встроенный
Уточняющие вопросы
- →Как всё же добраться до встроенного поля после того, как его затенило собственное?
- →Если два встроенных типа продвигают один и тот же метод, что произойдёт?
JuniorТеорияЧастоЧто такое метод в Go и чем он отличается от функции?
Что такое метод в Go и чем он отличается от функции?
Метод — это функция с дополнительным параметром-получателем, записанным перед именем, например func (c Counter) Inc(). Получатель привязывает метод к именованному типу — обычно к struct — объявленному в том же пакете, и вызывают его как c.Inc(). У обычной функции получателя нет, и её вызывают по голому имени. То есть метод — это просто функция, чей первый аргумент и есть получатель, но с более удобным синтаксисом вызова и набором методов на типе.
Типичные ошибки
- ✗Думать, что тело метода лежит внутри скобок struct, как в классе
- ✗Считать, что можно прицепить метод к типу из другого пакета
- ✗Забывать, что метод — это функция с получателем в роли первого аргумента
Уточняющие вопросы
- →Почему тип получателя метода должен быть объявлен в том же пакете?
- →Как получатель позволяет вызывать метод как
c.Inc(), а неInc(c)?
MiddleТеорияЧастоКогда метод должен использовать pointer-получатель вместо value-получателя?
Когда метод должен использовать pointer-получатель вместо value-получателя?
Используйте pointer-получатель, когда метод должен изменять получателя, ведь value-получатель получает лишь копию, и изменение теряется. Используйте его и когда struct большая, чтобы не копировать каждое поле при каждом вызове, или ради единообразия, когда часть методов типа уже требует указателя — смешивать виды получателей не рекомендуется. Value-получатель подходит для маленьких неизменяемых типов, где копия дёшева и безопасна, и оставляет значение пригодным при конкурентном чтении.
Типичные ошибки
- ✗Брать value-получатель для метода, который должен изменять получателя
- ✗Копировать большую struct при каждом вызове вместо взятия указателя
- ✗Непоследовательно смешивать value- и pointer-получатели на одном типе
Уточняющие вопросы
- →Почему смешивать pointer- и value-получатели на одном типе не рекомендуют?
- →Как выбор получателя влияет на то, какой набор методов удовлетворяет интерфейсу?
JuniorКодИногдаПочему мутация в этом методе не сохраняется, и как это исправить?
Почему мутация в этом методе не сохраняется, и как это исправить?
Печатает foo. Вызов метода — это сахар для передачи получателя аргументом, а у Set значимый получатель, поэтому b — копия: b.Value = s меняет копию, а оригинал не трогается. Даже передача &bs всё равно напечатает foo, ведь Set копирует значение в любом случае. Исправление: сделать Set с указательным получателем, func (b *BasicStruct) Set(s string), и передавать &bs.
Типичные ошибки
- ✗Думать, что значимый получатель меняет структуру вызывающего, а не копию
- ✗Считать, что одна лишь передача
&bsчинит это без смены получателя - ✗Полагать, что код не компилируется, а не просто печатает неизменённое значение
Уточняющие вопросы
- →Почему метод со значимым получателем входит в набор методов и
T, и*T? - →После смены
Setна указательный получатель, почему надо передавать&bs, а неbs?