SPONSORED ADS

Golang interface best practices

Last Updated Feb 26, 2023

An interface is a contract. It say the exact methods that other types must implement.

type error interface {
    Error() string
}

Any structs has method Error() string can be considered fulfil a contract and is implements the error interface.

For example, the following Problem type satisfies the interface because it has a Error() string method:

type Problem struct {
    Msg string
    Code    int
}

func (p Problem) Error() string {
    return fmt.Sprintf("oops! msg: %s, code: %v", p.Msg, p.Code)
}

We can also define other structs that does other things but still implement the error interface, such as a TroubleIsAFriend or HelpMeOhMyGod struct in the same way.

The thing is, if you have a func like this

func Debug(err error) {
    log.Println(err.String())
}

You can pass a Problem, a TroubleIsAFriend or a HelpMeOhMyGod to this func without any "problem" or "trouble" here. The code would work perfectly OK because you can use an object of any type if it fulfil a contract with error interface, or in other words it satisfies the error interface.

So Interfaces generalize behaviour and do it implicitly. They show you how seemingly unrelated things work the same way in the context of this problem. They are about doing, not being. It doesn't make sense to make interfaces before you have at least two types to generalize from.

The fact that interfaces are implicit makes them such a powerful way to describe reality in code. The same type can be used in many different ways, even if you don't know them in advance. There are no need for type to say which interfaces it implemented.

To put it in short using interfaces make your code flexible, extensible, and easy to test. It also promotes good coding practices by separating concerns and creating modular code.

However using interfaces is super hard (or so I think.. could be only me?). Anytime I dealing with abstractions in code, my simple, stupid code become a deep shit very quickly. To make the best out of golang interfaces, consider some best practices that I found helpful.

Don't build interfaces unless you need them.

Interface is good, but you can't add a method to an interface without breaking your code.

A really good example of this is the Marshaler interface from "encoding/json", which is defined as:

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

If you were to add a method to the above interface, a lot of things would break.

So if there is only one piece of advice that you take away from this article, make it this: delay the use of interfaces to the time you actually need them.

Interfaces must be small and focused

Interfaces should have a clear and concise purpose. Avoid creating overly broad interfaces that try to encompass too many behaviors. Interfaces are meant to define the minimal behavior.

For example the io package in Go standard library specifies the io.Reader interface, which represents the read end of a stream of data. It has only one method.

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

In fact, in the standard library, most interface definitions are a single method. This allows the greatest reuse because fullfil a constract with only 1 method is easy to do than a contract with 69 methods.

Accept interfaces, return struct.

By using interfaces as function parameters, you can increase the flexibility of your code. This is because you can pass in any type that implements the interface, rather than being restricted to a single concrete type.

However, when your function return consider return a struct, because you can easily add new method to the struct without worry about breaking compatibility.

type Robot interface {
   //...
}

type Eva struct {
   // *Eva implement Robot interface
}

// constructor return a struct instead of interface
func NewRobot(id string) *Eva {
   return &Eva{
      //...
   }
}

// an update func accept interface instead of concrete struct
func Update(robot Robot) {
  //...
}

Conclusion

Overall, using interfaces help us creating flexible, modular, and maintainable code. However remember that interface is not the only true way of principle here. In fact there is no one true way, as well as these best practices. Best practices change over the years, they come and go. In the end the only matter is your code problem: use a programming language and its features as the tools to solve your problem.

See you next time.

Others articles of me write about golang

If you like golang, here is some others articles of the same author (me) about golang topic. Check it out !

  1. Golang context best practices
  2. How to graceful shutdown golang service
  3. Everything you need to know about Packages in Go
  4. Optimizing the Go garbage collector

Hi there. Nodeepshit is a hobby website built to provide free information. There are no chargers to use the website.

If you enjoy our tutorials and examples, please consider supporting us with a cup of beer, we'll use the funds to create additional excellent tutorials.

If you don't want or unable to make a small donation please don't worry - carry on reading and enjoying the website as we explore more tutorials. Have a wonderful day!