Why is an interface assignment more strict than a method call?

Viewed 147

I am going through the Tour of Go. I learned that if we have a method that accepts a pointer as a receiver, it will also accept a value type as a receiver (with go doing the conversion automatically).

type Vertex struct { X, Y float64 }

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X * v.X + v.Y * v.Y)
}

Then the following code is valid, whether Vertex is received by value or pointer

v := Vertex{1, 2}
fmt.Println(v.Abs())

p := &v
fmt.Println(p.Abs())

However, let's say we have the following interface:

type Abser interface {
    Abs() float64
}

Then, why is the following code invalid?

var a Abser
v := Vertex{1, 2}

a = v // invalid

My understanding was that this would be fine. Even though v is a value type which "implements" the Abs function which takes a pointer receiver, it would also take it by value?

Were interfaces simply designed to be more strict in terms of what an interface variable can hold on the right hand side? The interface is seeing *Vertex and Vertex as two different types, however, the Abs() method doesn't have an issue processing either.

1 Answers

In both cases, Go wants a pointer to run the method on. The big difference is that method calls will automatically take the address of v, but checking whether something implements an interface does not.

Method Calls:

When calling a method with pointer receiver on a plain type, Go will take the address automatically if it is allowed. From the spec on method calls (highlight is mine):

A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m()

In this case, x refers to your variable v and is addressable. So on method calls, Go automatically runs (&v).Abs().

Assignment:

When attempting to assign a = v, the check that must be fullfilled is T is an interface type and x implements T.. v implements Abser only if its method set matches the interface. This method set is determined as follows:

The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).

You will notice that when computing the method set, Go does not take the address of v as it did in the method call. This means that the method set for var v Vertex is empty, failing to implement the interface.

Solution:

The way around this is to take the address of v yourself:

var a Abser
v := Vertex{1, 2}

a = &v

By doing this, you are now looking at the method set of *Vertex which does include Abs() float64 and thus implements the interface Abser.

Related