Почему назначение интерфейса более строгое, чем вызов метода?

Aug 20 2020

Я прохожу тур по го. Я узнал, что если у нас есть метод, который принимает указатель в качестве получателя, он также будет принимать тип значения в качестве получателя (с автоматическим выполнением преобразования).

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 () также не вызывает проблем с обработкой.

Ответы

3 Marc Aug 20 2020 at 12:32

В обоих случаях 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.