В 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.
Теги: