1

Consider the following code:

  package ifaces

  import (
          "fmt"
          "testing"
  )

  type IFace1 interface {
          Do1()
  }

  type IFace2 interface {
          Do2()
  }

  type Inner struct {
  }

  func (i *Inner) Do2() {
          fmt.Printf("Do2\n")
  }

  type Outer struct {
          Inner
  }

  func (o *Outer) Do1() {
          fmt.Printf("Do2\n")
  }

  func TestInterface(t *testing.T) {
          o := Outer{}
          var i interface{}
          i = o

          a := i.(IFace2)
          a.Do2()
  }

In summary, Inner implements Iface2, while Outer implements Iface1 and embeds Inner.

Checking Outer via gopls (with vim-go's command GoImplements) gives the following response:

  test/ifaces/interfaces_test.go|8 col 6| type IFace1 interface {                                                                                                                                                                             
  test/ifaces/interfaces_test.go|12 col 6| type IFace2 interface {

However, running the test results in the following error:

$ go test test/ifaces/interfaces_test.go 
--- FAIL: TestInterface (0.00s)
panic: interface conversion: ifaces.Outer is not ifaces.IFace2: missing method Do2 [recovered]
    panic: interface conversion: ifaces.Outer is not ifaces.IFace2: missing method Do2

goroutine 18 [running]:
testing.tRunner.func1.2({0x5095c0, 0xc0000a23c0})
    /home/x/sdk/go1.19.3/src/testing/testing.go:1396 +0x24e
testing.tRunner.func1()
    /home/x/sdk/go1.19.3/src/testing/testing.go:1399 +0x39f
panic({0x5095c0, 0xc0000a23c0})
    /home/x/sdk/go1.19.3/src/runtime/panic.go:884 +0x212
command-line-arguments.TestInterface(0x0?)
    /home/x/D/git/personal/go/test/ifaces/interfaces_test.go:36 +0x27
testing.tRunner(0xc0000829c0, 0x52f520)
    /home/x/sdk/go1.19.3/src/testing/testing.go:1446 +0x10b
created by testing.(*T).Run
    /home/x/sdk/go1.19.3/src/testing/testing.go:1493 +0x35f
FAIL    command-line-arguments  0.004s
FAIL

As shown in the snippet, this is Go 1.19: why does the program panic, if gopls says the interface is implemented?

Does holding Outer inside an interface{} variable have anything to do with this error? Note the error explicitly mentions Outer (ifaces.Outer is not ifaces.IFace2).

Running the same code against 1.13.15 produces the same panic, so this does not seem to be a bug (or it is a very old bug :)

caxcaxcoatl
  • 8,472
  • 1
  • 13
  • 21
  • 2
    Use `i = &o` in the test. Since it's `*Inner` that implements the interface you need `*Outer` instead of `Outer`. I'm not familiar with `gopls` nor vim-go `GoImplements` so I can only guess that the output from vim-go `GoImplements` shows the set of interfaces implemented by both `T` and `*T` _as a convenience_, but that's just a guess. – mkopriva Mar 25 '23 at 16:36
  • Thanks, @mkopriva! That definitely did the trick, but I can't say I understood it. What you're saying is that when the `i` received `o`, it received the method set only `Outer` method set, not `*Outer`'s? And then, when the type assertion was run it only saw `Outer` method set, and concluded that it did not implement `IFace2`? Can you help me with any keywords that would get me to some resource that explains that behavior? – caxcaxcoatl Mar 25 '23 at 16:43
  • 2
    The ONLY resource you need to understand the language proper is the official [language specification](https://go.dev/ref/spec). Regarding method sets you can take a look at the [method sets](https://go.dev/ref/spec#Method_sets) section of the spec. The first two points in that section are the most relevant ones for you question. – mkopriva Mar 25 '23 at 16:53
  • 3
    And these points in section https://go.dev/ref/spec#Struct_types: `If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.`, `If S contains an embedded field *T, the method sets of S and *S both include promoted methods with receiver T or *T.`. – Zeke Lu Mar 25 '23 at 17:00
  • 2
    I want to add that `o.Do2()` is valid because `When the value is addressable, the language takes care of the common case of invoking a pointer method on a value by inserting the address operator automatically`. See https://go.dev/doc/effective_go#pointers_vs_values – Zeke Lu Mar 25 '23 at 17:12

0 Answers0