Что такое интерфейс под капотом
Интерфейс в Go — это самая обычная структура. Внутри она выглядит так:
type iface struct {
// информация о конкретном типе
// и о том, как он реализует интерфейс
tab *itab
// указатель на само значение
data unsafe.Pointer
}Для пустого интерфейса interface{} (он же any) структура немного другая:
type eface struct {
typ *_type
data unsafe.Pointer
}Но смысл один: интерфейс хранит пару «тип + значение». Именно из-за этого возникает несколько неочевидных моментов, которые любят спрашивать на собеседовании.
Typed nil: почему err == nil бывает false
Один из самых частых вопросов на собеседовании:
type MyErr struct{}
func (*MyErr) Error() string {
return "boom"
}
func run() error {
var err *MyErr = nil
return err
}
fmt.Println(run() == nil)Что выведет? false.
Почему? Потому что error — это интерфейс, а внутри него уже лежит конкретный тип *MyErr. То есть значение nil, но тип есть:
type: *MyErr
data: nilА интерфейс равен nil только тогда, когда у него нет ни типа, ни значения. Это и есть разница между nil interface и typed nil.
Сравнение интерфейсов: компилируется, но паникует
var x any = []int{1, 2, 3}
fmt.Println(x == x)Код скомпилируется, но в runtime будет паника.
Почему? Сам интерфейс сравнивать можно, но внутри лежит слайс, а слайсы в Go не сравниваются. Компилятор видит any и пропускает код. А во время выполнения Go смотрит на реальный тип внутри и говорит: слайсы так сравнивать нельзя — и запускает панику. То есть any не отключает типизацию, а просто откладывает проверку на потом.
Method set: User{} или &User{}
type Store interface {
Save() error
}
type User struct{}
func (u *User) Save() error {
return nil
}
var _ Store = User{} // ?
var _ Store = &User{} // ?Первый вариант не скомпилируется, второй будет окей.
Почему? Потому что метод Save() объявлен на *User, а не на User. У *User этот метод есть, у User — нет. Поэтому логика интерфейсов чуть хитрее, чем просто «реализовать метод»: важно, на каком ресивере объявлен метод. Это и есть method set.
Ещё раз про typed nil
var s Sender
fmt.Println(s == nil)Результат — true: интерфейс пустой, нет ни типа, ни значения. А теперь так:
type EmailSender struct{}
func (e *EmailSender) Send(text string) error {
return nil
}
var sender *EmailSender = nil
var s Sender = sender
fmt.Println(s == nil)Результат — false. Снова та же история: значение внутри nil, но тип *EmailSender уже лежит в интерфейсе.
Что нужно уверенно понимать про интерфейсы
- чем nil interface отличается от typed nil;
- как работают value receiver и pointer receiver;
- что такое method set;
- почему
anyне отключает типизацию, а просто откладывает проверку на потом.