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
Top comments (7)
Pretty cool seeing Go handled like this, really makes me wanna mess around with new combos myself
Facts, honestly I didn't know any of this until a few weeks back! changed the way I viewed Go. it's so fun now! 🔥 it's literally like building LEGO!
Great breakdown of struct embedding! I love how composable Go structs can be. Has anyone else used struct embedding in their projects? Any cool patterns or tips to share?
Thanks!, oh yeah I use them all the time! like Agentic project and large Go OSS code bases! they are good to know before reading any codebase.
Love the LEGO analogy, makes it so clear. How do you keep things modular with embedding as projects grow bigger?
Great question; I used to struggle with this a lot too!
But the trick is to use your folders as separators or logical groups, especially in Go. Let me show you what I mean:
In the image, if you look at the
pkg/agent
folder, each file contains a single base struct.No other major structs are allowed; unless it's a small helper or supporting type.
So the idea is this:
You have a folder (e.g.,
agent
) under one Go packageagent
, and inside that folder, each file holds one “main” struct.From there, you can embed easily.
For example:
agent.go
contains the base agentfn_call.go
embeds that base agentdialogueagent.go
embedsfn_call.go
It’s basically dependency injection by structure; but done with folders and files.
TL;DR:
Some comments may only be visible to logged-in visitors. Sign in to view all comments.