TGViewer
Daria’s room Daria’s room @dariasroom · 1.2K subscribers
Post #74 2.15K
Почему в Go интерфейсы требуют точного совпадения method set и не делают auto-addressing?

Вообще Go может автоматически преобразовывать значение типа к указателю на этот тип, (то есть неявно переопределять). Например, когда вызываешь pointer receiver метод на значении, Go неявно берет его адрес:

var m MyType

func (m *MyType) PointerMethod() {}

m.PointerMethod() // OK — Go превращает в (&m).PointerMethod()


Но… не в интерфейсах

type MyType int

func (m *MyType) PointerMethod() {}
func (m MyType) ValueMethod() {}

type PointerInterface interface {
PointerMethod()
ValueMethod()
}

var m MyType
var i PointerInterface

i = &m // OK: *MyType has both methods
i = m // Compile error: MyType has no PointerMethod


Мы все помним, что для присваивания интерфейсу какой либо значения, оно должно реализовывать все методы (method set) интерфейса.

Но, в отличии от стат. типов, мы не можем вызвать pointer-метод на значении в интерфейсе, а ограничиваемся правилами:
T - наше значение, которое присваиваем интерфейсу 

Method set = набор методов для конкретного значения

Method set у T → только методы с T-receiver
Method set у *T → все методы (T и *T-receiver)


В FAQ ответ о том, что отсутствие безопасного способа вытащить адрес переданного значения внутри интерфейса обусловлен спецификацией языка меня не совсем устроил, поэтому захотелось идти дальше:
If an interface value contains a value T, there is no safe way for a method call to obtain a pointer. (Doing so would allow a method to modify the contents of the value inside the interface, which is not permitted by the language specification.)


Получается, что в интерфейсах нельзя сделать неявное преобразование в виде auto-addressing, потому что при присваивании значения интерфейс копирует это значение куда то (—gcflags показал, как весь интерфейс уходит в heap, но я не ручаюсь за каждый случай), а указатель на откопированные данные кладет в поле data

Копия это immutable, то есть неизменяемая, так же как и неадресуемая (то есть мы не можем никак сделать &i.(MyType), так как получим ошибку Cannot take the address of 'i.(MyType)'

Представим, если бы мы могли взять адрес:

var m MyType
var i ValueInterface
i = m
vAddr := &i.(MyType) // допустим, если бы можно было взять адрес значения внутри интерфейса. Тогда мы получим два референсера на одни и те же данные

var newType MyType
i = newType // переопределяем интерфейс новым значением myType -> newType, копия на newType разместилась в другом участке памяти с другим адресом

fmt.Println(vAddr) // у нас остается указатель на старые данные из временного хранилища, а у интерфейса "i" новое значение newType, соответственно и данные будут другие


Теперь, имея два референсера на одни и те же данные, которые лежат где то во временной области памяти, мы получаем опасную ситуацию:
- Во первых: при каждом присваивании интерфейсу значения копия этого значения хранится уже в другом участке памяти. Поэтому иметь указатели на все эти копии было бы бессмысленно, так как интерфейс никак с ними не связан больше: при любом новом присваивании внутреннее значение будет перезаписано в новую область памяти, и старый адрес станет недействителен

- Во вторых: даже без переопределения, когда у нас есть другой референсер на данные помимо самого интерфейса, есть риск data invalidation. Поэтому интерфейс не предоставляет доступ к внутреннему адресу намеренно

Хороший пример и обьяснение также представлены в этой статье

Аналогично тому, как запрещено брать указатель на значения мап &myMap[key] из за эвакуации и смещения в памяти

Выводы такие:
- Интерфейс всегда копирует значение, которое ему присваивают
- Копия не адресуется и не изменяется
- Поэтому нет auto-addressing, и требуется строгое совпадение method set интерфейса и method set конкретного значения

#go
go.dev Go Wiki: MethodSets - The Go Programming Language
  • ❤ 27
  • 👍 4
More from @dariasroom
  1. Sep 15, 2026Автоматическое сжатие на клиенте vs ручное на сервере Стандартный клиент http.Transport са…
  2. Sep 5, 2026Причина перекосов в уровне балансировки L4-балансировщик выбирает серверную ноду при созда…
  3. Aug 28, 2026Итераторы… TL;DR: в iter.Seq итератор сам передаёт следующие элементы в код внутри range,…
  4. Aug 17, 2026Что на самом деле нужно сохранять при сериализации сложной структуры? TL;DR: Важно отделит…
  5. Aug 13, 2026Привет! Вас стало больше, так что пора наконец представиться 🙂 Я Даша, давно пишу на Go,…
  6. Aug 12, 2026HNSW: как устроен графовый индекс для векторного поиска One million years later, я наконец…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →