Chapter 2: Functions, Structs & Packages

Writing Structured Go Code


1. Why This Chapter Matters

In Python, structuring code usually revolves around:

  • Classes
  • Dynamic typing
  • Duck typing
  • Flexible imports

In Go, structuring code is about:

  • Explicit types
  • Composition over inheritance
  • Clear package boundaries
  • Predictable design

This chapter teaches how to write structured, maintainable Go code, the way production systems expect it.


2. Functions in Go

2.1 Basic Function Syntax

func add(a int, b int) int {
    return a + b
}

Unlike Python:

def add(a, b):
    return a + b

In Go:

  • Types are mandatory
  • Return type must be declared
  • No implicit dynamic behavior

2.2 Multiple Return Values (Very Important)

Go commonly returns:

  • Value
  • Error
func divide(a int, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("cannot divide by zero")
    }
    return a / b, nil
}

Usage:

result, err := divide(10, 2)
if err != nil {
    log.Fatal(err)
}

This is a fundamental mindset shift from Python exceptions.

In Python:

try:
    result = divide(10, 2)
except Exception as e:
    print(e)

In Go:

  • Errors are values
  • Error handling is explicit
  • You must check them

This makes code verbose — but predictable and safe.


2.3 Named Return Values

func rectangle(width int, height int) (area int) {
    area = width * height
    return
}

⚠ Use sparingly. Clarity > cleverness.


2.4 Variadic Functions

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

Similar to Python’s *args.


2.5 First-Class Functions

Functions can be passed around:

func operate(a int, b int, fn func(int, int) int) int {
    return fn(a, b)
}

Used heavily in middleware patterns later (Chapter 7).


3. Structs: Go’s Core Building Block

Go does NOT have classes.

Instead, it has:

Struct + Methods


3.1 Basic Struct

type User struct {
    ID    int
    Name  string
    Email string
}

Creating an instance:

u := User{
    ID:    1,
    Name:  "Aditya",
    Email: "aditya@example.com",
}

Or:

u := User{}
u.Name = "Aditya"

3.2 Structs Are Value Types

This is critical.

func updateName(u User) {
    u.Name = "Updated"
}

This won’t modify the original struct.

Why?

Because Go passes structs by value.

To modify:

func updateName(u *User) {
    u.Name = "Updated"
}

Now you’re using pointers.


4. Methods on Structs

Go attaches methods to types.

func (u User) Display() string {
    return fmt.Sprintf("%s (%s)", u.Name, u.Email)
}

This is called a value receiver.


4.1 Pointer Receivers

func (u *User) UpdateEmail(newEmail string) {
    u.Email = newEmail
}

Use pointer receivers when:

  • Modifying struct
  • Avoiding large copy
  • Struct contains mutable state

Rule of thumb:

If one method uses pointer receiver, most methods should.

Consistency matters.


5. Composition Over Inheritance

Python supports inheritance.

Go does NOT.

Instead:

type Address struct {
    City    string
    Country string
}

type Customer struct {
    Name    string
    Address Address
}

Or embedding:

type Customer struct {
    Name string
    Address
}

Now Customer gets Address fields promoted.

This is Go’s version of “inheritance” — but safer.


6. Export Rules (Very Important)

Capital letter = exported Lowercase = private

type user struct {        // not exported
    name string           // not exported
}

type User struct {        // exported
    Name string           // exported
}

This replaces Python’s “convention-based privacy”.


7. Constructors (Idiomatic Pattern)

Go has no constructors.

Instead:

func NewUser(name string, email string) *User {
    return &User{
        Name:  name,
        Email: email,
    }
}

Convention:

  • Constructor starts with New
  • Returns pointer usually

8. Packages & Project Structure

8.1 Why Packages Matter

Go enforces modularity strongly.

Unlike Python where circular imports are common, Go pushes clean boundaries.


8.2 Creating a Module

go mod init github.com/practice-golang-for-beginners/chapter2

8.3 Basic Project Structure

project/
├── main.go
├── go.mod
└── user/
    └── user.go

In user.go:

package user

In main.go:

import "project/user"

8.4 Avoid This Mistake

Don’t create deep nesting early.

Prefer:

internal/
pkg/
cmd/

(We’ll revisit in Chapter 8.)


9. Zero Values (Critical Concept)

Go gives every type a default value.

  • int → 0
  • string → “”
  • bool → false
  • struct → zeroed fields
  • pointer → nil

Example:

var u User
fmt.Println(u.Name) // empty string

This reduces need for constructors in simple cases.


10. Idiomatic Design Principles

As a mentor, emphasize:

1. Keep functions small

2. Avoid unnecessary abstraction

3. Prefer composition

4. Return errors, don’t panic

5. Make zero value useful


11. Common Python → Go Mistakes

❌ Trying to simulate classes ❌ Overusing interfaces too early ❌ Ignoring error values ❌ Using global variables ❌ Writing Java-style deep hierarchies

Teach simplicity.

Go rewards clarity over cleverness.


12. Mini Design Example

Design a simple Order system:

type Order struct {
    ID     int
    Amount float64
}

func (o Order) IsLarge() bool {
    return o.Amount > 1000
}

Then:

  • Add constructor
  • Add validation
  • Add method with pointer receiver
  • Move to package

This becomes an exercise next.


13. End of Chapter Learning Outcome

By the end of this chapter, the learner should:

  • Understand Go function signatures
  • Handle multiple return values
  • Write and attach methods
  • Understand pointer vs value receivers
  • Design clean structs
  • Organize code into packages
  • Follow export conventions

14. Please check the Excercises for Practice and Solutions


This site uses Just the Docs, a documentation theme for Jekyll.