Self referencing interfaces in golang 1.18

Michael Ernst
3 min readDec 17, 2021

--

Intro

I absolutely love structural typing ( which might be referred to as duck typing, but has some slight technical differences ). But there has always been one thing that I wished I could do, but couldn’t, till generics came into the picture!

The first beta of go1.18 was released some days ago and I took it out for a spin since then. And it works just as expected and it is fast! It will save me from a lot of copying, even within a single project. ( check my attempt of generics Read-Copy-Update maps can can be found here )

The Problem

But let’s get back to the problem that has annoyed me ever since I started using golang. Let’s imagine we have the following interface:

type Logger interface {
WithField(name, value string) Logger
Info(message string)
}

It is a very easy logger that you can add fields to and it returns itself. We don’t really need generics for it. But without them, we need to be aware of the interface itself, which effectively breaks the implicit interfacing. As you can see we need to return Logger from WithField in order to implement the interface, which tightly couples our package to the package that defined the interface:

type MyLogger struct {
}
func (m *MyLogger) WithField(string, string) Logger {
...
}
func (m *MyLogger) Info(string) {
}

The Solution

When trying to implement this with generics, the first straight forward idea would be to implement something like this:

type GenericLogger[T any] interface {
WithField(string, string) T
Info(string)
}

However, if you take a close look, this by itself is not exactly what we need yet. What we return is T which can be any but which is actually not GenericLogger! We are also not allowed to refer to the own interface here

We need to make sure that T is actually the type that we want to implement. The interface is sufficient to do this, but there is something we need to do when actually using it:

func DoStuff[T GenericLogger[T]](t T) {
t.WithField("go", "1.18").Info("is awesome")
}

Reading this is surely something you need to get used to first, but it is great that it works. What’s happening is basically that we accept a type T which needs to implement GenericLogger[T]. So at this point we basically recursively tell the GenericLogger that it itself is the generic object that shall be returned.

Try it on playground!

Conclusion

Go was already powerful before but I believe that thanks to generics there will be a new spark of interest. By getting less verbose we will be able to focus more on writing business logic and it helps go to be used in even more areas.

Structural typing, as one of go’s core strengths (IMHO), will also be more powerful.

I am looking forward to the release so I can use that in production!

Notes:

Thanks to Christoph Berger who pointed out to me how we can make use of generics this way!

--

--