Functions

Functions

Functions are like "vending machines" in everyday life — you insert parameters, they process according to predetermined rules, and return results. In Go, functions are first-class citizens: they can be assigned to variables, passed as arguments, returned as values, and can even be nameless. Mastering functions is a key step to writing elegant Go code.


1. Core Concepts

Concept Description
func definition Use the func keyword to declare functions, supporting parameters and return value types
Multiple return values Go functions can return multiple values, commonly used for result + error
Named return values Return values can be named, used directly in the function body, and automatically returned with return
Variadic parameters Use ...Type to accept any number of arguments
Anonymous functions Functions without names, often used for immediate invocation or assignment to variables
Closures Anonymous functions that capture external variables, forming closures
init function Each package can have multiple init functions that execute automatically at program startup
defer Delayed execution, runs in stack order before the function returns

2. Basic Syntax/Usage

Function Definition

GO
// Basic function definition
func functionName(parameterList) returnType {
    // Function body
    return value
}

// No parameters, no return value
func sayHello() {
    fmt.Println("Hello!")
}

// With parameters and return value
func add(a int, b int) int {
    return a + b
}

// Shorthand when parameter types are the same
func add(a, b int) int {
    return a + b
}

Multiple Return Values

GO
// Return two values: result and error
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("divisor cannot be zero")
    }
    return a / b, nil
}

// Receive both return values when calling
result, err := divide(10, 3)

Named Return Values

GO
// Named return values: use variable names directly in the function body
func rectangleInfo(length, width float64) (area, perimeter float64) {
    area = length * width
    perimeter = 2 * (length + width)
    return // Automatically returns the named return values
}

Variadic Parameters

GO
// Variadic parameters: use ...Type to accept any number of arguments
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// Calling
sum(1, 2, 3)       // 6
sum(1, 2, 3, 4, 5) // 15

Anonymous Functions and Closures

GO
// Assign anonymous function to variable
greet := func(name string) string {
    return "Hello, " + name
}

// Immediately invoked function
func() {
    fmt.Println("Executed immediately")
}()

// Closure: captures external variable
func counter() func() int {
    count := 0
    return func() int {
        count++ // Captures the external count variable
        return count
    }
}

init Function

GO
// init function: executes automatically at program startup, no manual call needed
// Each package can have multiple init functions
func init() {
    fmt.Println("init function executed")
}

defer

GO
// defer: delayed execution, runs in LIFO (last-in, first-out) order before function returns
func readFile(filename string) {
    f, _ := os.Open(filename)
    defer f.Close() // Close file before function ends
    // ... read file
}
💡 Tip: In Go, all function arguments are passed by value. Slices, maps, channels, pointers, etc. pass a copy of the reference, but the data they point to is shared.

💡 Tip: defer statement arguments are evaluated when declared, not when executed. This is a common pitfall.

💡 Tip: init functions cannot be called by other functions; they execute automatically before main. If there are multiple init functions, they execute in source file alphabetical order.


3. Basic Syntax/Usage (Examples)

Example 1: Basic Usage (Difficulty ⭐)

Objective: Learn basic function definition, calling, and multiple return values.

GO
package main

import (
    "errors"
    "fmt"
)

// greet accepts a name and returns a greeting
func greet(name string) string {
    return "Hello, " + name + "!"
}

// add returns the sum of two integers
func add(a, b int) int {
    return a + b
}

// swap swaps two strings (demonstrates multiple return values)
func swap(a, b string) (string, string) {
    return b, a
}

// divide demonstrates error handling
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("divisor cannot be zero")
    }
    return a / b, nil
}

func main() {
    // Call single return value functions
    fmt.Println(greet("Alice")) // Output: Hello, Alice!
    fmt.Println(add(3, 5))      // Output: 8

    // Call multiple return value function
    x, y := swap("hello", "world")
    fmt.Println(x, y) // Output: world hello

    // Call function with error handling
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Printf("10 / 3 = %.2f\n", result) // Output: 10 / 3 = 3.33
    }

    // Test division by zero error
    _, err = divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err) // Output: Error: divisor cannot be zero
    }
}
▶ Try it Yourself

Output:

TEXT
Hello, Alice!
8
world hello
10 / 3 = 3.33
Error: divisor cannot be zero

Key Points:


Example 2: Intermediate Usage (Difficulty ⭐⭐)

Objective: Learn named return values, variadic parameters, anonymous functions, and closures.

GO
package main

import "fmt"

// ============ Named Return Values ============

// rectangle calculates area and perimeter of a rectangle (using named return values)
func rectangle(length, width float64) (area, perimeter float64) {
    area = length * width           // Use named variables directly
    perimeter = 2 * (length + width)
    return // Automatically returns named return values, no need to write return area, perimeter
}

// ============ Variadic Parameters ============

// sum calculates the sum of any number of integers
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// average calculates the average, demonstrating mixing variadic with other parameters
func average(first float64, rest ...float64) float64 {
    total := first
    count := 1.0
    for _, v := range rest {
        total += v
        count++
    }
    return total / count
}

// ============ Anonymous Functions ============

// apply applies a function to an integer and returns the result
func apply(n int, f func(int) int) int {
    return f(n)
}

func main() {
    // Named return values
    area, perimeter := rectangle(5, 3)
    fmt.Printf("Area: %.1f, Perimeter: %.1f\n", area, perimeter)
    // Output: Area: 15.0, Perimeter: 16.0

    // Variadic parameters
    fmt.Println("sum(1,2,3):", sum(1, 2, 3))           // 6
    fmt.Println("sum(1,2,3,4,5):", sum(1, 2, 3, 4, 5)) // 15

    // Variadic parameters: passing a slice
    nums := []int{10, 20, 30}
    fmt.Println("sum(nums...):", sum(nums...)) // 60 (use ... to expand slice)

    // Mixing variadic with other parameters
    fmt.Printf("average: %.2f\n", average(10, 20, 30)) // 20.00

    // Anonymous functions as arguments
    double := func(n int) int { return n * 2 }
    square := func(n int) int { return n * n }

    fmt.Println("apply(5, double):", apply(5, double)) // 10
    fmt.Println("apply(5, square):", apply(5, square)) // 25

    // Immediately invoke anonymous function
    msg := "Hello"
    func(m string) {
        fmt.Println(m)
    }(msg)

    // ============ Closures ============

    // counter returns a counter closure
    counter := func() func() int {
        count := 0
        return func() int {
            count++ // Captures external variable count
            return count
        }
    }()

    fmt.Println("counter:", counter()) // 1
    fmt.Println("counter:", counter()) // 2
    fmt.Println("counter:", counter()) // 3

    // Closure implementing an accumulator
    adder := func() func(int) int {
        sum := 0
        return func(n int) int {
            sum += n
            return sum
        }
    }()

    fmt.Println("adder(10):", adder(10)) // 10
    fmt.Println("adder(20):", adder(20)) // 30
    fmt.Println("adder(30):", adder(30)) // 60
}
▶ Try it Yourself

Output:

TEXT
Area: 15.0, Perimeter: 16.0
sum(1,2,3): 6
sum(1,2,3,4,5): 15
sum(nums...): 60
average: 20.00
apply(5, double): 10
apply(5, square): 25
Hello
counter: 1
counter: 2
counter: 3
adder(10): 10
adder(20): 30
adder(30): 60

Key Points:


Example 3: Comprehensive Application (Difficulty ⭐⭐⭐)

Objective: Real-world function design patterns: functional options pattern, middleware chain, defer with error handling.

GO
package main

import (
    "fmt"
    "strings"
    "time"
)

// ============ Functional Options Pattern ============

// Server configuration
type Server struct {
    host    string
    port    int
    timeout time.Duration
    maxConn int
}

