Ответ 1
Эта ошибка времени компиляции возникает, когда вы пытаетесь назначить или передать (или преобразовать) конкретный тип в тип интерфейса; и сам тип не реализует интерфейс, только указатель на тип.
Давайте посмотрим на пример:
type Stringer interface {
String() string
}
type MyType struct {
value string
}
func (m *MyType) String() string { return m.value }
Тип интерфейса Stringer
имеет только один метод: String()
. Любое значение, которое хранится в значении интерфейса Stringer
должно иметь этот метод. Мы также создали MyType
и создали метод MyType.String()
с получателем указателя. Это означает, что метод String()
находится в наборе *MyType
типа *MyType
, но не в MyType
.
Когда мы пытаемся присвоить значение MyType
переменной типа Stringer
, мы получаем соответствующую ошибку:
m := MyType{value: "something"}
var s Stringer
s = m // cannot use m (type MyType) as type Stringer in assignment:
// MyType does not implement Stringer (String method has pointer receiver)
Но все в порядке, если мы пытаемся присвоить значение типа *MyType
для Stringer
:
s = &m
fmt.Println(s)
И мы получаем ожидаемый результат (попробуйте на Go Playground):
something
Итак, требования для получения этой ошибки времени компиляции:
- Значение не указательного конкретного типа, присваиваемого (или переданного или преобразованного)
- Тип интерфейса, назначаемый (или переданный, или преобразованный в)
- Конкретный тип имеет требуемый метод интерфейса, но с указателем приемника
Возможности решения вопроса:
- Необходимо использовать указатель на значение, набор методов которого будет включать метод с получателем указателя
- Или тип получателя должен быть изменен на не указатель, поэтому набор методов конкретного типа без указателя также будет содержать метод (и, таким образом, удовлетворять интерфейсу). Это может или не может быть жизнеспособным, так как если метод должен изменить значение, приемник без указателя не вариант.
Структуры и встраивание
При использовании структур и встраивания часто не "вы" реализуют интерфейс (предоставляют реализацию метода), а тип, который вы встраиваете в свою struct
. Как в этом примере:
type MyType2 struct {
MyType
}
m := MyType{value: "something"}
m2 := MyType2{MyType: m}
var s Stringer
s = m2 // Compile-time error again
Опять же, ошибка времени компиляции, потому что набор методов MyType2
не содержит метод String()
встроенного MyType
, а только набор методов *MyType2
, поэтому работает следующее (попробуйте на Go Playground):
var s Stringer
s = &m2
Мы также можем заставить его работать, если встраиваем *MyType
и используем только не указатель MyType2
(попробуйте на Go Playground):
type MyType2 struct {
*MyType
}
m := MyType{value: "something"}
m2 := MyType2{MyType: &m}
var s Stringer
s = m2
Кроме того, независимо от того, что мы встраиваем (MyType
или *MyType
), если мы используем указатель *MyType2
, он всегда будет работать (попробуйте на Go Playground):
type MyType2 struct {
*MyType
}
m := MyType{value: "something"}
m2 := MyType2{MyType: &m}
var s Stringer
s = &m2
Соответствующий раздел из спецификации (из раздела Типы структур):
Учитывая тип структуры
S
и тип с именемT
, продвигаемые методы включаются в набор методов структуры следующим образом:
- Если
S
содержит анонимное полеT
, наборы методовS
и*S
включают в себя повышенные методы с приемникомT
Набор методов*S
также включает повышенные методы с приемником*T
- Если
S
содержит анонимное поле*T
, наборы методовS
и*S
включают в себя повышенные методы с получателемT
или*T
Другими словами: если мы встраиваем тип без указателя, набор методов устройства для вставки без указателя получает только методы с получателями без указателя (из встроенного типа).
Если мы встраиваем тип указателя, набор методов устройства для вставки без указателя получает методы как с указателями, так и с получателями без указателя (из встроенного типа).
Если мы используем значение указателя для устройства для внедрения, независимо от того, является ли встроенный тип указателем или нет, набор методов указателя на устройство для внедрения всегда получает методы как с указателями, так и с получателями без указателей (из встроенного типа).
Замечания:
Существует очень похожий случай, а именно, когда у вас есть значение интерфейса, которое переносит значение MyType
, и вы пытаетесь напечатать из него другое значение интерфейса, Stringer
. В этом случае утверждение не будет выполнено по причинам, описанным выше, но мы получим немного другую ошибку времени выполнения:
m := MyType{value: "something"}
var i interface{} = m
fmt.Println(i.(Stringer))
Паника во время выполнения (попробуйте на Go Playground):
panic: interface conversion: main.MyType is not main.Stringer:
missing method String
Пытаясь преобразовать вместо типа assert, мы получаем ошибку времени компиляции, о которой мы говорим:
m := MyType{value: "something"}
fmt.Println(Stringer(m))