1. go
  2. /basics
  3. /functions

Understanding Functions in Go Programming

Functions are fundamental building blocks in Go programming. This guide covers everything you need to know about working with functions effectively.

Function Declaration

Basic Function

func functionName(parameter1 type1, parameter2 type2) returnType {
    // Function body
    return value
}

// Example
func add(x int, y int) int {
    return x + y
}

Multiple Parameters of Same Type

func add(x, y int) int {
    return x + y
}

func process(name, title string, age int) {
    // Process data
}

Multiple Return Values

func divide(x, y float64) (float64, error) {
    if y == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return x / y, nil
}

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

Named Return Values

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return  // Naked return
}

// Alternative explicit return
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return x, y
}

Function Parameters

Value Parameters

func double(x int) int {
    x = x * 2  // Modifies local copy
    return x
}

// Usage
num := 5
result := double(num)  // num remains unchanged

Pointer Parameters

func doublePtr(x *int) {
    *x = *x * 2  // Modifies original value
}

// Usage
num := 5
doublePtr(&num)  // num is modified

Variadic Parameters

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

// Usage
total := sum(1, 2, 3, 4, 5)
numbers := []int{1, 2, 3, 4, 5}
total = sum(numbers...)  // Spread operator

Function Types

Function as a Type

type Operation func(x, y int) int

func calculate(op Operation, x, y int) int {
    return op(x, y)
}

// Usage
func add(x, y int) int { return x + y }
func sub(x, y int) int { return x - y }

result := calculate(add, 10, 5)  // 15
result = calculate(sub, 10, 5)   // 5

Anonymous Functions

// Immediate execution
result := func(x, y int) int {
    return x + y
}(10, 5)

// Assign to variable
operation := func(x, y int) int {
    return x * y
}
result = operation(10, 5)

Closures

func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

// Usage
increment := counter()
fmt.Println(increment())  // 1
fmt.Println(increment())  // 2

Methods

Method Declaration

type Rectangle struct {
    width, height float64
}

func (r Rectangle) Area() float64 {
    return r.width * r.height
}

func (r *Rectangle) Scale(factor float64) {
    r.width *= factor
    r.height *= factor
}

// Usage
rect := Rectangle{width: 10, height: 5}
area := rect.Area()
rect.Scale(2)

Value vs Pointer Receivers

type Point struct{ x, y float64 }

// Value receiver - works on copy
func (p Point) Move(dx, dy float64) Point {
    return Point{p.x + dx, p.y + dy}
}

// Pointer receiver - modifies original
func (p *Point) MoveInPlace(dx, dy float64) {
    p.x += dx
    p.y += dy
}

Error Handling

Error Return Pattern

func divide(x, y float64) (float64, error) {
    if y == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return x / y, nil
}

// Usage with multiple return values
if result, err := divide(10, 0); err != nil {
    log.Printf("Error: %v", err)
} else {
    fmt.Printf("Result: %f", result)
}

Custom Error Types

type ValidationError struct {
    Field string
    Error string
}

func (v *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", v.Field, v.Error)
}

func validate(age int) error {
    if age < 0 {
        return &ValidationError{
            Field: "age",
            Error: "must be positive",
        }
    }
    return nil
}

Defer, Panic, and Recover

Defer Statement

func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()  // Executed when function returns

    // Process file...
    return nil
}

Panic and Recover

func mayPanic() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("Recovered from panic: %v\n", r)
        }
    }()

    panic("something bad happened")
}

Best Practices

1. Function Naming

// Good: Clear and descriptive names
func calculateTotal(items []Item) float64
func validateUser(user *User) error
func parseConfig(filename string) (*Config, error)

// Avoid: Unclear or ambiguous names
func process(data interface{}) interface{}
func doStuff(x int) int

2. Parameter Grouping

// Good: Related parameters grouped in struct
type UserOptions struct {
    Name     string
    Age      int
    Location string
}

func createUser(opts UserOptions) (*User, error)

// Instead of
func createUser(name string, age int, location string) (*User, error)

3. Error Handling

// Good: Descriptive errors with context
func processOrder(order Order) error {
    if order.ID == "" {
        return fmt.Errorf("processing order: missing ID")
    }
    
    if err := validateOrder(order); err != nil {
        return fmt.Errorf("processing order: %w", err)
    }
    
    return nil
}

Common Patterns

1. Options Pattern

type ServerOption func(*Server)

func WithPort(port int) ServerOption {
    return func(s *Server) {
        s.port = port
    }
}

func NewServer(options ...ServerOption) *Server {
    server := &Server{
        port: 8080,  // Default value
    }
    
    for _, option := range options {
        option(server)
    }
    
    return server
}

// Usage
server := NewServer(
    WithPort(3000),
)

2. Builder Pattern

type QueryBuilder struct {
    table string
    where string
    limit int
}

func (qb *QueryBuilder) From(table string) *QueryBuilder {
    qb.table = table
    return qb
}

func (qb *QueryBuilder) Where(condition string) *QueryBuilder {
    qb.where = condition
    return qb
}

// Usage
query := new(QueryBuilder).
    From("users").
    Where("age > 18")

3. Middleware Pattern

type Middleware func(http.HandlerFunc) http.HandlerFunc

func Logger(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Request: %s %s", r.Method, r.URL.Path)
        next(w, r)
    }
}

// Usage
http.HandleFunc("/", Logger(handleRequest))

Next Steps

  1. Learn about error handling
  2. Explore interfaces
  3. Study concurrency
  4. Practice with testing

Additional Resources