Interfaces and Method sets

There is a tiny, important and a bit confusing thing to remember while dealing with Interfaces. I recently faced an issue while asserting a variable to a particular interface type. Even though I had defined all the methods to the struct type mentioned in an interface, I wasn’t able to assert it.

Consider the following example -

type Test struct {
}

func (t Test) Value() {
  fmt.Println("Value")
}

func (t *Test) Ptr() {
  fmt.Println("Ptr")
}

type Value interface {
  Value()
}

type Ptr interface {
  Ptr()
}

func main() {
  var x Test
  x.Value()
  x.Ptr()
  runValue(x)
  runPtr(x)
}

func runValue(i interface{}) {
  x := i.(Value)
  x.Value()
}

func runPtr(i interface{}) {
  x := i.(Ptr)
  x.Ptr()
}

You can run the above code here

Notice how the type assertion in runPtr() fails. Let’s look at the defenition of interfaces and method sets from the spec.

Interfaces

An interface type specifies a method set called its interface.

Method sets

A type may have a method set associated with it. The method set of an interface type is its interface. 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).

What’s wrong with my code?

The definition of the two is clear enough to understand what’s wrong with the above code. In the main function I have a variable x, which is of type Test. So, the method set of x is obviously only Value(). Had I declared varialbe x of type *Test, then it’s method set would be both Value() and Ptr(). Since method set of x has no method Ptr() it can’t be asserted as interface type Ptr.

Wait!

If the above statement is true, then how did x.Ptr() in the main function even work? This was the confusing part. The magic is actually done by the Go compiler. The compiler adjusts the value to a pointer based on the type of method set defined for the type. When we do a x.Ptr() the compiler automatically adjusts it to be called with a pointer (&x).Ptr() because the method Ptr() is defined under method set with pointer receiver.

Solution

There are a couple of ways to solve the above problem. You can either change runPtr(x) to runPtr(&x) in the main function or make x a pointer to Test rather than Test type by changing var x Test to var x *Test = new(Test). You can run the solved code here and here.

Thanks to go slack channel and William Kennedy for helping me.

comments powered by Disqus
comments powered by Disqus