// Option is a function type for configuration options
type Option func(*Server)

// WithHost sets the host address
func WithHost(host string) Option {
    return func(s *Server) {
        s.host = host
    }
}

// WithPort sets the port
func WithPort(port int) Option {
    return func(s *Server) {
        s.port = port
    }
}

// WithTimeout sets the timeout duration
func WithTimeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.timeout = timeout
    }
}

// WithMaxConn sets the maximum connections
func WithMaxConn(max int) Option {
    return func(s *Server) {
        s.maxConn = max
    }
}

// NewServer creates a server using the options pattern
func NewServer(opts ...Option) *Server {
    // Default configuration
    s := &Server{
        host:    "localhost",
        port:    8080,
        timeout: 30 * time.Second,
        maxConn: 100,
    }
    // Apply all options
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// String implements the Stringer interface
func (s *Server) String() string {
    return fmt.Sprintf("Server{host:%s, port:%d, timeout:%v, maxConn:%d}",
        s.host, s.port, s.timeout, s.maxConn)
}

// ============ Middleware Chain Pattern ============

// Handler is a processing function type
type Handler func(string) string

// Middleware is a middleware type
type Middleware func(Handler) Handler

// loggingMiddleware logs input and output
func loggingMiddleware(next Handler) Handler {
    return func(input string) string {
        fmt.Printf("[Log] Input: %s\n", input)
        result := next(input)
        fmt.Printf("[Log] Output: %s\n", result)
        return result
    }
}

// upperMiddleware converts input to uppercase
func upperMiddleware(next Handler) Handler {
    return func(input string) string {
        return next(strings.ToUpper(input))
    }
}

// wrapMiddleware wraps output with brackets
func wrapMiddleware(next Handler) Handler {
    return func(input string) string {
        return "【" + next(input) + "】"
    }
}

// chain combines multiple middleware into a chain
func chain(handler Handler, middlewares ...Middleware) Handler {
    // Wrap from back to front, ensuring the first middleware executes first
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// ============ defer in Practice: Resource Cleanup and Error Recovery ============

// simulateDB simulates database operations
func simulateDB() (err error) {
    fmt.Println("1. Acquiring database connection")

    // defer executes in LIFO order
    defer fmt.Println("5. Releasing database connection (executes last)")
    defer fmt.Println("4. Writing operation log")
    defer fmt.Println("3. Closing cursor")

    fmt.Println("2. Executing query")

    // Simulate panic recovery
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("  [Recovery] Caught panic: %v\n", r)
            err = fmt.Errorf("operation failed: %v", r)
        }
    }()

    // Simulate a panic
    panic("connection timeout")

    // Code below won't execute
    // fmt.Println("Query complete")
    // return nil
}

func main() {
    // ===== Functional Options Pattern =====
    fmt.Println("=== Functional Options Pattern ===")

    // Using default configuration
    s1 := NewServer()
    fmt.Println(s1)

    // Custom configuration
    s2 := NewServer(
        WithHost("0.0.0.0"),
        WithPort(9090),
        WithTimeout(60*time.Second),
        WithMaxConn(500),
    )
    fmt.Println(s2)

    // ===== Middleware Chain =====
    fmt.Println("\n=== Middleware Chain ===")

    // Base handler
    handler := func(s string) string {
        return "Processed: " + s
    }

    // Combine middleware
    final := chain(handler, loggingMiddleware, upperMiddleware, wrapMiddleware)
    result := final("hello world")
    fmt.Println("Final result:", result)

    // ===== defer and Error Recovery =====
    fmt.Println("\n=== defer Execution Order ===")
    err := simulateDB()
    if err != nil {
        fmt.Println("Main function caught error:", err)
    }
}
▶ Try it Yourself

Output:

TEXT
=== Functional Options Pattern ===
Server{host:localhost, port:8080, timeout:30s, maxConn:100}
Server{host:0.0.0.0, port:9090, timeout:1m0s, maxConn:500}

