Loading...
Loading...

Go Structures Tutorial

Structures (structs) are the fundamental building blocks for organizing and working with complex data in Go. Unlike classical object-oriented languages, Go uses structs to create custom types that group together related data fields. This tutorial will guide you through all aspects of working with structs in Go.

1. Struct Basics

1.1 What Are Structs?

Structs are composite data types that allow you to combine fields of different types under a single name. They are similar to classes in other languages but are more lightweight and don't support inheritance.

Key characteristics of structs:

  • Group related data together
  • Can contain fields of any type (including other structs)
  • Value types (copied when assigned or passed to functions)
  • Support methods (like class methods in OOP)

1.2 Defining Structs

Struct definitions specify the name and type of each field. The basic syntax is:

type StructName struct {
    Field1 Type1
    Field2 Type2
    // ...
}

Example with a Person struct:

type Person struct {
    FirstName string
    LastName  string
    Age       int
    Height    float64
}

1.3 Creating Struct Instances

There are several ways to create struct instances in Go:

1. Zero-value initialization

Creates a struct with all fields set to their zero values:

var p Person // All fields get zero values

2. Literal initialization (positional)

Initialize fields in order (must include all fields):

p := Person{"John", "Doe", 30, 1.75}

3. Literal initialization (named fields)

More readable and flexible approach:

p := Person{
    FirstName: "Jane",
    Age:       25,
    Height:    1.68,
    // LastName omitted (gets zero value)
}

4. Using new()

Allocates memory and returns a pointer:

pPtr := new(Person)
pPtr.FirstName = "Mike" // Access fields through pointer

1.4 Accessing Struct Fields

Struct fields are accessed using dot notation, whether working with the struct directly or through a pointer:

// Direct access
person := Person{"John", "Doe", 30, 1.75}
fmt.Println(person.FirstName) // "John"

// Pointer access (automatic dereferencing)
personPtr := &person
fmt.Println(personPtr.LastName) // "Doe" - no need for (*personPtr).LastName

2. Struct Methods

2.1 Understanding Methods

Methods in Go are functions that operate on a specific type (the receiver). Struct methods allow you to define behavior associated with your data.

Key points about methods:

  • Defined with a special receiver parameter before the function name
  • Can be value receivers (operate on a copy) or pointer receivers (operate on original)
  • Support the same features as regular functions

2.2 Value Receivers

Value receivers work on a copy of the struct. Use these when:

  • The method doesn't need to modify the receiver
  • You want to work with an immutable copy
  • The struct is small (copying is efficient)
// Value receiver example
func (p Person) FullName() string {
    return p.FirstName + " " + p.LastName
}

// Usage
person := Person{"John", "Doe", 30, 1.75}
name := person.FullName() // Works on a copy of person

2.3 Pointer Receivers

Pointer receivers work on the original struct. Use these when:

  • The method needs to modify the receiver
  • The struct is large (avoid copying overhead)
  • You want consistent behavior (if some methods need pointers, use for all)
// Pointer receiver example
func (p *Person) Birthday() {
    p.Age++ // Modifies the original struct
}

// Usage
person := Person{"John", "Doe", 30, 1.75}
person.Birthday() // Age is now 31

2.4 Method Selection Guidelines

Choosing between value and pointer receivers:

Scenario Recommended Receiver
Method needs to modify receiver Pointer
Struct contains sync.Mutex Pointer
Struct is large (> 64 bytes) Pointer
Method only reads data Value
Small struct (< 64 bytes) Either (value often preferred)

3. Composition and Embedding

3.1 Struct Embedding

Go favors composition over inheritance. Struct embedding allows one struct to include another struct's fields and methods.

type ContactInfo struct {
    Email string
    Phone string
}

type Employee struct {
    Name        string
    ContactInfo // Embedded struct (no field name)
}

// Usage
emp := Employee{
    Name: "John Doe",
    ContactInfo: ContactInfo{
        Email: "john@example.com",
        Phone: "123-456-7890",
    },
}

// Fields are promoted
fmt.Println(emp.Email) // Works directly

3.2 Method Promotion

When you embed a struct, its methods are also promoted to the containing struct:

type Writer struct {
    Name string
}

func (w Writer) Write(message string) {
    fmt.Printf("%s writes: %s\n", w.Name, message)
}

type Author struct {
    Writer // Embed Writer
    Book   string
}

// Usage
author := Author{
    Writer: Writer{Name: "Jane Austen"},
    Book:   "Pride and Prejudice",
}

author.Write("It is a truth universally acknowledged...")
// Output: "Jane Austen writes: It is a truth universally acknowledged..."

3.3 Overriding Embedded Methods

You can override methods from embedded types by defining a method with the same name:

func (a Author) Write(message string) {
    fmt.Printf("%s, author of %s, writes: %s\n", 
        a.Name, a.Book, message)
}

// Now calling Write() will use Author's version
author.Write("New novel coming soon")

4. Advanced Struct Techniques

4.1 Field Tags

Struct tags are string literals that provide metadata about struct fields, commonly used for JSON/XML serialization and validation.

type User struct {
    ID        int       `json:"id" db:"user_id"`
    Username  string    `json:"username" validate:"required,min=3"`
    Password  string    `json:"-"`                  // Skip in JSON
    CreatedAt time.Time `json:"created_at" db:"created_at"`
}

// Usage with JSON
user := User{
    ID:        1,
    Username:  "admin",
    Password:  "secret",
    CreatedAt: time.Now(),
}

jsonData, _ := json.Marshal(user)
// {"id":1,"username":"admin","created_at":"2023-07-20T12:00:00Z"}

4.2 Anonymous Structs

Go supports anonymous structs - structs defined without a type name, useful for one-time use cases.

// Inline struct definition and initialization
config := struct {
    Timeout time.Duration
    Retries int
}{
    Timeout: 5 * time.Second,
    Retries: 3,
}

// Common in tests
testCases := []struct {
    input    string
    expected int
    hasError bool
}{
    {"123", 123, false},
    {"abc", 0, true},
}

4.3 Struct Comparison

Structs can be compared if all their fields are comparable. Pointer fields are compared by address, not value.

type Point struct {
    X, Y int
}

p1 := Point{1, 2}
p2 := Point{1, 2}
p3 := Point{2, 3}

fmt.Println(p1 == p2) // true
fmt.Println(p1 == p3) // false

// For structs with non-comparable fields (like slices),
// use reflect.DeepEqual()

5. Memory Layout and Performance

5.1 Struct Field Ordering

Field order affects memory usage due to padding. Proper ordering can reduce struct size.

// Bad ordering - wastes space (24 bytes)
type Inefficient struct {
    a bool     // 1 byte
    b int64    // 8 bytes
    c bool     // 1 byte
    // 6 bytes padding
}

// Better ordering - only 16 bytes
type Efficient struct {
    b int64    // 8 bytes
    a bool     // 1 byte
    c bool     // 1 byte
    // 6 bytes padding can be used for other fields
}

5.2 Pointer vs Value Semantics

Understanding when to use pointers to structs versus direct values:

Use Case Recommendation
Function parameters Pointer for large structs or when modification needed
Return values Pointer for large structs, value for small
Collections (slices, maps) Pointers if structs are large or need modification
Concurrent access Pointers with proper synchronization

6. Common Struct Patterns

6.1 Constructor Functions

Idiomatic way to initialize structs with validation:

func NewPerson(name string, age int) (*Person, error) {
    if name == "" {
        return nil, errors.New("name cannot be empty")
    }
    if age <= 0 {
        return nil, errors.New("age must be positive")
    }
    return &Person{Name: name, Age: age}, nil
}

// Usage
person, err := NewPerson("Alice", 25)
if err != nil {
    // handle error
}

6.2 Builder Pattern

For complex struct initialization with many optional fields:

type Config struct {
    Timeout  time.Duration
    Retries  int
    Logger   io.Writer
}

type ConfigBuilder struct {
    config Config
}

func (b *ConfigBuilder) WithTimeout(timeout time.Duration) *ConfigBuilder {
    b.config.Timeout = timeout
    return b
}

func (b *ConfigBuilder) Build() Config {
    // Set defaults and validate
    if b.config.Timeout == 0 {
        b.config.Timeout = 5 * time.Second
    }
    return b.config
}

// Usage
cfg := ConfigBuilder{}.
    WithTimeout(10 * time.Second).
    Build()
0 Interaction
0 Views
Views
0 Likes
×
×
×
🍪 CookieConsent@Ptutorials:~

Welcome to Ptutorials

$ Allow cookies on this site ? (y/n)

top-home