Go: Интерфейсы

25 вопросов

1 Что такое интерфейс в Go?

Интерфейс - именованный набор сигнатур методов. Тип реализует интерфейс неявно (не нужно объявлять implements): если у типа есть все методы интерфейса, он ему удовлетворяет. Переменная типа интерфейса может хранить значение любого конкретного типа с этими методами.

Открыть отдельно →
2 Как устроены iface и eface внутри?

iface - интерфейс с методами: указатель на таблицу методов (itable) и указатель на данные. eface - пустой интерфейс interface{}: указатель на тип и указатель на данные. itable связывает конкретный тип с таблицей методов интерфейса.

Открыть отдельно →
3 Что такое пустой интерфейс (empty interface)?

interface{} или any (Go 1.18+) - интерфейс без методов. Ему удовлетворяет любой тип. Используется для универсальных контейнеров и функций, принимающих любое значение. Для извлечения типа нужна type assertion или type switch.

Открыть отдельно →
4 Когда интерфейс равен nil?

Интерфейс равен nil только если и тип, и значение внутри nil (и указатель на тип, и указатель на данные нулевые). Если в интерфейс записан ненулевой тип и nil значение (например, *MyType(nil)), интерфейс не nil - сравнение if err != nil может дать true для "логически" nil ошибки.

Открыть отдельно →
5 В чём подвох nil в интерфейсах?

Переменная интерфейса хранит (тип, значение). Если присвоить указатель nil конкретного типа, в интерфейсе будет (тип, nil). if x == nil тогда false - интерфейс не nil. Для ошибок это типично: возвращать return err где err имеет тип, но значение nil. Решение: возвращать явно nil или проверять через errors.Is.

Открыть отдельно →
6 Что такое type assertion и type switch?

Type assertion: v := x.(T) - извлечь значение типа T из интерфейса; при неудаче паника. Безопасная форма: v, ok := x.(T). Type switch: switch x.(type) { case T1: ... case T2: ... } - ветвление по типу значения в интерфейсе.

Открыть отдельно →
7 Где лучше определять интерфейс - у производителя или потребителя?

В Go принято определять интерфейс со стороны потребителя: маленький интерфейс с нужными методами в пакете, который использует зависимость. Так зависимость не импортирует "чужой" пакет, и интерфейс остаётся минимальным. Крупные интерфейсы в пакете с реализацией - реже.

Открыть отдельно →
8 Проверяется ли реализация интерфейса на этапе компиляции?

Да. Присваивание значения конкретного типа переменной типа интерфейса компилируется только если тип реализует все методы интерфейса. Явной декларации "implements" нет - компилятор проверяет наличие методов. Невыполнение контракта даёт ошибку компиляции.

Открыть отдельно →
9 Чем интерфейсы в Go отличаются от Java/C#?

В Go не нужно объявлять, что тип реализует интерфейс (no implements). Интерфейс определяет только методы, не поля. Любой тип может удовлетворить интерфейс без наследования. Интерфейсы обычно маленькие (1-3 метода). Структуры могут удовлетворять нескольким интерфейсам без явной иерархии.

Открыть отдельно →
10 Что такое duck typing в контексте Go?

"Если ходит как утка и крякает - это утка". В Go это неявная реализация интерфейса: тип не объявляет интерфейс, но если у него есть нужные методы - он подходит. Компилятор проверяет это статически. Часто говорят "structural typing" для интерфейсов.

Открыть отдельно →
11 Как преобразовать в пустой интерфейс и обратно?

В пустой: любое значение присваивается var x any = value. Обратно: type assertion v := x.(ConcreteType) или v, ok := x.(ConcreteType). Для проверки типа - type switch. При неверном утверждении типа - паника (без ok) или ok == false.

Открыть отдельно →
12 Что такое comparable в контексте интерфейсов?

Встроенное ограничение comparable (Go 1.18+) - типы, которые можно сравнивать с ==/!=. Используется в дженериках: func [K comparable, V any]. Интерфейсы сравниваемы: сравниваются тип и значение внутри. Слайсы, map, функции - не comparable.

Открыть отдельно →
13 interface{} и any - в чём разница?

