Вообще 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