-2

I'm trying an example related to struct embedding of interfaces

// https://talks.golang.org/2014/go4java.slide#52
// Struct embedding of interfaces
// https://play.golang.org/p/SYiZ7M1OEhU

package main

import (
    "bytes"
    "fmt"
    "net"
)

// net.Conn has Read and Write

type loopBack struct {
    net.Conn
    buf bytes.Buffer
}

func (c *loopBack) Read(b []byte) (int, error) {
    fmt.Println("loopBack Read")
    return 0, nil 
}

func main() {

    loop := loopBack{}
    loop.Read(nil)
    loop.Write(nil)                                                                           
}

and the Write method is undefined, so I get this runtime error

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xe28ca]

goroutine 1 [running]:
main.main()
    /tmp/sandbox812386031/main.go:28 +0x6a

Exist some way to validate it at compile time?

link to code

https://play.golang.org/p/SYiZ7M1OEhU

JuanPablo
  • 23,792
  • 39
  • 118
  • 164
  • 1
    No, you cannot check for runtime panics at compile time, or we wouldn't need runtime panics. Perhaps create a constructor for your `loopBack` type? – JimB Jan 28 '19 at 17:10
  • 2
    The `Write` method isn't undefined - it's defined by the embedded `Conn`. The problem is simply that the `Conn` is `nil`. – Adrian Jan 28 '19 at 17:14
  • 1
    If the problem *were* that it was undefined, that *would* be caught at compile time. – Adrian Jan 28 '19 at 17:14

2 Answers2

0

What you are doing is not the same as saying "loopBack implements net.Conn".
To get compile-time error(s) about the missing method – and the mismatched Read(), too – declare loop's type:
(And don't embed net.Conn in loopBack)

func main() {
  var loop net.Conn = loopBack{}

P Varga
  • 19,174
  • 12
  • 70
  • 108
  • 1
    That's a completely different thing, though... embedding an interface type is common, and useful. Just initialise the `Conn` field, job done. – Elias Van Ootegem Jan 28 '19 at 20:01
  • @EliasVanOotegem True, it depends on how one reads the question, whether it's "How to make this code work" or "How to get a compile-time error that it won't work" – P Varga Jan 28 '19 at 20:06
  • Given how the `net.Conn` interface is far bigger than `Read` and `Write` alone, I'm taking it the OP wants a `net.Conn` with a buffer fields for whatever reason (maybe mocking?). Either way, that's up to the OP to specify, because when it comes to mocking, there's many better ways to go about it – Elias Van Ootegem Jan 28 '19 at 20:11
  • He did ask "Exist some way to validate it at compile time?". But, it's hard to tell. Also because it's from a go4java talk, I assumed he wanted to express explicit `implements` in Go – P Varga Jan 28 '19 at 20:13
  • 2
    Yeah, I see how you interpreted the question now. I brushed that bit off as an X-Y problem (as in: OP thinks embedded interfaces somehow force compile-time checks on the struct in which he's embedding) – Elias Van Ootegem Jan 28 '19 at 20:16
0

Golang doesn't require exlicit interface implementations. What you do here:

type loopBack struct {
    net.Conn
    buf bytes.Buffer
}

Is the similar to:

type loopBack struct{
    Conn net.Conn
    buf bytes.Buffer
}

net.Conn being an interface type, the first field of your loopBack type can be anything that implements the net.Conn interface, which is of course more than Read and Write alone (see here).

The advantage of embedding types is that the fields and receiver functions (name conflicts aside) can be accessed directly on the types that embeds them.

With an embedded net.Conn field, you can indeed write:

loop.Write(nil)

If the Conn field is initialised (otherwise, its value is nil). Changing the declaration to the second version, loop.Write won't work, you'll have to write:

loop.Conn.Write(nil)

Type embedding is very powerful, but there's a number of gotcha's when you first get started. Thankfully, there's an entire paragraph explaining embedding on the effective go doc

Anyway, as stated, you are able to call functions, and access fields (if you're embedding a struct type instead of an interface). There is one thing, though: the field must be initialised correctly!

And that's where you went wrong: You still have to initialise your Conn part of the loopBack variable, otherwise what you're doing is the same as:

net.Conn(nil).Write(nil)

Which, naturally, results in a panic (nil pointer dereference)...

For example:

conn, err := net.Dial("tcp", "localhost:80")
if err != nil {
    log.Fatalf("failed to dial localhost: %+v", err)
}
loop := loopBack{
    Conn: conn,
}
loop.Write(nil) // same as conn.Write(nil)

Failing to set the net.Conn embedded field is akin to doing something like this:

s := make([]*int, 10) // make slice of 10 pointers to int
fmt.Println(len(s)) // outputs 10
*s[0]++ // add 1 to first element PANICS

The 10 elements in the slice exist, but they've all been initialised to nil

Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149