Accepting an argument that satisfies a local package interface from outside the package in GO

Lets say you’re writing some semi-complex code… For example you’re writing an engine for a network service and you want to allow people to wrap transports around it however they want. So maybe you want someone to be able to slap this functionality inside a websockets server, or a zmq workflow, or an RPC server, or communicate via some other heretofore unknown or unconsidered method. So you define an interface for all the functions that you need to make the engine work… but how do you accept mything.MyNetworkInterface when the actual type could be anything?

I actually beat my head on this for long enough that it turned out to be slightly embarrassing. The answer was actually in a set of articles (here, and here) that I’d already read (and if you’re here looking for the same thing that I was looking for you’ve undoubtedly already read them as well… but it hasn’t clicked yet…)

So let me give you an example that hopefully illustrates exactly how you do this…

First we have our package: “foo” which defined the “blahable” interface like so…

type blahable interface{
	Blah()
}

Next we have our program we’re writing… and we’ve written our struct “bar”, a pointer to which will satisfy the foo.blahable interface because it impliments the Blah() method like so…

type bar struct {
	count int
}

func (b *bar)Blah() {
	b.count = b.count + 1
	log.Printf("Blah() #%d", b.count)
}

Now the thing that I was having a hard time with was that I wanted to write a foo.DoBlah() function which takes a foo.blahable  and uses the Blah() method. Obviously this is simplified and contrived, but bare with me here…

I was trying things like this

func DoBlah(arg blahable) {
    arg.Blah()
}

Or this

func DoBlah(arg interface{}) {
    arg.Blah()
}

Or a dozen other things (with permutations of pointers, not pointers, etc)… Did I mention that I fumbled with this for an embarrassingly long time?  I would end up with maddening errors about how this could not be passed as that because it’s the wrong type; or how this was not an interface of type that; etc. Of course trying out pointers and non pointers and references out of frustration only added fuel to the fire of my confusion…

Also frustratingly most of the questions and answers out there are for types within the same package. I wanted things unknown to the current package to be passed in and used as an interface defined locally inside the package.

First let me give you a foo package and a main package that actually do what I want.

package foo

import "log"

type blahable interface{
	Blah()
}

func DoBlah(blah interface{}) {
	log.Printf( "entering foo.blah()..." )
	theblah := blah.(blahable)
	theblah.Blah()
	theblah.Blah()
	theblah.Blah()
	theblah.Blah()
	log.Printf( "leaving foo.blah()..." )
}
package main

import "foo"
import "log"

type bar struct {
	count int
}

func (b *bar)Blah() {
	b.count = b.count + 1
	log.Printf("Blah() #%d", b.count)
}

func main() {
	mybar := new(bar)
	mybar.Blah()
	mybar.Blah()
	foo.DoBlah(mybar)
	mybar.Blah()
	mybar.Blah()
}

Which outputs the following…

 2013/08/18 21:05:21 Blah() #1
 2013/08/18 21:05:21 Blah() #2
 2013/08/18 21:05:21 entering foo.blah()...
 2013/08/18 21:05:21 Blah() #3
 2013/08/18 21:05:21 Blah() #4
 2013/08/18 21:05:21 Blah() #5
 2013/08/18 21:05:21 Blah() #6
 2013/08/18 21:05:21 leaving foo.blah()...
 2013/08/18 21:05:21 Blah() #7
 2013/08/18 21:05:21 Blah() #8

So here’s the deal in two points…

#1 You can accept any type as an “empty interface” (interface{}) because every type has at least no methods and no members. But you can’t use it as is after it being passed to you because of the same reason that you can accept it: it has no defined methods or members. Which is why in the foo package inside func DoBlah() we must assert blah (our passed empty interface) to an interface of type blahable via the following.

theblah := blah.(blahable)

Had the passed interface not satisfied blahable a runtime panic would have happened… (though it’s catchable if you want)

#2 A type and a pointer to a type are different things and can satisfy, or not, an interface separately… Given the “blahable” interface from above and the following code…

type A struct {}
func (a A) Blah() {}

Type B struct {}
func (b *B) Blah() {}

Type C struct{}

Obviously type C does not implement blahable.  Given foo = A{} foo implements blahable; but when given foo = new(A) foo (which is a pointer to an A) does not implement blahable.  The opposite  is true for B. foo = B{} does not implement blahable, but new(B), or &foo both do. (see the method signatures)

Perhaps inconveniently you can’t define both… at runtime you’ll get a method redeclared error…

One thought on “Accepting an argument that satisfies a local package interface from outside the package in GO

Leave a Reply to Matt WilkinsonCancel reply