Imagine you have three LEGO pieces: a Gatling gun, a helicopter, and a pilot.
Now smash them together, and boom! You’ve got a fighter chopper.
That’s basically struct embedding in Go: merging different components into one to create a super component.
And honestly? This is one of my favorite patterns in Go.
Here’s a real example from my current project, where I fuse three basic agents to build a super agent:
- A Base Agent that can respond
- A Function Calling Agent that embeds the Base Agent
- A Long Conversational Agent that embeds the Function Calling Agent
So the Long Convo Agent ends up inheriting behaviors from both, the Function Calling Agent and the Base Agent. It’s like nested power-ups.
Now, I know this looks like inheritance. but it’s not. Not in the OOP sense. There’s no strict relationship here. Just components merged together like modular building blocks.
Let’s bring this concept back to our Storage project, we've been working on and see how we can embed a Logger into it.
Full Code(including prev article):
git clone https://github.com/sklyt/goway.git
Loggers: Our Next LEGO Piece
Loggers are lightweight and super easy to embed, perfect for demonstrating this pattern.
Let’s create a new folder:
logger/
log.go
logger.go:
package logger
import "fmt"
type Logger struct {
Prefix string
}
func NewLogger(prefix string) *Logger {
return &Logger{Prefix: prefix}
}
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.Prefix, msg)
}
Before we embed this into our storage structs, a quick word on constructors.
Constructors in Go
A constructor is just a function that wraps struct creation and returns the struct. You’ve already seen one above:
func NewLogger(prefix string) *Logger {
return &Logger{Prefix: prefix}
}
This makes creating components clean and consistent.
Now let’s apply this to our storage system.
Embedding the Logger in OnlineStore
type OnlineStore struct {
*logger.Logger
*gohttplib.Client
URL string
}
func NewOnlineStore(url string) *OnlineStore {
return &OnlineStore{
Logger: logger.NewLogger("ONLINE"),
Client: gohttplib.NewClient(url, httpOpts...),
URL: url,
}
}
Notice this line:
*logger.Logger
We’re embedding a pointer to our logger directly into the OnlineStore. And during creation, we pass in a prefixed logger instance.
Embedding the Logger in LocalStore
type LocalStore struct {
*logger.Logger
Path string
store map[string][]byte
}
func NewLocalStore(path string) *LocalStore {
return &LocalStore{
Logger: logger.NewLogger("LOCAL"),
Path: path,
store: make(map[string][]byte),
}
}
Now both our storage types are enhanced with logging behavior, plug-and-play style.
Updating main.go
Let’s switch from manual struct creation to using constructors:
func main() {
online := storage.NewOnlineStore("https://jsonplaceholder.typicode.com")
local := storage.NewLocalStore("./data")
// Use them like before, now with logging!
}
And that’s struct embedding in a nutshell: building composable, extendable components without the chains of inheritance.
We just went from plain storage to log-enhanced modular storage, with zero friction.
In the next article, we’ll explore:
- Functional Options


