Skip to content

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

В следующих темах мы рассмотрим:

  • что такое интерфейс, полиморфизм и рефлексия;

  • Interface internal;

  • что такое блок управления потоком;

  • как упаковывать значения.

Что такое интерфейс

Интерфейс — самое интересное в Go, что отличает его от других языков программирования.

Интерфейсный тип в Go — это своего рода определение. Он определяет и описывает конкретные методы, которые должны быть у какого-то другого типа.

Тип интерфейса — это особый вид типов в Go. Интерфейсы играют несколько важных ролей в Go. Типы интерфейсов заставляют Go поддерживать упаковку значений, абстрагировать объект. Следовательно, благодаря упаковке значений поддерживаются рефлексия и полиморфизм.

Interface internal

Значения интерфейса сохраняются в виде двух структур eface и iface во время выполнения. Go отслеживает сопоставление структур и интерфейсов во внутреннем списке, называемом itabTable. Этот список создается как во время компиляции, так и во время выполнения, и будет использоваться для проверки того, реализует ли структура интерфейс.

Большинство из нас часто используют его в своем коде, но, похоже, не все понимают, как он работает внутри. В этой главе мы покопаемся в коде среды выполнения, чтобы увидеть, как она работает. Чтобы избежать сложностей, показанный код может скрывать некоторые детали от реального кода.

Самое интересное в интерфейсе в Go — это просто набор сигнатур методов. Или мы можем думать об этом как о наборе поведения, которое мы ожидаем от зависимости. Некоторые люди могут назвать это связью contract между двумя или более компонентами. Давайте посмотрим на fmt.Stringer интерфейс.

Чтобы структура вызывалась как implements интерфейс fmt.Stringer, ей нужно просто иметь ту же сигнатуру метода, что и интерфейс.

Это так просто! Структура реализует интерфейс только тогда, когда она определяет тот же набор сигнатур методов, что и интерфейс. Таким образом, интерфейс Go действительно похож на утиную типизацию.

«Если нечто выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка». Википедия

Отсутствие необходимости явного объявления во время компиляции делает интерфейс Go действительно мощным по сравнению с другими языками программирования, такими как Java. Java, как и php, требует явного ключевого слова implements в объявлении класса во время компиляции. Это означает, что тебе нужно импортировать библиотеку, которая определяет интерфейс и все его зависимости только для того, чтобы реализовать его.

С таким языком, как Java, компилятору и среде выполнения легко узнать, реализует ли класс интерфейс или нет, поскольку он имеет явное ключевое слово implements в классе и может легко построить таблицу сопоставления для проверки как во время компиляции, так и во время выполнения. Но Go не требует явного объявления, как он проверяет, реализует ли тип интерфейс? Это происходит во время компиляции? Или это происходит во время выполнения?

Ответ — оба варианта верны! Go проверяет преобразование типов и во время компиляции на наличие явных преобразований, и во время выполнения на наличие неявных преобразований.

Это означает, что приведенное ниже явное преобразование будет проверено во время компиляции.

Но для любого неявного преобразования, такого как следующий блок кода, проверка будет выполняться во время выполнения, когда вызывается этот конкретный блок кода.

Для проверки преобразования типа во время компиляции и во время выполнения информация будет храниться во внутреннем списке с именем itabTable. Этот список itabTable хранит информацию обо всех преобразованиях типов и утверждениях. Он организован и используется как кеш для ускорения производительности любых будущих преобразований типов и утверждений.

Чтобы понять itabTable, давай сначала рассмотрим систему типов во время выполнения.

Во время выполнения все поддерживаемые типы определены в runtime/type.go  и runtime/typekind.go. Эти типы охватывают все основные типы, а также типы структур и интерфейсов.

Во время выполнения значения интерфейса будут храниться в структуре eface при пустом интерфейсе или в iface, если у него есть методы. Структура содержит поле _type — информацию о типе значения, которое содержит интерфейс, и поле data, являющееся указателем на реальные данные. В случае iface, itab содержит информацию как об интерфейсе, так и о его целевом типе, а fun[0] указывает, реализует ли тип интерфейс или нет. fun[0] !\= 0 означает, что тип реализует интерфейс.

На рисунке ниже показано, как типы структурированы во время выполнения.

Таким образом, itab используется для хранения сопоставления между интерфейсом и структурой и содержит информацию о том, реализует ли структура интерфейс, через свое свойство fun[0].