Embedding in Golang
Embedding is a powerful feature in Go that allows you to include one type (struct or interface) within another type. This provides a way to compose types and achieve code reuse without inheritance. When you embed a type, the embedded type’s fields and methods become part of the embedding type, allowing for elegant composition patterns.
Struct Embedding
Struct embedding allows you to include one struct as a field in another struct without specifying a field name.
Basic Struct Embedding
package main
import "fmt"
// Base struct
type Person struct {
Name string
Age int
}
// Embedded in Employee
type Employee struct {
Person // Embedded struct
EmployeeID int
Department string
}
func main() {
emp := Employee{
Person: Person{
Name: "John Doe",
Age: 30,
},
EmployeeID: 12345,
Department: "Engineering",
}
// Access embedded fields directly
fmt.Println("Name:", emp.Name)
fmt.Println("Age:", emp.Age)
fmt.Println("Employee ID:", emp.EmployeeID)
fmt.Println("Department:", emp.Department)
// Access embedded struct explicitly
fmt.Println("Person:", emp.Person)
}Method Promotion
When you embed a struct, its methods are promoted to the embedding struct:
package main
import "fmt"
type Address struct {
Street string
City string
Country string
}
func (a Address) FullAddress() string {
return fmt.Sprintf("%s, %s, %s", a.Street, a.City, a.Country)
}
type Person struct {
Name string
Age int
Address // Embedded
}
func (p Person) Introduce() string {
return fmt.Sprintf("Hi, I'm %s, %d years old", p.Name, p.Age)
}
func main() {
person := Person{
Name: "Alice",
Age: 25,
Address: Address{
Street: "123 Main St",
City: "Anytown",
Country: "USA",
},
}
// Call promoted method
fmt.Println(person.FullAddress())
// Call own method
fmt.Println(person.Introduce())
}Field Name Conflicts
When embedded structs have conflicting field names, you must access them explicitly:
package main
import "fmt"
type Engine struct {
Type string
Power int
}
type Battery struct {
Type string
Capacity int
}
type ElectricCar struct {
Engine // Embedded
Battery // Embedded
Model string
}
func main() {
car := ElectricCar{
Engine: Engine{
Type: "Electric Motor",
Power: 150,
},
Battery: Battery{
Type: "Lithium-ion",
Capacity: 75,
},
Model: "Tesla Model 3",
}
// Access conflicting fields explicitly
fmt.Println("Engine Type:", car.Engine.Type)
fmt.Println("Battery Type:", car.Battery.Type)
fmt.Println("Engine Power:", car.Engine.Power)
fmt.Println("Battery Capacity:", car.Battery.Capacity)
fmt.Println("Model:", car.Model)
}Interface Embedding
Interface embedding allows you to compose interfaces from other interfaces.
Basic Interface Embedding
package main
import "fmt"
// Basic interfaces
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Embedded interface
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
// Implementation
type File struct {
name string
}
func (f *File) Read(p []byte) (n int, err error) {
// Implementation...
return len(p), nil
}
func (f *File) Write(p []byte) (n int, err error) {
// Implementation...
return len(p), nil
}
func (f *File) Close() error {
// Implementation...
return nil
}
func main() {
var f ReadWriteCloser = &File{name: "test.txt"}
// Can call all methods
data := []byte("hello")
f.Write(data)
buffer := make([]byte, 100)
f.Read(buffer)
f.Close()
fmt.Println("All operations completed")
}Advanced Patterns
Composition over Inheritance
package main
import "fmt"
// Behavior interfaces
type Flyer interface {
Fly()
}
type Swimmer interface {
Swim()
}
type Walker interface {
Walk()
}
// Concrete implementations
type Bird struct {
Name string
}
func (b Bird) Fly() {
fmt.Printf("%s is flying\n", b.Name)
}
func (b Bird) Walk() {
fmt.Printf("%s is walking\n", b.Name)
}
type Fish struct {
Name string
}
func (f Fish) Swim() {
fmt.Printf("%s is swimming\n", f.Name)
}
// Duck can do multiple things
type Duck struct {
Bird // Can fly and walk
Fish // Can swim (but we'll override)
}
func (d Duck) Swim() {
fmt.Printf("%s is swimming like a duck\n", d.Bird.Name)
}
func main() {
duck := Duck{
Bird: Bird{Name: "Donald"},
Fish: Fish{Name: "Donald"}, // Same name for simplicity
}
duck.Fly() // From embedded Bird
duck.Walk() // From embedded Bird
duck.Swim() // Overridden in Duck
}Embedded Mutex Pattern
package main
import (
"fmt"
"sync"
)
type SafeCounter struct {
sync.Mutex // Embedded mutex
count int
}
func (sc *SafeCounter) Increment() {
sc.Lock() // Method promoted from sync.Mutex
defer sc.Unlock()
sc.count++
}
func (sc *SafeCounter) Value() int {
sc.Lock()
defer sc.Unlock()
return sc.count
}
func main() {
counter := SafeCounter{}
for i := 0; i < 10; i++ {
go counter.Increment()
}
// Give goroutines time to complete
fmt.Println("Final count:", counter.Value())
}Embedded Logger
package main
import (
"log"
"os"
)
type Service struct {
*log.Logger // Embedded logger
name string
}
func NewService(name string) *Service {
return &Service{
Logger: log.New(os.Stdout, name+": ", log.LstdFlags),
name: name,
}
}
func (s *Service) Start() {
s.Println("Service starting") // Method promoted from log.Logger
}
func (s *Service) Stop() {
s.Println("Service stopping")
}
func main() {
service := NewService("MyService")
service.Start()
service.Stop()
}Method Overriding
When embedding types, you can override methods from the embedded type:
package main
import "fmt"
type Animal struct {
Name string
}
func (a Animal) Speak() {
fmt.Printf("%s makes a sound\n", a.Name)
}
type Dog struct {
Animal // Embedded
Breed string
}
func (d Dog) Speak() {
fmt.Printf("%s barks: Woof!\n", d.Animal.Name)
}
type Cat struct {
Animal
Color string
}
func (c Cat) Speak() {
fmt.Printf("%s meows: Meow!\n", c.Animal.Name)
}
func main() {
dog := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}
cat := Cat{
Animal: Animal{Name: "Whiskers"},
Color: "Black",
}
dog.Speak() // Overridden method
cat.Speak() // Overridden method
// Access embedded method explicitly
dog.Animal.Speak()
}Initialization and Construction
Proper initialization is important when using embedding:
package main
import "fmt"
// Component that needs initialization
type DatabaseConnection struct {
Host string
Port int
Connected bool
}
func (db *DatabaseConnection) Connect() {
// Simulate connection
db.Connected = true
fmt.Printf("Connected to %s:%d\n", db.Host, db.Port)
}
func (db *DatabaseConnection) Disconnect() {
db.Connected = false
fmt.Println("Disconnected")
}
// Service that embeds DatabaseConnection
type UserService struct {
*DatabaseConnection // Embedded pointer
ServiceName string
}
func NewUserService(host string, port int, serviceName string) *UserService {
db := &DatabaseConnection{
Host: host,
Port: port,
}
service := &UserService{
DatabaseConnection: db,
ServiceName: serviceName,
}
// Initialize the embedded component
db.Connect()
return service
}
func (us *UserService) GetUser(id int) string {
if !us.Connected {
return "Not connected"
}
return fmt.Sprintf("User %d from %s", id, us.ServiceName)
}
func main() {
service := NewUserService("localhost", 5432, "UserDB")
fmt.Println(service.GetUser(123))
service.Disconnect()
}Common Patterns
Decorator Pattern
package main
import "fmt"
type Component interface {
Operation() string
}
type ConcreteComponent struct{}
func (c ConcreteComponent) Operation() string {
return "ConcreteComponent"
}
type Decorator struct {
Component // Embedded interface
}
func (d Decorator) Operation() string {
return fmt.Sprintf("Decorator(%s)", d.Component.Operation())
}
type ConcreteDecoratorA struct {
Decorator // Embedded struct
addedState string
}
func (d ConcreteDecoratorA) Operation() string {
return fmt.Sprintf("ConcreteDecoratorA(%s, %s)", d.Component.Operation(), d.addedState)
}
func main() {
component := ConcreteComponent{}
decoratorA := ConcreteDecoratorA{
Decorator: Decorator{
Component: component,
},
addedState: "extra feature",
}
fmt.Println(decoratorA.Operation())
}Strategy Pattern
package main
import "fmt"
type SortStrategy interface {
Sort([]int)
}
type BubbleSort struct{}
func (bs BubbleSort) Sort(data []int) {
fmt.Println("Sorting with bubble sort")
// Bubble sort implementation...
}
type QuickSort struct{}
func (qs QuickSort) Sort(data []int) {
fmt.Println("Sorting with quick sort")
// Quick sort implementation...
}
type Sorter struct {
SortStrategy // Embedded interface
}
func (s Sorter) SortData(data []int) {
s.SortStrategy.Sort(data)
}
func main() {
data := []int{3, 1, 4, 1, 5}
bubbleSorter := Sorter{SortStrategy: BubbleSort{}}
bubbleSorter.SortData(data)
quickSorter := Sorter{SortStrategy: QuickSort{}}
quickSorter.SortData(data)
}Best Practices
- Use embedding for composition - Prefer composition over inheritance
- Keep embedded types simple - Embedded types should have clear, focused responsibilities
- Document embedded fields - Make it clear what types are embedded
- Avoid deep embedding hierarchies - Keep embedding levels shallow
- Use pointer embedding carefully - Be aware of nil pointer issues
- Override methods intentionally - Only override methods when you need different behavior
- Initialize embedded types properly - Ensure embedded components are properly initialized
Potential Pitfalls
Nil Pointer with Embedded Pointers
package main
import "fmt"
type Logger struct {
prefix string
}
func (l *Logger) Log(msg string) {
if l == nil {
fmt.Println("Logger is nil")
return
}
fmt.Printf("[%s] %s\n", l.prefix, msg)
}
type Service struct {
*Logger // Embedded pointer
}
func main() {
// This will panic because Logger is nil
// service := Service{}
// service.Log("test")
// Correct way
service := Service{
Logger: &Logger{prefix: "INFO"},
}
service.Log("Service started")
// Or initialize in constructor
service2 := &Service{}
service2.Logger = &Logger{prefix: "DEBUG"}
service2.Log("Service initialized")
}Method Name Conflicts
package main
import "fmt"
type A struct {
value int
}
func (a A) Get() int {
return a.value
}
type B struct {
value int
}
func (b B) Get() int {
return b.value * 2
}
type C struct {
A // Embedded
B // Embedded
}
func main() {
c := C{
A: A{value: 10},
B: B{value: 5},
}
// This would be ambiguous
// fmt.Println(c.Get())
// Must call explicitly
fmt.Println("A.Get():", c.A.Get())
fmt.Println("B.Get():", c.B.Get())
}External Resources:
Related Tutorials: