Post

07. Structs and Methods

🚀 Master Go's structs and methods! Learn how to define structs, leverage embedded fields, understand method sets, and use struct tags effectively. This post empowers you to build robust and organized Go applications. ✨

07. Structs and Methods

What we will learn in this post?

  • 👉 Defining Structs
  • 👉 Anonymous Structs and Embedded Fields
  • 👉 Methods in Go
  • 👉 Pointer vs Value Receivers
  • 👉 Method Sets
  • 👉 Struct Tags
  • 👉 Conclusion!

Structs in Go 🚀

Go structs are like blueprints for creating custom data types. They bundle together different fields, each with its own type.

Defining Structs 📝

To define a struct, we use the type keyword followed by the struct’s name and the struct keyword, and then list the fields and their types within curly braces:

1
2
3
4
type Person struct {
    Name string
    Age  int
}

Here, Person is a struct with two fields: Name (a string) and Age (an integer).

graph LR
    S_DECL["Struct Declaration"]:::javaStyle --> S_FIELD["Fields: Name, Age"]:::jdbcStyle
    S_FIELD --> S_INST["Instance Creation"]:::driverStyle
    S_INST --> S_USE["Use Struct"]:::useStyle

    classDef javaStyle fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
    classDef jdbcStyle fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
    classDef driverStyle fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
    classDef useStyle fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;

    class S_DECL javaStyle;
    class S_FIELD jdbcStyle;
    class S_INST driverStyle;
    class S_USE useStyle;

    linkStyle default stroke:#e67e22,stroke-width:3px;

Initializing Structs ✨

We can initialize structs in a few ways:

  • Struct Literals:
1
person1 := Person{"Alice", 30} // Order matters!
  • Field: Value Syntax:
1
person2 := Person{Name: "Bob", Age: 25} // Order doesn't matter!
  • new() Function:
1
2
3
person3 := new(Person) // Returns a pointer to a zero-valued Person
person3.Name = "Charlie"
person3.Age = 40

Nested Structs 🏠

Structs can contain other structs as fields, creating nested structures:

1
2
3
4
5
6
7
8
9
10
11
type Address struct {
    Street string
    City   string
}

type Employee struct {
    Name    string
    Address Address // Nested struct
}

employee1 := Employee{Name: "David", Address: Address{Street: "123 Main St", City: "Anytown"}}
graph LR
    EMP["Employee Struct"]:::javaStyle --> EMP_NAME["Name Field"]:::jdbcStyle
    EMP --> EMP_ADDR["Address Field"]:::driverStyle
    EMP_ADDR --> ADDR["Address Struct"]:::dbStyle
    ADDR --> ADDR_STREET["Street Field"]:::jdbcStyle
    ADDR --> ADDR_CITY["City Field"]:::jdbcStyle

    classDef javaStyle fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
    classDef jdbcStyle fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
    classDef driverStyle fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
    classDef dbStyle fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;

    class EMP javaStyle;
    class EMP_NAME jdbcStyle;
    class EMP_ADDR driverStyle;
    class ADDR dbStyle;
    class ADDR_STREET jdbcStyle;
    class ADDR_CITY jdbcStyle;

    linkStyle default stroke:#e67e22,stroke-width:3px;

Structs in Go: One-Time Use & Composition 🧩

#

FeatureStruct Embedding (Composition)Inheritance (Classical OOP)
Relationship“Has-a” (contains another type)“Is-a” (inherits from another type)
Supported in Go?YesNo
Field PromotionYes (fields promoted to outer type)No (fields accessed via parent class)
Code ReuseBy embeddingBy inheritance
FlexibilityHigh (can embed multiple types)Lower (single inheritance chain)
CouplingLooseTight

Let’s explore structs in Go, focusing on anonymous structs for one-off situations and struct embedding for building complex types.

Anonymous Structs 👻

FeatureAnonymous StructsNamed Structs
DefinitionDefined inline, no type nameDefined with a type name
Usage ScopeOne-off, temporaryReusable, used in multiple places
Syntaxvar x = struct { ... }{ ... }type X struct { ... }
Typical Use CaseQuick grouping, function paramsData models, application logic
ReadabilityLower for complex structsHigher, especially for large structs

Anonymous structs are defined without a name and are useful when you need a struct type only once.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
    person := struct {
        Name string
        Age  int
    }{
        Name: "Alice",
        Age:  30,
    }

    fmt.Println(person) // Output: {Alice 30}
}

Struct Embedding: Composition over Inheritance 🏗️

Go doesn’t have inheritance in the traditional sense. Instead, it uses struct embedding which provides a powerful composition mechanism.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

type Address struct {
    Street string
    City   string
}

type Employee struct {
    ID   int
    Address // Embedded field
}

func main() {
    emp := Employee{
        ID: 123,
        Address: Address{
            Street: "1 Main St",
            City:   "Anytown",
        },
    }

    fmt.Println(emp.Street) // **Promoted field!** Output: 1 Main St
    fmt.Println(emp.Address.City) //Output: Anytown
}
  • Embedded Fields: Address is embedded in Employee.
  • Promotion: Fields from the embedded struct are promoted to the outer struct. So emp.Street directly accesses the Street field of the embedded Address struct.