=== Middleware Chain ===
[Log] Input: HELLO WORLD
[Log] Output: 【Processed: HELLO WORLD】
Final result: 【Processed: HELLO WORLD】

=== defer Execution Order ===
1. Acquiring database connection
2. Executing query
  [Recovery] Caught panic: connection timeout
3. Closing cursor
4. Writing operation log
5. Releasing database connection (executes last)
Main function caught error: operation failed: connection timeout

Key Points:


3. Common Use Cases

Case 1: Error Handling Encapsulation

In real projects, you often need to encapsulate unified error handling logic:

GO
package main

import (
    "fmt"
    "strconv"
)

// Result is a unified result wrapper
type Result struct {
    Data    interface{}
    Err     error
}

// parseJSON simulates JSON parsing, returns result and error
func parseJSON(jsonStr string) (map[string]interface{}, error) {
    // Simplified demo: use encoding/json in real projects
    if jsonStr == "" {
        return nil, fmt.Errorf("JSON string cannot be empty")
    }
    return map[string]interface{}{"key": "value"}, nil
}

// parseInt safely converts a string to an integer
func parseInt(s string) (int, error) {
    n, err := strconv.Atoi(s)
    if err != nil {
        return 0, fmt.Errorf("failed to parse integer '%s': %w", s, err)
    }
    return n, nil
}

// withRetry retries a function
func withRetry(attempts int, f func() error) error {
    var err error
    for i := 0; i < attempts; i++ {
        err = f()
        if err == nil {
            return nil
        }
        fmt.Printf("  Attempt %d failed: %v\n", i+1, err)
    }
    return fmt.Errorf("failed after %d attempts: %w", attempts, err)
}

func main() {
    // Error handling
    result, err := parseJSON(`{"name": "test"}`)
    if err != nil {
        fmt.Println("Parse error:", err)
        return
    }
    fmt.Println("Parse result:", result)

    // Safe type conversion
    num, err := parseInt("123")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("Conversion result:", num)

    // Retry mechanism
    attempt := 0
    err = withRetry(3, func() error {
        attempt++
        if attempt < 3 {
            return fmt.Errorf("simulated failure")
        }
        return nil
    })
    if err != nil {
        fmt.Println("Final failure:", err)
    } else {
        fmt.Println("Final success!")
    }
}

Case 2: Functions as Data Processing Pipelines

GO
package main

import (
    "fmt"
    "strings"
)

// Transform is a data transformation function type
type Transform func(string) string

// Pipeline is a data processing pipeline
type Pipeline struct {
    steps []Transform
}

// NewPipeline creates a new pipeline
func NewPipeline() *Pipeline {
    return &Pipeline{}
}

// Add adds processing steps
func (p *Pipeline) Add(steps ...Transform) *Pipeline {
    p.steps = append(p.steps, steps...)
    return p
}

// Execute runs the pipeline
func (p *Pipeline) Execute(input string) string {
    result := input
    for _, step := range p.steps {
        result = step(result)
    }
    return result
}

func main() {
    // Define transformation functions
    trim := func(s string) string { return strings.TrimSpace(s) }
    lower := func(s string) string { return strings.ToLower(s) }
    replace := func(s string) string { return strings.ReplaceAll(s, " ", "-") }
    prefix := func(s string) string { return "processed-" + s }

    // Build processing pipeline
    pipeline := NewPipeline().
        Add(trim, lower, replace, prefix)

    // Execute pipeline
    result := pipeline.Execute("  Hello World Go  ")
    fmt.Println(result) // Output: processed-hello-world-go

    // Reuse pipeline for multiple inputs
    inputs := []string{"  Foo Bar  ", "  HELLO  ", "  Go Lang  "}
    for _, input := range inputs {
        fmt.Printf("%-20s => %s\n", input, pipeline.Execute(input))
    }
}

❓ FAQ

Q1: Are function parameters passed by value or by reference?