С Go 1.18 any - встроенный алиас для interface{}. Семантически одно и то же. any короче и читается как "любой тип". Рекомендуется использовать any в новом коде.

Открыть отдельно →
14 Как получить конкретный тип из интерфейса?

Type assertion: v := x.(*MyStruct) или v, ok := x.(*MyStruct). Type switch: switch v := x.(type) { case *MyStruct: ... case int: ... }. Для ошибок - errors.As для извлечения типа ошибки из цепочки.

Открыть отдельно →
15 Пустой интерфейс и интерфейс с методами - когда что?

Пустой интерфейс - когда нужна произвольная величина (сериализация, логирование, контейнеры). Интерфейс с методами - когда важен контракт (чтение/запись, сравнение, сортировка). Предпочтительно маленькие интерфейсы с нужными методами вместо any.

Открыть отдельно →
16 Зачем нужны интерфейсы в Go?

Абстракция без наследования: подмена реализаций (тесты, моки), dependency inversion. Один тип может удовлетворять разным интерфейсам. Полиморфизм: функция принимает интерфейс и работает с любым типом с нужными методами. Меньшая связность кода.

Открыть отдельно →
17 Что такое io.Reader и io.Writer?

io.Reader - интерфейс с методом Read(p []byte) (n int, err error). io.Writer - Write(p []byte) (n int, err error). Базовые интерфейсы для ввода-вывода: файлы, сеть, буферы реализуют их. Композиция через io.Reader/Writer позволяет универсально обрабатывать потоки.

Открыть отдельно →
18 Что такое sort.Interface?

Интерфейс для сортировки: Len() int, Less(i, j int) bool, Swap(i, j int). Тип с этими методами можно передать в sort.Sort. Для слайсов чаще используют sort.Slice с функцией сравнения, не реализуя полный интерфейс.

Открыть отдельно →
19 Что такое method set для интерфейсов?

Method set типа для интерфейса - набор методов, которые должны быть у типа, чтобы он удовлетворял интерфейсу. У интерфейса тоже есть method set - перечень методов. Тип реализует интерфейс, если его method set содержит метод set интерфейса (для указателей учитываются методы с pointer receiver).

Открыть отдельно →
20 Как добавить метод к чужому типу?

Определить свой тип-обёртку (или алиас до 1.9) и объявить для него методы. Либо обёртка-структура: type MyInt int; func (m MyInt) Foo() {}. К чужому типу из другого пакета методы добавить нельзя - только свой тип на его основе.

Открыть отдельно →
21 Что такое String() и stringer?

Интерфейс fmt.Stringer с методом String() string. Если тип реализует String(), fmt.Print, fmt.Sprintf("%v", x) используют его для строкового представления. "Stringer" - тип, реализующий этот интерфейс.

Открыть отдельно →
22 Почему err != nil не срабатывает для логически nil ошибки?

Переменная ошибки может иметь тип (например, *MyError), но значение nil. Тогда интерфейс (тип, значение) не nil, и err != nil истинно. Нужно возвращать return nil вместо return err когда err nil, либо проверять через errors.Is(err, nil) или по типу errors.As.

Открыть отдельно →
23 Что такое встраивание интерфейсов (embedding)?

Интерфейс может включать другой интерфейс по имени (встраивание): type ReadWriter interface { Reader; Writer }. Результирующий интерфейс требует все методы встроенных. Дублирование методов не допускается. Удобно для композиции контрактов.

Открыть отдельно →
24 Могут ли быть поля в интерфейсе?

Нет. В интерфейсе только методы. Поля задаются в структурах. Если нужны и "поля", и поведение - интерфейс с методами (в том числе геттеры), а данные в конкретном типе.

Открыть отдельно →
25 Что такое боксинг при присваивании в интерфейс?

При присваивании значения конкретного типа переменной типа интерфейса значение копируется в heap (если не указатель), а в интерфейс кладётся пара (тип, указатель на это значение или само значение для малых). Боксинг - упаковка значения в интерфейс, возможное выделение памяти. Указатели в интерфейсе не боксят сам объект - копируется указатель.

Открыть отдельно →
🧠Квиз 🏆Лидеры 🎯Собесед. 📖Вопросы 📚База зн.