haselkern

Explicit Interfaces In Go

I have an issue with Go: Interfaces are implemented implicitly.

You implement an interface by implementing all methods of that interface. However, you don't mention that you are implementing it.

type Speaker interface {
    Speak() string
}

type Cat struct {}
func (c Cat) Speak() string {
    return "meow"
}

func Announce(s Speaker) {
    fmt.Println("Behold, for it speaks: ", s.Speak())
}

func main() {
    cat := Cat{}
    Announce(cat)
}

This is fine and works as you would expect. What happens if you don't implement the interface correctly?

//              ↓ Whoops, typo
func (c Cat) Speek() string { … }

Trying to run this will result in a compiler error.

./prog.go:21:11:
cannot use cat (variable of type Cat) as Speaker value in argument to Announce:
Cat does not implement Speaker (missing method Speak)

The error is immediately noticeable and easy enough to fix. However, this is only a tiny example.

In a bigger real-life application, main, Announce, Cat, and Speaker would all be located in different files in different packages. The compiler would still complain that the error is in main. This is technically correct. main wants to Announce a Speaker, but Cat isn't one.

However, the mistake happened much earlier. I wrote Cat and wanted it to implement Speaker, except I made a mistake. The compiler should direct me towards fixing the method I implemented on Cat and not tell me that I cannot use Cat in main.

The Solution

Hidden away in a short paragraph in the Go FAQ lies the solution to this problem:

You can ask the compiler to check that the type T implements the interface I by attempting an assignment using the zero value for T:

var _ I = T{} // Verify that T implements I.

Applied to the previous example it would look like this.

type Cat struct {}

var _ Speaker = Cat{}
func (c Cat) Speek() string {…}

In addition to the previous error message, you now get another error.

./prog.go:11:17:
cannot use Cat{} (value of type Cat) as Speaker value in variable declaration:
Cat does not implement Speaker (missing method Speak)

The error points to the line var _ Speaker = Cat{}, right next to the method I want to implement! Brilliant. This is especially useful with an IDE that lets you quickly jump to compile errors.

Instead of hunting through files trying to figure this out, the Go compiler now tells me exactly where I need to fix my mistake.

Expressing Intent

Another bonus is that my intent is more clear. I did not implement a Speak method by accident and just so happened to fulfill the Speaker interface. I did it deliberately. var _ Speaker = Cat{} tells other programmers (which always includes yourself in the future) that I want Cat to implement Speaker.

The syntax is a little weird, but you can get used to it. However, I would prefer something like impl Speaker for Cat, which would be easier to understand.

The next time you are implementing an interface I for your struct T, consider making the implementation explicit by writing var _ I = T{}. This clarifies your intent and will make implementation errors easier to locate.

Tags: #programming #go