A: In Go, all function parameters are passed by value. However, slices, maps, channels, pointers, and similar types pass a "copy of the reference" — modifying the data they point to affects the original data, but reassigning them does not affect the original variable.

GO
package main

import "fmt"

// modifySlice modifies slice contents (affects the original slice)
func modifySlice(s []int) {
    s[0] = 999 // Modifying element affects original slice
    fmt.Println("Inside function:", s)
}

// reassignSlice reassigns the slice (doesn't affect original slice)
func reassignSlice(s []int) {
    s = append(s, 4, 5, 6) // append may create a new underlying array
    fmt.Println("Inside function:", s)
}

func main() {
    nums := []int{1, 2, 3}

    modifySlice(nums)
    fmt.Println("Outside function:", nums) // [999 2 3], original slice modified

    reassignSlice(nums)
    fmt.Println("Outside function:", nums) // [999 2 3], original slice unchanged
}

Q2: When are defer arguments evaluated?

A: defer statement arguments are evaluated when declared, not when executed.

GO
package main

import "fmt"

func main() {
    x := 10

    // defer arguments evaluated at declaration time, x = 10
    defer fmt.Println("x in defer =", x)

    x = 20
    fmt.Println("x after modification =", x)

    // If you need evaluation at defer execution time, use a closure
    defer func() {
        fmt.Println("x in closure defer =", x) // x = 20
    }()

    x = 30
}

Output:

TEXT
x after modification = 20
x in closure defer = 30
x in defer = 10

Q3: Can a package have multiple init functions? What's the execution order?

A: Yes. A package (or even a single file) can have multiple init functions. Execution order:

  1. First, execute init functions of imported packages
  2. Within the same package, execute in source file alphabetical order
  3. Within the same file, execute in the order init functions appear
GO
package main

import "fmt"

func init() {
    fmt.Println("First init")
}

func init() {
    fmt.Println("Second init")
}

func main() {
    fmt.Println("main function")
}

Output:

TEXT
First init
Second init
main function

Q4: How to implement optional parameters?

A: Go doesn't directly support optional parameters. The following three methods are commonly used:

GO
package main

import "fmt"

// Method 1: Using struct
type Config struct {
    Host string
    Port int
    Debug bool
}

func connect(cfg Config) {
    fmt.Printf("Connecting to %s:%d (debug=%v)\n", cfg.Host, cfg.Port, cfg.Debug)
}

// Method 2: Using functional options pattern (recommended)
type Option func(*Config)

func WithHost(host string) Option {
    return func(c *Config) { c.Host = host }
}

func WithPort(port int) Option {
    return func(c *Config) { c.Port = port }
}

func WithDebug(debug bool) Option {
    return func(c *Config) { c.Debug = debug }
}

func newConnect(opts ...Option) {
    cfg := &Config{Host: "localhost", Port: 8080, Debug: false}
    for _, opt := range opts {
        opt(cfg)
    }
    fmt.Printf("Connecting to %s:%d (debug=%v)\n", cfg.Host, cfg.Port, cfg.Debug)
}

func main() {
    // Struct method
    connect(Config{Host: "127.0.0.1", Port: 3306})

    // Options pattern
    newConnect()
    newConnect(WithHost("127.0.0.1"), WithPort(3306), WithDebug(true))
}

📖 Summary


📝 Exercises

Exercise 1 (⭐): Basic Function Practice

Requirements: Write and test the following functions:

  1. max(a, b int) int — Returns the larger of two integers
  2. isEven(n int) bool — Checks if an integer is even
  3. swap(a, b *int) — Swaps two integers using pointers
GO
// Reference framework
package main

import "fmt"

func max(a, b int) int {
    // Implement here
    return 0
}

func isEven(n int) bool {
    // Implement here
    return false
}

func swap(a, b *int) {
    // Implement here (hint: use pointers)
}

