DEV Community

Fareez Ahamed
Fareez Ahamed

Posted on • Originally published at fareez.info

Why Interfaces in Go Are Not Explicitly Implemented

Languages that came after Java, had great influence of it irrespective of static-typed or dynamic---especially the Object-Oriented features. Interfaces, popularized by Java was taken straight into other languages like C# and even dynamically typed languages like PHP! But Go has questioned every existing solution thoroughly before inheriting those features. No while loops, no do-while loops, no ternary operators, no constructors, nothing was brought in Go just because it existed earlier.

Similarly, interfaces in Go were designed different from Java purposefully. It has one simple change---you don't explicitly 'implement' your interface. Which means, you will not explicitly mention that this class implements this interface when defining the class/type. When I read this initially, it looked like chaos. 'How will I know what (list of) interfaces my object implements?!', 'What if I get the parameters wrong while implementing?', 'And what about the shiny helping light bulb which will implement all the methods automatically for me with a NotImplementedException?'. It took a while for me to get convinced, that its actually helpful not to implement interfaces explicitly and opens new possibilities.

First, let's address our insecurity about compromises to type-safety due to not explicitly mentioning the intefaces when creating a type/class. Imagine you have a type and interface as shown below with a conflict in types of the parameter.

type DbLogger struct {}

func (db *DbLogger) Log(errNo string, msg string) {/*...*/}

type Logger interface {
    Log(errNo int, msg string)
}
Enter fullscreen mode Exit fullscreen mode

If you notice, I have written the type definition first and the interface definition below to break our notions of types/classes being always dependent on intefaces. In Go, it doesn't. In Go, interface is a window through which you are trying to see/access an external object. You not having a window, doesn't make the external object not to exist --- it will be there.

In a language like Java, you would have got the compiler catch it by now with the following message.

Class 'DbLogger' must either be declared abstract or implement 
abstract method 'Log(int, String)' in 'Logger'
Enter fullscreen mode Exit fullscreen mode

But Go waits till you actually use it. So lets use it in a function.

func DoSomething() {
    var logger Logger
    logger = &DbLogger{}
    logger.Log(234, "Message")
}
Enter fullscreen mode Exit fullscreen mode

And now Go catches it as we are trying to assign the object to the interface. Following will be the error from compiler.

cannot use &(DbLogger literal) (value of type *DbLogger) as Logger 
value in assignment: wrong type for method 
Log (have func(errNo string, msg string), want func(errNo int, msg string))
Enter fullscreen mode Exit fullscreen mode

This error message is even more clear and points out the actual issue compared to the one from Java. Credits to Go not having Method Overloading. Now we can rest assured that type safety is guaranteed with a proper message from the compiler.

Not having to explicitly implement an interface has an important purpose. Assume you are working with a third party package. To avoid tight coupling and to keep the code testable without depending on the third party package, you have to have an interface to communicate to the object from the third party library. You acheive this through a Repository/Service pattern in other languages. What if the third party object can comply with the interface that you create in your package? It means you don't have to write a wrapper service or repository.

But it is not possible in languages like Java or C# because your package provider can't explicitly implement all the interfaces that his clients wants. Making the interface work without implementing explicitly enables Go to create the interface at the consuming end and not the providers end.

Let's look at an example where you have a Store which takes care of some operations at the database.

package store

type User struct{ Name string }

type Store struct{}

func (s *Store) CreateUser(u User)      {}
func (s *Store) DeleteUser(u User)      {}
func (s *Store) UserExists(u User) bool { /* return value */ }
func (s *Store) UpdateUser(u User)      {}
Enter fullscreen mode Exit fullscreen mode

We are going to have a function which takes a Store as a dependency and going to have signup logic.

package app

import "store"

func SignUp(s *store.Store, u User) bool {
    if !s.UserExists(u) {
        s.CreateUser(u)
        return true
    }
    return false
}
Enter fullscreen mode Exit fullscreen mode

Let's swap store.Store with an interface.

type UserCreator interface {
    CreateUser(u User)
    UserExists(u User) bool
}

func SignUp(s UserCreator, u User) bool {
    if !s.UserExists(u) {
        s.CreateUser(u)
        return true
    }
    return false
}
Enter fullscreen mode Exit fullscreen mode

Here UserCreator has a subset of the methods available in store.Store type. It simply means UserCreator can point to store.Store. you will be able to pass an object of type store.Store to the SignUp function.

You could argue that, we may not want to keep the signature of the method same as the third party package. Agreed. In that case, you can always make your own implementation. But in most cases, we just call a method or two from the package and just having an interface to imitate is perfect.

If you take a look at the standard library of Go, most of the interface definitions are small, carrying only a couple of methods. The best examples are from the io package. Reader, Writer, Closer, Seeker--- all of these have just one method and ReadWriter, ReadWriteCloser interfaces are made by composing the single method interfaces.

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}
Enter fullscreen mode Exit fullscreen mode

This is how Go's interfaces are meant to be used.

There could be concerns about naming a lot of small one time used interfaces. Certainly, naming is the most difficult problem in programming. We can address that with anonymous interface declarations.

func SignUp(s interface {
    CreateUser(u User)
    UserExists(u User) bool
}, u User) bool {
    if !s.UserExists(u) {
        s.CreateUser(u)
        return true
    }
    return false
}
Enter fullscreen mode Exit fullscreen mode

As an effect of having smaller interfaces, testing this code becomes easier. We don't need complex mocking frameworks to create mocks for these simple interfaces.

type mock struct {
    userExistsVal bool
}

func (mock) CreateUser(u User)        {}
func (m mock) UserExists(u User) bool { return m.userExistsVal }

func TestSignupUserExists(t *testing.T) {
    result := SignUp(mock{true}, User{})
    if result == true {
        t.Error("Want: No user creation, Got: user creation")
    }
}

func TestSignupUserNotExists(t *testing.T) {
    result := SignUp(mock{false}, User{})
    if result == false {
        t.Error("Want: User creation, Got: No user creation")
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above code, we have created a very simple mock based on the interface and supplied to the function for testing it --- without any mocking libraries.

All of these benefits are powered by one simple change to the interface that Go brought in: Implicit Interface Implementation.


PS: Originally posted on my blog at https://www.fareez.info/blog/why-interfaces-in-go-are-not-explicitly-implemented/

Discussion (0)