Интерфейсы в Go

Теория: Указатели и интерфейсы

В Go интерфейс описывает поведение. Он задаёт набор методов, которые должен поддерживать тип. Не важно, как устроен этот тип внутри — главное, чтобы у него были нужные методы. Интерфейсы позволяют писать код, который не зависит от конкретной реализации. Если тип поддерживает нужный набор операций, он может быть использован в любом месте, где ожидается такой интерфейс.

Понимание интерфейсов невозможно без понимания того, как Go передаёт данные в функции. По умолчанию всё передаётся по значению — то есть копируется. Когда функция получает аргумент, она работает с его копией. Оригинальное значение остаётся снаружи и не меняется. Если нужно изменить исходные данные, вместо значения нужно передать указатель — ссылку на эти данные. Это касается и обычных функций, и методов структур. А значит, напрямую влияет на то, как именно тип реализует интерфейс и можно ли его передать в интерфейсную переменную.

Простой пример:

func change(x int) {
	x = 10
}

func main() {
	num := 5
	change(num)
	fmt.Println(num) // 5 — потому что x была копией
}

А теперь с указателем:

func change(x *int) {
	*x = 10
}

func main() {
	num := 5
	change(&num)
	fmt.Println(num) // 10 — потому что передали ссылку
}

Тот же принцип действует при работе со структурами. Если метод объявлен на значении, он получает копию. Если метод объявлен на указателе, он работает с исходным значением. Go не делает неявного преобразования T в *T, в том числе при передаче в интерфейс.

В обозначениях Go T — сам тип (например, User), а *T — указатель на него (*User). Это разные типы с разными наборами методов. Для интерфейсов важен не «где объявлен метод» сам по себе, а какого типа переменную вы передаёте: именно её набор методов сравнивается с интерфейсом. Если требуемый интерфейсный метод объявлен у *T, интерфейс реализует только *T; значение типа T интерфейс не реализует. Go не «догадывается» взять адрес — всё должно совпадать точно.

Пример:

type Runner interface {
	Run()
}

type Job struct{}

// Метод реализован у *Job
func (j *Job) Run() {
	fmt.Println("running")
}

Так работает:

var r Runner
r = &Job{}
r.Run() // running

А так — нет:

r = Job{} // ошибка компиляции: Job не реализует Runner

Если метод реализован у T, то интерфейс реализуют и T, и *T. Но если метод у *T, то только *T.

Если метод должен изменять поля структуры — он должен быть написан на указателе. В противном случае метод будет работать с копией, и все изменения пропадут после выхода из метода. Указатель позволяет работать с оригиналом.

Если структура небольшая и метод только читает поля, можно использовать значение. Это не даст побочных эффектов, и метод будет доступен и у T, и у *T.

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

Рассмотрим структуру Counter:

type Counter struct {
	Value int
}

Если метод реализован на значении:

func (c Counter) Inc() {
	c.Value++
}

То он не изменит оригинал:

c := Counter{}
c.Inc()
fmt.Println(c.Value) // 0 — потому что работали с копией

Чтобы изменения сохранялись, метод должен быть на указателе:

func (c *Counter) Inc() {
	c.Value++
}

Теперь работает:

c := Counter{}
c.Inc()
fmt.Println(c.Value) // 1 — потому что работали с оригиналом

Аналогичная логика с интерфейсом:

type Increaser interface {
	Inc()
}

var i Increaser
c := Counter{}

i = &c
i.Inc()
fmt.Println(c.Value) // 1

Передача i = c приведёт к ошибке компиляции, потому что метод реализован только у *Counter.

Go строго различает тип T и указатель *T. Это два разных типа. Если метод реализован у *T, то T интерфейс не реализует. Если метод реализован у T, то его можно вызывать и через значение, и через указатель. Изменять структуру можно только через методы на указателе. И если интерфейс требует такой метод — в него тоже нужно передавать указатель. Это правило определяет, сработает ли код или упадёт уже на этапе компиляции.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845