Composition vs. Inheritance ⚖️

graph LR
    EMP_E["Employee Struct"]:::javaStyle --> ADDR_E["Embedded Address Struct"]:::dbStyle
    ADDR_E --> ADDR_STREET_E["Street"]:::jdbcStyle
    ADDR_E --> ADDR_CITY_E["City"]:::jdbcStyle
    EMP_E --> EMP_ID["ID Field"]:::driverStyle
    EMP_E --> PROMO["Promoted: emp.Street"]:::useStyle

    classDef javaStyle fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
    classDef jdbcStyle fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
    classDef driverStyle fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
    classDef dbStyle fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
    classDef useStyle fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;

    class EMP_E javaStyle;
    class ADDR_E dbStyle;
    class ADDR_STREET_E jdbcStyle;
    class ADDR_CITY_E jdbcStyle;
    class EMP_ID driverStyle;
    class PROMO useStyle;

    linkStyle default stroke:#e67e22,stroke-width:3px;
  • Composition: Creates a “has-a” relationship. Employee has an Address. It’s flexible and avoids the tight coupling of inheritance.
  • Inheritance: Creates an “is-a” relationship. Not directly supported in Go. Composition provides the benefits of code reuse without the complexities of inheritance hierarchies.

Instead of thinking of Employee inheriting from Address, think of Employee containing an Address and conveniently providing direct access to its fields. Check out this awesome blog to know more.

Methods on Structs in Go ⚙️

Go lets you add behaviors to your struct types using methods. Think of it like giving your data abilities!

Defining Methods with Receiver Functions 🤝

The magic happens with a receiver function. Here’s how it looks:

1
2
3
func (r MyStruct) MyMethod() {
  // Your code here, accessing fields of 'r'
}
  • func: Keyword for defining a function.
  • (r MyStruct): The receiver. r is a variable (like this or self in other languages) that refers to an instance of MyStruct.
  • MyMethod(): The name of your method.

Calling Methods and Chaining 📞🔗

You call methods using the . operator:

1
2
myVar := MyStruct{ /* ... */ }
myVar.MyMethod()

If a method returns a value that allows you to call another method on it directly, you can use method chaining:

1
result := myVar.Method1().Method2().Method3()

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Rectangle struct {
    Width  int
    Height int
}

func (r Rectangle) Area() int {
    return r.Width * r.Height
}

func main() {
    myRect := Rectangle{Width: 10, Height: 5}
    area := myRect.Area() // calling the Area method.
    println(area) // output is 50
}

More Info

Pointer vs. Value Receivers in Go 🛠️

In Go, methods can have either pointer receivers (*T) or value receivers (T). This choice impacts how the method interacts with the underlying data. Let’s break it down!

Understanding the Difference 🧐

  • Value Receiver (T): The method operates on a copy of the original struct. Changes made inside the method do not affect the original struct. Think of it like making a photocopy and editing it. 📝

  • Pointer Receiver (*T): The method operates directly on the original struct. Changes made inside the method do affect the original struct. Think of it like directly editing a document. 💻

FeatureValue Receiver (T)Pointer Receiver (*T)
Operates OnCopy of structOriginal struct (reference)
Can Modify Original?NoYes
Use CaseRead-only methodsMethods that modify struct fields
PerformanceMay be costly for large structsEfficient for large structs
Interface SatisfactionSatisfies interface with valueSatisfies interface with pointer
Method Callobj.Method()(&obj).Method() or obj.Method()

When to Use Which? 🤔

  • Mutation (Changing Data): Use a pointer receiver (*T) if the method needs to modify the struct’s fields. If we want to save the changes. ✅
  • Read-Only (No Changes): Use a value receiver (T) if the method only needs to read the struct’s fields and doesn’t modify them. 📖
  • Large Structs (Performance): Use a pointer receiver (*T) for large structs to avoid copying the entire struct every time the method is called. This can significantly improve performance. 🚀

Go’s Magic: Automatic Conversions ✨

Go automatically handles conversions. You can call a method with a pointer receiver on a value, and Go will automatically take the address. Similarly, you can call a method with a value receiver on a pointer, and Go will automatically dereference it.

Comparative Examples 💡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Circle struct {
    Radius int
}

// Value receiver - Doesn't modify the original.
func (c Circle) AreaValue() int {
    c.Radius = 100 // This DOES NOT affect the original circle
    return c.Radius * c.Radius
}

// Pointer receiver - Modifies the original.
func (c *Circle) DoubleRadius() {
    c.Radius = c.Radius * 2 // This affects the original circle.
}

func main() {
    myCircle := Circle{Radius: 5}

    area := myCircle.AreaValue() //Calls area value on value reciever
 println("Area:",area)       //prints 10000 since value reciever works on a copy.

    myCircle.DoubleRadius()
    println("Radius:", myCircle.Radius) //prints Radius 10
}

