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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
}
defer statement arguments are evaluated when declared, not when executed. This is a common pitfall.
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.
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
}
}
Output:
Hello, Alice!
8
world hello
10 / 3 = 3.33
Error: divisor cannot be zero
Key Points:
- Use the
funckeyword to define functions; parameter types come after parameter names - Multiple return values are separated by commas; use multiple variables to receive them when calling
- Error handling is a core Go pattern: return
(result, error)
Example 2: Intermediate Usage (Difficulty ⭐⭐)
Objective: Learn named return values, variadic parameters, anonymous functions, and closures.
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
}
Output:
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:
- Named return values make code clearer, but don't overuse them as it can reduce readability
- Variadic parameters
...Typemust be the function's last parameter - When passing a slice to variadic parameters, use
slice...to expand it - Closures "remember" the state of external variables; each call shares the same variable
Example 3: Comprehensive Application (Difficulty ⭐⭐⭐)
Objective: Real-world function design patterns: functional options pattern, middleware chain, defer with error handling.
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)
}
}
Output:
=== 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:
- Functional options pattern: Achieves flexible configuration through returning closures; widely used design pattern in the Go community
- Middleware chain: Functions as first-class citizens can be combined and passed to form processing pipelines
- defer execution order: LIFO (last-in, first-out); first declared defer executes last
- panic/recover:
defer+recovercan catchpanicand prevent program crashes
3. Common Use Cases
Case 1: Error Handling Encapsulation
In real projects, you often need to encapsulate unified error handling logic:
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
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.
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.
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:
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:
- First, execute
initfunctions of imported packages - Within the same package, execute in source file alphabetical order
- Within the same file, execute in the order
initfunctions appear
package main
import "fmt"
func init() {
fmt.Println("First init")
}
func init() {
fmt.Println("Second init")
}
func main() {
fmt.Println("main function")
}
Output:
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:
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
- Function definition: Use the
funckeyword; parameter types come after parameter names; supports multiple return values - Named return values: Return values can be named, used directly in the function body, and automatically returned with
return - Variadic parameters:
...Typeaccepts any number of arguments; must be the last parameter - Anonymous functions: Functions without names; can be assigned to variables or immediately invoked
- Closures: Anonymous functions that capture external variables; variable lifetimes are extended
- init function: Executes automatically at program startup; cannot be called manually; suitable for initialization work
- defer: Delayed execution in LIFO order; arguments are evaluated at declaration time
- Functions are first-class citizens: Can be assigned, passed, and returned, enabling flexible design patterns
📝 Exercises
Exercise 1 (⭐): Basic Function Practice
Requirements: Write and test the following functions:
max(a, b int) int— Returns the larger of two integersisEven(n int) bool— Checks if an integer is evenswap(a, b *int)— Swaps two integers using pointers
// 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:
- Write
filter(nums []int, predicate func(int) bool) []int— Filters elements in a slice that satisfy a condition - Write
makeMultiplier(factor int) func(int) int— Returns a multiplication closure - Use
filterand a closure to select all even numbers from a slice
// 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:
- Define a
Calculatorstruct with a command mapmap[string]func([]int) int - Implement
Register(name string, op func([]int) int)to register commands - Implement
Execute(name string, args []int) (int, error)to execute commands - Register the following commands:
add(sum),mul(product),max(maximum) - Use
initfunction to automatically register all commands
// 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:
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



