Skip to main content
โšก Calmops

Go Structs: Composition and Embedding

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:

  1. Grouping related data with fields
  2. Methods through receivers
  3. Composition through embedding
  4. Method promotion from embedded types
  5. Serialization through struct tags

These features enable clean, composable Go code.

Comments