Struct And Interface Embedding

Ankur Agarwal
3 min readMay 30, 2020

--

A walkthrough of Embedding in go version 1.14

Embedding¹ is a clever way of extending/composing many types into a single type. It represents the O² part of SOLID³ principles — Open closed principle; open to extension, closed for manipulation. What this means is that we can easily extend a type so that it does the original thing, as well as something else without having to amend the original implementation.

Sample Code for this Post

All running sample code can be found here — https://github.com/ankur22/embedding

Struct Embedding

In the example below, I’ve declared a logger and a checker, and each implement a function do and validate respectively. I’ve also declared loggerAndChecker and within it I’ve declared logger and checker. loggerAndChecker now implements both the logger and checker functions:

Note how I’ve not named the fields. If I had then loggerAndChecker would not implement the functions from the other two types implicitly, and I would be required to explicitly call the functions, as in this example:

Also worth noting that loggerAndChecker can contain its own fields along with the embedded structs.

If either or both the logger and checker have a field s, then the s declared in loggerAndChecker would override the s from the embedded structs. Both the s values from the embedded structs are still accessible explicitly. If loggerAndChecker did not declare s but both logger and checker did, we wouldn’t be able to implicitly call s from loggerAndChecker, but we can still retrieve each of the s values from both embedded structs:

Like above, if the logger and checker implement the same function (e.g. validate), and we try and call validate from loggerAndChecker it will not compile. However, if we call any other function that do not overlap it will compile and run:

I think it’s a good idea to override the overlapping method, otherwise it will be very annoying for someone who tries to use that function. In the example below I use the validate function from the logger only:

Interface Embedding

Interface embedding is similar conceptually to struct embedding — we’re embedding interfaces into interfaces instead of structs into structs.

In the following example we have a few interfaces. doer and validator each declare functions that should be implemented for a type to qualify to be identified as one or the other. doerAndValidator doesn’t declare any functions explicitly but instead relies on the declarations of the embedded interfaces.

It’s worth noting that the doesAndValidator can still declare its own functions too as well as embedding other interfaces:

What if the doer and the validator declare the same function? As of go version 1.14, that’s completely fine:

Wait, what about if we two interfaces declare functions with same name, but different signatures? That’s still not ok and it won’t compile since go doesn’t do method overloading like in other languages such as Java or Python:

This won’t compile

Real World Example

In the example below I’ve created a retryableHTTPClient to perform GET requests, and if a transient error occurs the request is retried. I’ve built on top of the builtin HTTP client to perform this extra action. retryableHTTPClient can be passed around anywhere that accepts http.Client, and the benefits of retry will be reaped there too.

You will see that I’ve used struct embedding of http.Client into retryableHTTPClient. We can call all the HTTP client public functions, even if we haven’t implemented them for retryableHTTPClient as they will fall back to using the HTTP client’s implementation. In this example only the GET is retryable, so you would need to complete this for the other public methods that also need to be retryable.

Using interface embedding to extend the builtin http.Client to be retryable

Other Articles That Are Also Worth A Read

References

[1] https://golang.org/doc/effective_go.html#embedding

[2] https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle

[3] https://en.wikipedia.org/wiki/SOLID

[4] https://www.w3schools.com/java/java_methods_overloading.asp

[5] https://github.com/cenkalti/backoff

--

--

No responses yet