Go Structs: Composition and Embedding
Structs are Go’s way of grouping related data. They enable composition-based design and are fundamental to Go’s approach to object-oriented programming.
Struct Definition
Basic Struct
package main
import "fmt"
type Person struct {
Name string
Age int
Email string
}
func main() {
// Create struct with field names
p := Person{
Name: "Alice",
Age: 30,
Email: "[email protected]",
}
fmt.Println(p.Name, p.Age)
}
Struct Initialization
package main
import "fmt"
type Point struct {
X, Y float64
}
func main() {
// Positional initialization
p1 := Point{10, 20}
fmt.Println(p1)
// Named field initialization
p2 := Point{X: 30, Y: 40}
fmt.Println(p2)
// Partial initialization (zero values for missing fields)
p3 := Point{X: 50}
fmt.Println(p3) // {50 0}
// Empty struct
p4 := Point{}
fmt.Println(p4) // {0 0}
}
Struct Methods
Receiver Methods
package main
import (
"fmt"
"math"
)
type Circle struct {
Radius float64
}
// Method with value receiver
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// Method with pointer receiver
func (c *Circle) SetRadius(r float64) {
c.Radius = r
}
func main() {
c := Circle{Radius: 5}
fmt.Println(c.Area()) // 78.53981633974483
c.SetRadius(10)
fmt.Println(c.Area()) // 314.1592653589793
}
Composition
Embedding Structs
package main
import "fmt"
type Address struct {
Street string
City string
State string
}
type Person struct {
Name string
Age int
Address Address
}
func main() {
p := Person{
Name: "Alice",
Age: 30,
Address: Address{
Street: "123 Main St",
City: "New York",
State: "NY",
},
}
fmt.Println(p.Name)
fmt.Println(p.Address.City)
}
Anonymous Embedding
package main
import "fmt"
type Address struct {
Street string
City string
}
type Person struct {
Name string
Address // Anonymous embedding
}
func main() {
p := Person{
Name: "Alice",
Address: Address{
Street: "123 Main St",
City: "New York",
},
}
// Access embedded fields directly
fmt.Println(p.Name)
fmt.Println(p.City) // Direct access to embedded field
}
Method Promotion
package main
import "fmt"
type Reader struct {
data string
}
func (r Reader) Read() string {
return r.data
}
type Document struct {
Reader // Embedded struct
Title string
}
func main() {
doc := Document{
Reader: Reader{data: "Content"},
Title: "My Document",
}
// Method from embedded struct is promoted
fmt.Println(doc.Read()) // Content
}
Struct Tags
Field Tags
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
}
func main() {
user := User{ID: 1, Name: "Alice", Email: "[email protected]"}
// Marshal to JSON
data, _ := json.Marshal(user)
fmt.Println(string(data))
// Output: {"id":1,"name":"Alice","email":"[email protected]"}
// Unmarshal from JSON
jsonStr := `{"id":2,"name":"Bob"}`
var u User
json.Unmarshal([]byte(jsonStr), &u)
fmt.Println(u)
}
Custom Tag Parsing
package main
import (
"fmt"
"reflect"
)
type Config struct {
Host string `config:"host"`
Port int `config:"port"`
}
func parseConfig(v interface{}) map[string]interface{} {
result := make(map[string]interface{})
t := reflect.TypeOf(v)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("config")
if tag != "" {
result[tag] = reflect.ValueOf(v).Field(i).Interface()
}
}
return result
}
func main() {
cfg := Config{Host: "localhost", Port: 8080}
parsed := parseConfig(cfg)
fmt.Println(parsed)
}
Pointers to Structs
Pointer Receivers
package main
import "fmt"
type Counter struct {
count int
}
// Value receiver - doesn't modify original
func (c Counter) Get() int {
return c.count
}
// Pointer receiver - modifies original
func (c *Counter) Increment() {
c.count++
}
func main() {
counter := Counter{count: 0}
counter.Increment()
counter.Increment()
fmt.Println(counter.Get()) // 2
}
Best Practices
โ Good: Use Composition
// DO: Use composition for code reuse
type Logger struct {
// fields
}
type Service struct {
Logger // Embedded
// other fields
}
โ Bad: Deep Inheritance
// DON'T: Create deep hierarchies
type A struct { /* ... */ }
type B struct { A }
type C struct { B }
type D struct { C }
โ Good: Pointer Receivers for Mutations
// DO: Use pointer receivers when modifying
func (p *Person) SetAge(age int) {
p.Age = age
}
โ Good: Value Receivers for Immutable Operations
// DO: Use value receivers for read-only operations
func (p Person) GetName() string {
return p.Name
}
โ Good: Use Struct Tags for Serialization
// DO: Use tags for JSON marshaling
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
Summary
Go structs provide:
- Grouping related data with fields
- Methods through receivers
- Composition through embedding
- Method promotion from embedded types
- Serialization through struct tags
These features enable clean, composable Go code.
Comments