Структуры, указатели, методы и интерфейсы

Интерфейсы: определение, неявная реализация и type assertion

Содержание курса

В прошлом уроке мы дали Counter набор методов с pointer-receiver — Increment, Reset, Value. Теперь посмотрим, как именно этот набор превращается в нечто большее: тип автоматически начинает «удовлетворять» интерфейсу, не сообщая об этом явно ни компилятору, ни читателю кода.

Объявление интерфейса и неявная реализация через совпадение сигнатур

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

type Stringer interface {
    String() string
}

Вот и всё объявление. Компилятор читает его так: «любой тип, у которого есть метод String(), возвращающий string, удовлетворяет этому интерфейсу». Никакого implements Stringer, никакой регистрации — достаточно совпадения сигнатур.

Определим два типа:

type Dog struct{ Name string }
type Cat struct{ Name string }

func (d Dog) String() string { return "Dog: " + d.Name }
func (c Cat) String() string { return "Cat: " + c.Name }

Оба типа реализуют Stringer — компилятор проверяет это в момент присваивания или передачи в функцию. Если сигнатуры совпадают, всё работает; если нет — ошибка компиляции с точным указанием, какого метода не хватает.

Теперь важный нюанс, который мы уже встречали при разборе pointer-receiver. Посмотри на такой вариант:

type Counter struct{ n int }

func (c *Counter) String() string {
    return fmt.Sprintf("counter=%d", c.n)
}

Здесь метод объявлен на *Counter, а не на Counter. Значит, интерфейс Stringer реализует тип *Counter — именно указатель. Значение Counter (не указатель) этому интерфейсу не удовлетворяет:

var s Stringer

s = &Counter{}  // OK: *Counter реализует Stringer
s = Counter{}   // ошибка компиляции: Counter не реализует Stringer

Правило простое: набор методов T включает только те методы, у которых receiver — T. Набор методов *T включает методы и с receiver T, и с receiver *T. Поэтому *T всегда реализует как минимум то, что реализует T, но не наоборот.

Переменная интерфейсного типа хранит пару: конкретное значение и конкретный тип. Её нулевое значение — nil. Попытка вызвать метод на nil-интерфейсе приводит к панике, потому что нет ни значения, ни типа, по которому можно найти метод.