Go Embedding — Struct Embedding Interface Embedding and Method Promotion Explained
In this tutorial, you will learn about Go Embedding. We cover key concepts, practical examples, and best practices to help you master this topic.
Go embedding allows one struct or interface to include another type anonymously, promoting its fields and methods to the embedding type for composition-based code reuse without class inheritance.
What You'll Learn
- Struct embedding for field/method promotion
- Interface embedding for composition
- Overriding promoted methods
- Embedding vs composition patterns
Why It Matters
Embedding is Go's composition approach. Docker embeds structs for container configurations and driver implementations. Kubernetes uses embedding for resource types (PodSpec, ObjectMeta). DodaZIP embeds archive readers for format support.
Real-World Use
sync.Mutex is embedded in structs for locking. http.Handler is embedded in middleware wrappers. Standard library uses embedding extensively for type composition.
flowchart LR
A["Embedding"] --> B["Struct"]
B --> C["Method Promotion"]
C --> D["Interface"]
D --> E["Overriding"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Struct Embedding Basics
type Address struct {
City string
State string
Zip string
}
type Person struct {
Name string
Age int
Address // Embedded — fields promoted
}
func main() {
p := Person{
Name: "Alice",
Age: 30,
Address: Address{
City: "New York",
State: "NY",
Zip: "10001",
},
}
// Promoted fields — accessed directly
fmt.Println(p.City) // New York
fmt.Println(p.State) // NY
fmt.Println(p.Zip) // 10001
// Also accessible through the embedded type
fmt.Println(p.Address.City) // New York
}
Method Promotion
Embedded types' methods are promoted to the embedding type:
type Logger struct{}
func (l Logger) Info(msg string) {
fmt.Println("[INFO]", msg)
}
func (l Logger) Error(msg string) {
fmt.Println("[ERROR]", msg)
}
type Server struct {
Logger // Embedded — methods promoted
Host string
Port int
}
func main() {
s := Server{
Host: "localhost",
Port: 8080,
}
// Promoted methods
s.Info("Server starting")
s.Error("Failed to connect")
// Also works through embedded type
s.Logger.Info("Explicit call")
}
Overriding Promoted Methods
Define a method with the same name to override:
type Base struct{}
func (b Base) Greet() string {
return "Hello from Base"
}
type Derived struct {
Base
}
func (d Derived) Greet() string {
return "Hello from Derived"
}
func main() {
d := Derived{}
// Overridden method
fmt.Println(d.Greet()) // Hello from Derived
// Original still accessible!
fmt.Println(d.Base.Greet()) // Hello from Base
}
Embedding Interfaces
Interfaces can embed other interfaces:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// Embedded interfaces
type ReadWriter interface {
Reader
Writer
}
type Closer interface {
Close() error
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
// Implementing the composed interface
type File struct{}
func (f File) Read(p []byte) (int, error) { return 0, nil }
func (f File) Write(p []byte) (int, error) { return len(p), nil }
func (f File) Close() error { return nil }
func main() {
var rw ReadWriter = File{}
var rwc ReadWriteCloser = File{}
_ = rw
_ = rwc
}
Embedding with Initialization
type Config struct {
Host string
Port int
}
type Database struct {
Config // Embedded
Name string
PoolSize int
}
func main() {
// Initialize with struct literal
db := Database{
Config: Config{
Host: "localhost",
Port: 5432,
},
Name: "myapp",
PoolSize: 10,
}
fmt.Println(db.Host) // localhost (promoted)
fmt.Println(db.Port) // 5432 (promoted)
fmt.Println(db.Name) // myapp
}
Embedding Multiple Types
type Writer struct{}
func (w Writer) Write(data []byte) (int, error) {
return len(data), nil
}
type Flusher struct{}
func (f Flusher) Flush() {
fmt.Println("Flushed")
}
type Closer struct{}
func (c Closer) Close() error {
fmt.Println("Closed")
return nil
}
// Embedding multiple types
type WriteFlushCloser struct {
Writer
Flusher
Closer
}
func main() {
wfc := WriteFlushCloser{}
wfc.Write([]byte("data"))
wfc.Flush()
wfc.Close()
}
Name Conflicts
When embedded types have the same field or method name:
type A struct {
Name string
}
type B struct {
Name string
}
type C struct {
A
B
}
func main() {
c := C{}
// c.Name // Ambiguous! Compile error
fmt.Println(c.A.Name) // Must use explicit path
fmt.Println(c.B.Name) // Must use explicit path
}
Common Patterns
Mutex Embedding
type Counter struct {
sync.Mutex
value int
}
func (c *Counter) Increment() {
c.Lock()
defer c.Unlock()
c.value++
}
func (c *Counter) Value() int {
c.Lock()
defer c.Unlock()
return c.value
}
Decorator Pattern
type LoggedReader struct {
io.Reader
LogFile *os.File
}
func (lr LoggedReader) Read(p []byte) (int, error) {
n, err := lr.Reader.Read(p)
if n > 0 {
lr.LogFile.Write(p[:n])
}
return n, err
}
func main() {
file, _ := os.Open("data.txt")
log, _ := os.Create("read.log")
reader := LoggedReader{Reader: file, LogFile: log}
io.Copy(os.Stdout, reader)
}
Embedding vs Composition
// Composition (named field)
type Car struct {
Engine Engine // "Has-a" relationship
Wheels []Wheel
}
// Embedding (anonymous field)
type Motorcycle struct {
Engine // Is-like-a relationship
Wheels []Wheel
}
// Embedding promotes methods, composition requires delegation
type Engine struct {
Horsepower int
}
func (e Engine) Start() {
fmt.Println("Engine started")
}
func main() {
// Embedding — methods promoted
m := Motorcycle{Engine: Engine{Horsepower: 100}}
m.Start() // Promoted from Engine
// Composition — must call explicitly
c := Car{Engine: Engine{Horsepower: 200}}
c.Engine.Start() // Must use field name
}
Common Mistakes
1. Name Collisions
type A struct{ X int }
type B struct{ X int }
type C struct {
A
B
}
// c.X — compile error, ambiguous
2. Forgetting to Initialize Embedded Type
type Server struct {
Logger
Host string
}
s := Server{Host: "localhost"}
// s.Info("test") // Panic! Logger is nil struct
// Fix — initialize
s = Server{
Logger: Logger{},
Host: "localhost",
}
s.Info("test") // Works
3. Pointer vs Value Embedding Confusion
type Config struct{ Port int }
type Server struct {
*Config // Pointer embedding
}
s := Server{Config: &Config{Port: 8080}}
fmt.Println(s.Port) // 8080 — works because Config is initialized
4. Overriding Without Access to Original
type Base struct{}
func (b Base) Method() { fmt.Println("base") }
type Derived struct{ Base }
func (d Derived) Method() {
// Can't call b.Base.Method() if you want the original
// Unless Base exposes it differently
}
5. Interface Embedding with Conflicting Methods
type A interface { Method() string }
type B interface { Method() int } // Same name, different return
// type C interface { A; B } // Compile error — conflicting methods
Practice Questions
1. What is embedding in Go?
Including one type within another anonymously (without a field name). The embedded type's fields and methods are promoted to the embedding type, accessible as if defined on the embedding type.
2. How do you resolve name conflicts in embedded types?
Use explicit field access: c.A.Name instead of c.Name when both A and B have a Name field. Go refuses to promote ambiguous fields.
3. What's the difference between embedding a struct and using a named field?
Embedding promotes methods and fields. Named fields require explicit access. Embedding is for "is-like-a" relationships; named fields are for "has-a" relationships.
4. Can you embed a pointer type?
Yes. type Server struct { *Config } embeds a pointer to Config. The embedded type must be initialized before use, or methods on nil pointer will panic.
Challenge: Create a MeasuredReader that embeds io.Reader and tracks bytes read and read duration.
Solution
package main
import (
"fmt"
"io"
"strings"
"sync/atomic"
"time"
)
type MeasuredReader struct {
io.Reader
bytesRead int64
duration int64 // nanoseconds
}
func (mr *MeasuredReader) Read(p []byte) (int, error) {
start := time.Now()
n, err := mr.Reader.Read(p)
elapsed := time.Since(start)
atomic.AddInt64(&mr.bytesRead, int64(n))
atomic.AddInt64(&mr.duration, elapsed.Nanoseconds())
return n, err
}
func (mr *MeasuredReader) Stats() (bytes int64, avgDuration time.Duration) {
bytes = atomic.LoadInt64(&mr.bytesRead)
totalNS := atomic.LoadInt64(&mr.duration)
if bytes > 0 {
avgDuration = time.Duration(totalNS / bytes)
}
return
}
func main() {
data := strings.NewReader("Hello, Measured Reader!")
mr := &MeasuredReader{Reader: data}
result, _ := io.ReadAll(mr)
fmt.Printf("Read: %s\n", string(result))
bytes, avg := mr.Stats()
fmt.Printf("Total bytes: %d\n", bytes)
fmt.Printf("Avg time per byte: %v\n", avg)
}
Expected output:
Read: Hello, Measured Reader!
Total bytes: 22
Avg time per byte: ~XXns
FAQ
{{< faq question="Is embedding the same as inheritance?" >}} No. Inheritance is an "is-a" relationship with polymorphism and virtual dispatch. Embedding is composition with method promotion. You can override methods but there's no dynamic dispatch or polymorphic behavior. {{< /faq >}}
{{< faq question="Can I embed multiple types with the same method?" >}}
Yes, but calling the method is ambiguous — compile error. Use explicit path to resolve: s.TypeA.Method() or s.TypeB.Method().
{{< /faq >}}
{{< faq question="Does embedding work with non-struct types?" >}}
Yes. You can embed any named type: type MyReader struct { io.Reader; buf [1024]byte }. The embedded type's methods are promoted if they don't conflict.
{{< /faq >}}
{{< faq question="Can I embed a type from another package?" >}}
Yes. The embedded type must be exported (capital letter). type MyWriter struct { io.Writer } embeds the exported Writer interface from the io package.
{{< /faq >}}
{{< faq question="How do I initialize an embedded struct?" >}
Use a struct literal with the embedded type's name: s := Server{Logger: Logger{}, Host: "localhost"}. Both named and embedded fields are initialized this way.
{{< /faq >}}
Try It Yourself
package main
import "fmt"
type Button struct {
label string
}
func (b Button) Click() {
fmt.Println(b.label, "clicked!")
}
type TextField struct {
value string
}
func (t TextField) Input(text string) {
t.value = text
fmt.Println("Text set to:", text)
}
type Form struct {
Button
TextField
Name string
}
func main() {
form := Form{
Button: Button{label: "Submit"},
TextField: TextField{},
Name: "Login Form",
}
form.Click() // Promoted from Button
form.Input("admin") // Promoted from TextField
fmt.Println(form.Name) // Own field
}
Expected output:
Submit clicked!
Text set to: admin
Login Form
What's Next
Now that you understand embedding, learn about error handling — Go's approach to explicit error management.
| Topic | Description | Link |
|---|---|---|
| Go Error Handling | Error interface, wrapping, sentinel errors | {{< ref "13-error-handling" >}} |
| Go Interfaces | Interface types, satisfaction, assertions | {{< ref "11-interfaces" >}} |
| Rust Error Handling | Compare with Rust's Result type | Rust |