Summary

  • Value Reciever Use if you only have to read the data.
  • Pointer Reciever Use if you have to read and modify the data.

Resources on GO programming

Here’s an explanation of method sets in Go, designed to be friendly and easy to understand!

Method Sets in Go: 🧐 What Are They?

Method sets determine which methods a type has. This is super important for interfaces!

Value vs. Pointer Receivers

  • A value receiver gets a copy of the value. Both a T and *T (pointer to T) can call these methods.
  • A pointer receiver gets the actual value’s address. Only *T can directly call these methods. A T can call it if Go can automatically get its address.

Interface Satisfaction and Method Sets

A type satisfies an interface if its method set includes all the interface’s methods.

  • If an interface has pointer receiver methods, only a *T can satisfy it.
  • If an interface has value receiver methods, both T and *T can satisfy it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type MyInterface interface {
  MethodWithPointerReceiver()
}

type MyType struct {}

func (t *MyType) MethodWithPointerReceiver() {} // *MyType implements MyInterface

func main() {
  var i MyInterface
  mt := MyType{}
  i = &mt // Correct!
  //i = mt  // Error: MyType does not implement MyInterface (MethodWithPointerReceiver has pointer receiver)
}

Example: Seeing it in Action 🚀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Number int

// Value receiver
func (n Number) String() string {
  return fmt.Sprintf("Value: %d", n)
}

// Pointer receiver
func (n *Number) Increment() {
  *n++
}

func main() {
  num := Number(5)
  fmt.Println(num.String())   // Works: Value receiver can be called on value
  (&num).Increment()      // Works: Pointer receiver called on pointer
  fmt.Println(num.String())
}

In the example:

  • String() is in the method set of both Number and *Number.
  • Increment() is only in the method set of *Number.
  • More Info: Check out this article about method sets. Go Method Sets

Here’s a friendly explanation of struct tags in Go:

Struct Tags Explained 🏷️

Struct tags are little pieces of metadata you can add to the fields of a struct in Go. They’re written inside backticks ` after the field’s type. Think of them as instructions for tools like JSON encoders, validators, or database mappers.

What are they for? 🤔

  • JSON Marshaling (json:"name"): Tells the json package how to name a field when encoding to JSON. For example: FieldName stringjson:”field_name” will make the JSON key field_name`.

  • Validation: Libraries can use tags to define validation rules. Name stringvalidate:”required,min=3” might specify that Name` must not be empty and have at least 3 characters.

  • Database ORM Mapping: Help ORMs (Object-Relational Mappers) map struct fields to database columns. ID intdb:”user_id,primarykey” could indicate that the IDfield corresponds to theuser_id` column and is a primary key.

How to Use Them? 🚀

1
2
3
4
type User struct {
    ID   int    `json:"id" db:"user_id,primarykey"`
    Name string `json:"name" validate:"required"`
}
  • Syntax: FieldName Typetag1:”value1” tag2:”value2”

  • Accessing Tags: You need to use reflection to read tag values at runtime:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
 "fmt"
 "reflect"
)

type Person struct {
 Name string `json:"name,omitempty" validate:"required"`
 Age  int    `json:"age"`
}

func main() {
 personType := reflect.TypeOf(Person{})

 for i := 0; i < personType.NumField(); i++ {
  field := personType.Field(i)
  jsonTag := field.Tag.Get("json")
  validateTag := field.Tag.Get("validate")

  fmt.Printf("Field: %s, JSON Tag: %s, Validate Tag: %s\n", field.Name, jsonTag, validateTag)
 }
}

This code snippet shows how to inspect the tags of a struct’s fields. The reflect package is used to get the type information of the struct and then iterate over its fields. For each field, the Tag.Get method is used to retrieve the value of the json and validate tags. Reflection in Go

Practical example 💡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
 "encoding/json"
 "fmt"
)

type User struct {
 ID     int    `json:"id"`
 Name   string `json:"user_name"`
 Email  string `json:"email,omitempty"` // omitempty: will hide if email is ""
 passwd string `json:"-"`             //-: will hide password
}

func main() {
 user := User{ID: 1, Name: "John Doe", Email: "john.doe@example.com", passwd: "securePassword"}

 jsonData, err := json.Marshal(user)
 if err != nil {
  fmt.Println("Error marshaling JSON:", err)
  return
 }

 fmt.Println(string(jsonData))
}

output:

1
{ "id": 1, "user_name": "John Doe", "email": "john.doe@example.com" }

Here, the Name Field in Json will be user_name, Email will hide when empty and passwd will hide.

Tags offer a powerful way to customize how Go structs interact with external systems and libraries, making your code more flexible and expressive.

Option 3:

Conclusion

Alright, we’ve reached the end! 😊 I’m really keen to know what you think. Any comments, questions, or suggestions? Please share them below! Your input helps make this even better. 👇 Looking forward to reading them! 🚀

This post is licensed under CC BY 4.0 by the author.