Почему я не могу использовать указатель на определенный тип, где ожидается *interface{}?

У меня есть следующая функция:

func bytesToData(data interface{}, b []byte) error {
    buf := bytes.NewBuffer(b)
    dec := gob.NewDecoder(buf)
    return dec.Decode(data)
}

Я использую это для получения структурных данных в и из Boltdb. Что я хотел бы сделать, так это изменить эту подпись на:

func bytesToData(data *interface{}, b []byte) error

И затем я хотел бы иметь возможность называть это так (b в этом случае - это Account в кодировке gob)

acc := &Account{}
err := bytesToData(acc, b)

Но когда я это делаю, я получаю сообщение об ошибке типа Cannot use *Account for type *interface{}.

На данный момент я только что изменил его обратно на interface{}. Но затем, если я передам Account или какой-либо другой тип напрямую, не делая его указателем, gob выдаст ошибку. Кажется, это должно быть проверено во время компиляции. А учитывая, что аргумент типа interface{} принимает что угодно, почему аргумент типа *interface{} не принимает указатель ни на что?


person bigblind    schedule 01.07.2017    source источник


Ответы (2)


Универсальность интерфейсных типов в Go не передается производным типам. Это относится к указателям (как вы заметили), а также к слайсам, каналам и т. д. Например, вы не можете присвоить []string []interface{}.

Есть разные способы объяснить это. Для программиста на Haskell:

В Go нет ковариантных или контравариантных типов. Все конструкторы типов (например, *, создающий тип указателя) являются инвариантными. Таким образом, хотя Account и *Account (и все другие типы) являются подтипами interface{}, ничто не является подтипом *interface{} или []interface{}. Иногда это неудобно, но система типов Go и правила присваиваемости становятся намного проще.

Для программиста C:

interface{} может содержать значение любого типа, но не содержит его напрямую. Это не магический контейнер переменного размера, а просто структура, состоящая из указателя на тип и указателя на значение. Когда вы назначаете конкретный тип interface{}, оба этих поля заполняются. *interface{} — это указатель на одну из этих структур. Когда вы пытаетесь присвоить *Account *interface{}, некуда поместить информацию о типе, потому что *interface{} — это одно машинное слово, которое просто содержит указатель. Поэтому компилятор не позволит вам это сделать.

person andybalholm    schedule 01.07.2017
comment
Любите крайности контраста Haskell/C. - person andy mango; 02.07.2017

interface{} также без проблем может содержать указатель. Но нет ничего лучше указателя на interface{}

Смотрите это:

Приведение указателя структуры к указателю интерфейса в Golang

Почему я не могу назначить *Struct *Interface?

person LucianoQ    schedule 01.07.2017