Почему назначение интерфейса более строгое, чем вызов метода?
Я прохожу тур по го. Я узнал, что если у нас есть метод, который принимает указатель в качестве получателя, он также будет принимать тип значения в качестве получателя (с автоматическим выполнением преобразования).
type Vertex struct { X, Y float64 }
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X * v.X + v.Y * v.Y)
}
Тогда следующий код действителен, независимо от того, получена ли Vertex по значению или по указателю
v := Vertex{1, 2}
fmt.Println(v.Abs())
p := &v
fmt.Println(p.Abs())
Однако, допустим, у нас есть следующий интерфейс:
type Abser interface {
Abs() float64
}
Тогда почему следующий код недействителен?
var a Abser
v := Vertex{1, 2}
a = v // invalid
Насколько я понимаю, это было бы хорошо. Даже несмотря на то, что v - это тип значения, который «реализует» функцию Abs, которая принимает получатель указателя, он также будет принимать его по значению?
Были ли интерфейсы просто спроектированы так, чтобы быть более строгими с точки зрения того, что переменная интерфейса может содержать с правой стороны? Интерфейс видит * Vertex и Vertex как два разных типа, однако метод Abs () также не вызывает проблем с обработкой.
Ответы
В обоих случаях Go требуется указатель для запуска метода. Большая разница в том, что вызовы методов автоматически принимают адрес v
, но проверка того, реализует ли что-либо интерфейс, этого не делает.
Вызов методов:
При вызове метода с приемником указателя для простого типа Go автоматически принимает адрес, если это разрешено. Из спецификации вызовов методов (выделено мной):
Вызов метода xm () допустим, если набор методов (тип) x содержит m и список аргументов может быть назначен списку параметров m. Если x адресуем и набор методов & x содержит m, xm () является сокращением для (& x) .m ()
В этом случае x
относится к вашей переменной v
и адресуется . Итак, при вызове метода Go автоматически запускается (&v).Abs()
.
Назначение:
При попытке назначить a = v
необходимо заполнить чек T is an interface type and x implements T.
. v
реализуется, Abser
только если его набор методов соответствует интерфейсу. Этот набор методов определяется следующим образом:
Метод набор любого другого типа Т состоит из всех методов , объявленных с типом приемника T . Набор методов соответствующего типа указателя * T - это набор всех методов, объявленных с помощью получателя * T или T (то есть он также содержит набор методов T).
Вы заметите, что при вычислении набора методов Go не принимает адрес, v
как это было при вызове метода. Это означает, что метод, установленный для var v Vertex
пуст, не может реализовать интерфейс.
Решение:
Чтобы обойти это, нужно взять свой адрес v
:
var a Abser
v := Vertex{1, 2}
a = &v
Делая это, вы теперь смотрите на набор методов, *Vertex
который действительно включает Abs() float64
и, таким образом, реализует интерфейс Abser
.