Что из себя представляет переменная с типом интерфейса в памяти Go
Что проверяют
Знаете ли вы, что переменная интерфейсного типа это не указатель на объект, а пара значений, и понимаете ли следствия: ловушку с nil, лишние размещения в куче, цену вызова метода.
Как отвечать
Интерфейсная переменная занимает два машинных слова. У непустого интерфейса первое слово это указатель на itab, где лежат тип интерфейса, динамический тип и таблица методов, второе это указатель на данные. У пустого интерфейса any вместо itab указатель на дескриптор типа, такая пара называется eface.
Данные всегда лежат по указателю. При укладывании значения в интерфейс оно копируется, и если это не указатель, копия обычно уезжает в кучу. Поэтому упаковка в интерфейс регулярно всплывает в профиле аллокаций.
var err error // оба слова пустые, err == nil
var p *MyErr // указатель нулевой
err = p // тип записан, данные нулевые
fmt.Println(err == nil) // falseОтсюда правило: интерфейс равен nil, только когда пусты оба слова. Вызов метода через интерфейс это косвенный вызов по таблице, без проверки типа в момент вызова; проверка происходит в приведении v, ok := x.(T). Дороже всего не сам переход, а потерянный инлайн и уехавшая в кучу переменная.
Где спотыкаются
Попадают в ловушку с нулевым указателем: функция объявляет результат как *MyError, возвращает его в error, и проверка на nil у вызывающего срабатывает всегда. Объявлять возвращаемый тип надо как error и в успешной ветке возвращать явный nil.
Говорят, что при каждом вызове метода через интерфейс идёт проверка типа. Её нет: таблица методов собирается заранее, в рантайме остаётся разыменование и переход по адресу.
Считают, что интерфейс хранит ссылку на исходную переменную. При укладывании структуры копируется значение, поэтому изменения оригинала через интерфейс не видны, а методы с указателем в получателе у такой копии вообще недоступны, и тип не считается реализующим интерфейс.