func main() {
    fmt.Println(max(3, 5))     // Should output: 5
    fmt.Println(isEven(4))     // Should output: true
    fmt.Println(isEven(7))     // Should output: false

    x, y := 10, 20
    swap(&x, &y)
    fmt.Println(x, y)          // Should output: 20 10
}

Exercise 2 (⭐⭐): Variadic Parameters and Closures

Requirements:

  1. Write filter(nums []int, predicate func(int) bool) []int — Filters elements in a slice that satisfy a condition
  2. Write makeMultiplier(factor int) func(int) int — Returns a multiplication closure
  3. Use filter and a closure to select all even numbers from a slice
GO
// Reference framework
package main

import "fmt"

func filter(nums []int, predicate func(int) bool) []int {
    // Implement: iterate nums, add elements satisfying predicate to result slice
    return nil
}

func makeMultiplier(factor int) func(int) int {
    // Implement: return a closure that multiplies input by factor
    return nil
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // Filter even numbers
    evens := filter(nums, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println("Evens:", evens) // [2 4 6 8 10]

    // Filter numbers greater than 5
    big := filter(nums, func(n int) bool {
        return n > 5
    })
    fmt.Println("Greater than 5:", big) // [6 7 8 9 10]

    // Use closures to create multipliers
    double := makeMultiplier(2)
    triple := makeMultiplier(3)

    fmt.Println("double(5):", double(5)) // 10
    fmt.Println("triple(5):", triple(5)) // 15
}

Exercise 3 (⭐⭐⭐): Practical Project — Calculator with Command Pattern

Requirements: Implement a calculator system that supports registering commands:

  1. Define a Calculator struct with a command map map[string]func([]int) int
  2. Implement Register(name string, op func([]int) int) to register commands
  3. Implement Execute(name string, args []int) (int, error) to execute commands
  4. Register the following commands: add (sum), mul (product), max (maximum)
  5. Use init function to automatically register all commands
GO
// Reference framework
package main

import (
    "errors"
    "fmt"
)

type Calculator struct {
    // Define fields here
}

func NewCalculator() *Calculator {
    // Implement here
    return nil
}

func (c *Calculator) Register(name string, op func([]int) int) {
    // Implement here
}

func (c *Calculator) Execute(name string, args []int) (int, error) {
    // Implement: find command and execute; return error if not found
    return 0, nil
}

func (c *Calculator) ListCommands() []string {
    // Implement: return all registered command names
    return nil
}

func main() {
    calc := NewCalculator()

    // Register commands
    calc.Register("add", func(args []int) int {
        sum := 0
        for _, v := range args {
            sum += v
        }
        return sum
    })

    calc.Register("mul", func(args []int) int {
        product := 1
        for _, v := range args {
            product *= v
        }
        return product
    })

    calc.Register("max", func(args []int) int {
        if len(args) == 0 {
            return 0
        }
        m := args[0]
        for _, v := range args[1:] {
            if v > m {
                m = v
            }
        }
        return m
    })

    // Test
    tests := []struct {
        cmd  string
        args []int
    }{
        {"add", []int{1, 2, 3, 4, 5}},
        {"mul", []int{2, 3, 4}},
        {"max", []int{10, 3, 7, 9, 1}},
    }

    for _, t := range tests {
        result, err := calc.Execute(t.cmd, t.args)
        if err != nil {
            fmt.Printf("Error: %v\n", err)
        } else {
            fmt.Printf("%s(%v) = %d\n", t.cmd, t.args, result)
        }
    }

    // List all commands
    fmt.Println("Available commands:", calc.ListCommands())

    // Test non-existent command
    _, err := calc.Execute("sqrt", []int{16})
    if err != nil {
        fmt.Println("Error:", err)
    }
}

Expected Output:

TEXT
add([1 2 3 4 5]) = 15
mul([2 3 4]) = 24
max([10 3 7 9 1]) = 10
Available commands: [add mul max]
Error: unknown command: sqrt

Next Lesson

Next Lesson: Arrays and Slices →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