Go: проверка соответствия интерфейсу при компиляции

• обновлено • 3 мин чтения

В Go тип реализует интерфейс неявно. Для интерфейсов, описывающих только методы, достаточно иметь нужные методы с подходящими сигнатурами. Если хочется явно зафиксировать, что *PgStorage должен реализовывать store.UserCreator, можно написать:

var _ store.UserCreator = (*PgStorage)(nil)

Если тип перестанет соответствовать интерфейсу, пакет не скомпилируется. Такой приём описан в Effective Go.

Предположим, в пакете store определены интерфейсы для работы с пользователями:

package store

type User struct {
	ID string
}

type UserCreator interface {
	CreateUser(user User) error
}

type UserModifier interface {
	ModifyUser(userID string, updatedUser User) error
}

type UserBlocker interface {
	BlockUser(userID string, block bool) error
}

В пакете pgstore уже есть тип PgStorage с реализацией этих методов. Рядом с ним добавим проверки. Здесь example.com/app это условный путь модуля, который нужно заменить на свой:

package pgstore

import "example.com/app/store"

var (
	_ store.UserCreator  = (*PgStorage)(nil)
	_ store.UserModifier = (*PgStorage)(nil)
	_ store.UserBlocker  = (*PgStorage)(nil)
)

Левая часть задаёт тип интерфейса, а правая содержит нулевой указатель типа *PgStorage. Компилятор проверяет допустимость присваивания. Идентификатор _ отбрасывает значение: переменная для дальнейшего использования не создаётся. Выражение (*PgStorage)(nil) не создаёт объект хранилища и не вызывает его методы.

Проверяется именно *PgStorage. Если метод объявлен с получателем *PgStorage, он входит в набор методов указателя, но не самого значения PgStorage. Поэтому следующая проверка завершится ошибкой, если CreateUser имеет получатель *PgStorage:

var _ store.UserCreator = PgStorage{} // ошибка: CreateUser имеет pointer receiver

Если же все необходимые методы объявлены с получателем PgStorage, интерфейсу удовлетворяют и PgStorage, и *PgStorage. Это следует из правил о наборах методов.

Просто var _ store.UserCreator = nil ничего не проверит относительно PgStorage: справа нет этого типа. А nil в исходной записи не означает, что интерфейс с таким значением будет равен nil. Подробнее об этой ловушке и выборе места для интерфейса я писал в статье «Интерфейсы в Go: паттерны и антипаттерны из продакшена».

Явная проверка полезна, когда пакет обещает реализацию стороннего интерфейса, но внутри пакета нет места, где конкретный тип присваивается этому интерфейсу. Если же *PgStorage уже передаётся в функцию, принимающую store.UserCreator, компилятор проверяет соответствие и без дополнительной строки. В Effective Go рекомендуют добавлять такие объявления, когда обычных статических проверок в коде ещё нет.

Проверки можно держать рядом с типом или в отдельном файле interfaces.go того же пакета. В обычном .go-файле они срабатывают при сборке пакета, а в _test.go только при сборке тестов. Они проверяют сигнатуры методов; поведение реализации нужно проверять тестами.

По состоянию на сентябрь 2026 года приём остаётся актуальным. В Go 1.27 появились методы с собственными параметрами типов, но методы интерфейсов по-прежнему не могут объявлять такие параметры. Метод с собственными параметрами типов также не может реализовать метод интерфейса. Это отличается от обычного метода обобщённого типа: например, метод Get() T у Box[T] после подстановки T = int может удовлетворять интерфейсу с методом Get() int. Подробнее это различие разобрано в официальной статье о generic methods.


Об авторе: Александр Бруяко — руководитель разработки с опытом в backend, инфраструктуре и управлении командами.


Теги: