Go: Go Functions
Functions are the "Lego bricks" of Go code—Go takes the concept of "first-فئة functions" to the extreme, making combinatorial programming simple.
Go's دالة design philosophy: Maximize reusability with minimal syntax. Multiple return values, named return values, variadic arguments, closures, and passing functions by value—features that require a "إطار عمل" or "library" in other languages are part of Go's native syntax.
1. You will learn
- The 4 elements of the
funcدالة definition - Multiple Return Values and Named Return Values
- Using the متغير argument
... - Anonymous Functions and Closures
- The
initدالة and package initialization - Functions as Arguments and Return Values (Higher-Order Functions)
- Building Data Processing Pipelines with Functions
2. A Data Engineer's True Story
(1) Pain point: Java functions can only return a single value
Bob is a data engineer who recently needed to write a data processing دالة:
"I need to write a
parseUserDataدالة that returns both the parsed user كائن and an خطأ code; it also needs to track the parsing time and return warning messages—but since Java only allows a single return value, I'm forced to cram everything into a singleResultكائن, making the code read like a tangled mess of spaghetti."
He opened the Java code:
// Java: Can only return one value, so it must be packaged
public class ParseResult {
public User user;
public int errorCode;
public long durationMs;
public List<String> warnings;
}
public ParseResult parseUserData(String raw) {
// All 5 return values are listed here
return new ParseResult(...);
}
During a review, a colleague complained, "Your function is like a set of Russian nesting dolls—if I need five fields, I have to peel back layer after layer."
(2) Solution in Go
Go functions natively support multiple return values:
// user_parser.go
package main
import (
"fmt"
"strconv"
"strings"
"time"
)
// Multiple return values: user + errorCode + durationMs + warnings
func parseUserData(raw string) (User, int, time.Duration, []string) {
start := time.Now()
var warnings []string
parts := strings.Split(raw, ",")
if len(parts) != 3 {
return User{}, 400, time.Since(start), []string{"Format error: 3 fields required"}
}
age, err := strconv.Atoi(parts[1])
if err != nil {
return User{}, 400, time.Since(start), []string{"Invalid age format"}
}
if age < 0 || age > 150 {
warnings = append(warnings, "Age Anomaly")
}
user := User{Name: parts[0], Age: age, City: parts[2]}
return user, 200, time.Since(start), warnings
}
type User struct {
Name string
Age int
City string
}
func main() {
user, code, duration, warnings := parseUserData("Alice,28,Shanghai")
fmt.Printf("Status: %d\n", code)
fmt.Printf("User: %+v\n", user)
fmt.Printf("Duration: %v\n", duration)
fmt.Printf("Warnings: %v\n", warnings)
}
Output:
Status: 200
User: {Name:Alice Age:28 City:Shanghai}
Duration: 12.5µs
Warnings: []
(3) Performance: Go Functions vs. Other Languages
| Feature | C | Java | Python | Go |
|---|---|---|---|---|
| Multiple return values | ❌ Requires a struct wrapper | ❌ Requires a wrapper class | ✅ Tuple | ✅ Native |
| Return Value Name | ❌ | ❌ | ❌ | ✅ Native |
| Functions as Values | Function Pointers | Lambda | First-Class Citizens | ✅ First-Class Citizens |
| Closures | ✅ (Complex) | Lambda | ✅ | ✅ Concise |
| Variable-length arguments | ✅ (stdarg) | ✅ varargs | ✅ *args | ✅ ... |
func foo() (T, error)). This is the cornerstone of Go's error-handling philosophy.
sequenceDiagram
participant Caller
participant Function as parseUserData()
participant Parser as Internal Logic
Caller->>Function: parseUserData(raw)
Function->>Parser: Split fields
Parser-->>Function: name, age, city
Function-->>Caller: user + 200 + duration + warnings
Note over Caller: Multiple return values: receive<br/>user/status/time/warnings simultaneously
3. func Function Definition
(1) The four elements of a function definition
func functionName(param1 type1, param2 type2) returnType {
// Function Body
return value
}
| Element | Keyword | Required? |
|---|---|---|
| Function Name | funcName |
Yes |
| Parameter list | (param type, ...) |
Yes |
| Return Type | returnType |
No (can be omitted if there is no return value) |
| Function body | { ... } |
Yes |
▶ Example: Basic functions are defined in four forms
package main
import "fmt"
// Form 1: No parameters, no return value
func sayHello() {
fmt.Println("Hello!")
}
// Form 2: With parameters but no return value
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
// Form 3: With parameters and a return value
func add(a, b int) int {
return a + b
}
// Form 4: Multiple Parameters and Multiple Return Values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
func main() {
sayHello() // Hello!
greet("Alice") // Hello, Alice!
fmt.Println(add(2, 3)) // 5
result, err := divide(10.0, 2.0)
fmt.Printf("%.2f, err=%v\n", result, err) // 5.00, err=<nil>
}
Output:
Hello!
Hello, Alice!
5
5.00, err=<nil>
(3) Parameter Abbreviations
Continuous parameters of the same type can be combined into a single type:
func add(a, b int) int // equivalent to a int, b int
func rect(w, h int) (int, int) // both parameters are int
4. Multiple Return Values
(1) Defining Multiple Return Values
package main
import "fmt"
func swap(a, b string) (string, string) {
return b, a
}
func main() {
x, y := swap("hello", "world")
fmt.Println(x, y) // world hello
}
▶ Example: Value + Error (Go's signature pattern)
package main
import (
"errors"
"fmt"
)
func findUser(id int) (string, error) {
if id <= 0 {
return "", errors.New("invalid id")
}
if id == 999 {
return "", fmt.Errorf("user %d not found", id)
}
return fmt.Sprintf("User-%d", id), nil
}
func main() {
user, err := findUser(1)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Found: %s\n", user)
// To ignore a return value: use _
_, err = findUser(999)
fmt.Printf("Ignored: err=%v\n", err)
}
Output:
Found: User-1
Ignored: err=user 999 not found
(3) Naming the return value
Named return values are declared at the top of the function; the return statement automatically returns these variables:
package main
import "fmt"
func calc(a, b int) (sum, diff, product int) {
sum = a + b
diff = a - b
product = a * b
return // bare return, automatically returns sum/diff/product
}
func main() {
s, d, p := calc(10, 3)
fmt.Printf("sum=%d, diff=%d, product=%d\n", s, d, p)
}
Output:
sum=13, diff=7, product=30
(4) Multiple Return Values vs. Named Return Values
| Scenario | Recommendation |
|---|---|
| Returns 1 value | Standard return value |
| Returns 2 values (value + error) | Standard return value |
| Returns 3 or more values | Name the return values (for clarity) |
The return value needs to be modified in defer |
The return value must be named |
defer (as shown in this lesson's comprehensive example).
5. Variadic Parameters
(1) Variable-argument syntax ...
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(10, 20)) // 30
fmt.Println(sum()) // 0
// Break the slice apart and pass it in: nums...
nums := []int{1, 2, 3, 4, 5}
fmt.Println(sum(nums...)) // 15
}
Output:
6
30
0
15
▶ Example: Variable Arguments + Formatted Strings
package main
import "fmt"
// Similar to `fmt.Printf`: The first argument is fixed, and the rest are variable.
func logMessage(level string, args ...interface{}) {
fmt.Printf("[%s] ", level)
fmt.Println(args...) // spread slice and pass to Println
}
func main() {
logMessage("INFO", "Server started", "on port", 8080)
logMessage("ERROR", "Database connection failed:", "timeout 5s")
logMessage("DEBUG")
}
Output:
[INFO] Server started on port 8080
[ERROR] Database connection failed: timeout 5s
[DEBUG]
(3) Restrictions on Variable Arguments
| Restriction | Description |
|---|---|
| Up to 1 variable argument | func foo(a int, b ...int) ✅ |
| Variadic parameters must be the last ones | func foo(a ...int, b int) ❌ |
| Types must be consistent | To allow multiple types, use ...interface{} |
6. Anonymous Functions and Closures
(1) Anonymous functions (function literals)
Anonymous functions have no name and can be assigned to a variable or called directly:
package main
import "fmt"
func main() {
// Assign to a variable
add := func(a, b int) int {
return a + b
}
fmt.Println(add(2, 3)) // 5
// Direct Call
func(x int) {
fmt.Printf("Anonymous function: x=%d\n", x)
}(42)
}
Output:
5
Anonymous function: x=42
(2) Closures: Capturing External Variables
A closure = a function + the external variables it references. Closures allow functions to "remember" the environment in which they were created:
▶ Example: Implementing a counter using closures
package main
import "fmt"
// Returns a closure: increments by 1 on each call
func makeCounter() func() int {
count := 0 // variable captured by closure
return func() int {
count++
return count
}
}
func main() {
counter := makeCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
fmt.Println(counter()) // 3
// Each counter is an independent closure.
another := makeCounter()
fmt.Println(another()) // 1 (restarts counting)
}
Output:
1
2
3
1
(4) Closures vs. Regular Functions
| Dimension | Regular Function | Closure |
|---|---|---|
| State | Stateless | Stateful (captured variables) |
| Memory | Static | Each time a new instance is created |
| Use Cases | Pure Computation | Factories, Decorators, Callbacks |
7. The init Function and Package Initialization
(1) Characteristics of the init function
| Feature | Description |
|---|---|
| No parameters, no return value | func init() |
| Automatic Invocation | Executed automatically when the package is imported |
| Multiple | A package can have multiple init functions (executed in the order they are declared) |
| Before main | Executed before main() |
▶ Example: Package Initialization (Registry)
// registry.go
package main
import "fmt"
var registry = make(map[string]int)
func init() {
registry["version"] = 1
registry["max_connections"] = 100
fmt.Println("[init] registry initialized")
}
func init() {
registry["debug"] = 1
fmt.Println("[init] debug enabled")
}
func main() {
fmt.Printf("Registry: %+v\n", registry)
}
Output:
[init] registry initialized
[init] debug enabled
Registry: map[debug:1 max_connections:100 version:1]
(3) init vs main
| Function | When Called | Purpose |
|---|---|---|
init() |
Executed automatically when the package is imported | Initializes global variables and registers drivers |
main() |
Executed when the program starts (once) | Program entry point |
8. Functions as Arguments and Return Values (Higher-Order Functions)
Functions in Go are first-class citizens—they can be assigned to variables, passed as arguments, and returned as values.
(1) Functions as Arguments (Callbacks)
package main
import "fmt"
// The second parameter is the function type: it accepts an `int` and returns an `int`.
func process(nums []int, callback func(int) int) []int {
result := make([]int, len(nums))
for i, n := range nums {
result[i] = callback(n)
}
return result
}
func double(n int) int { return n * 2 }
func square(n int) int { return n * n }
func main() {
nums := []int{1, 2, 3, 4, 5}
doubled := process(nums, double)
fmt.Println("doubled:", doubled)
squared := process(nums, square)
fmt.Println("squared:", squared)
}
Output:
doubled: [2 4 6 8 10]
squared: [1 4 9 16 25]
(2) Functions as Return Values (Factory)
package main
import "fmt"
func makeAdder(x int) func(int) int {
return func(y int) int {
return x + y
}
}
func main() {
add10 := makeAdder(10)
add100 := makeAdder(100)
fmt.Println(add10(5)) // 15
fmt.Println(add100(5)) // 105
}
Output:
15
105
▶ Example: Function Types + Higher-Order Functions (map/filter/reduce)
package main
import "fmt"
func mapFunc(nums []int, f func(int) int) []int {
result := make([]int, len(nums))
for i, n := range nums {
result[i] = f(n)
}
return result
}
func filterFunc(nums []int, predicate func(int) bool) []int {
var result []int
for _, n := range nums {
if predicate(n) {
result = append(result, n)
}
}
return result
}
func reduceFunc(nums []int, initial int, f func(int, int) int) int {
acc := initial
for _, n := range nums {
acc = f(acc, n)
}
return acc
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 1. Map: Each element *2
doubled := mapFunc(nums, func(n int) int { return n * 2 })
// 2. Filter: Keep even numbers
evens := filterFunc(nums, func(n int) bool { return n%2 == 0 })
// 3. Reduce: Sum
sum := reduceFunc(nums, 0, func(acc, n int) int { return acc + n })
fmt.Printf("doubled: %v\n", doubled)
fmt.Printf("evens: %v\n", evens)
fmt.Printf("sum: %d\n", sum)
}
Output:
doubled: [2 4 6 8 10 12 14 16 18 20]
evens: [2 4 6 8 10]
sum: 55
9. Complete Example: Data Processing Pipeline
Combine all the features of the function to build an ETL (Extract-Transform-Load) data pipeline:
// pipeline.go
package main
import (
"fmt"
"strings"
"time"
)
// Data Sources
func extract() []string {
return []string{
" Alice,28,Shanghai ",
"Bob,32,Beijing",
"CHARLIE,45,Guangzhou",
"", // empty data
"Dave,abc,ErrorCity", // anomalous data
}
}
// Step 1: Remove Spaces + Split Fields
func trim(s string) []string {
return strings.Split(strings.TrimSpace(s), ",")
}
// Step 2: Convert to uppercase
func upper(s []string) []string {
for i, v := range s {
s[i] = strings.ToUpper(v)
}
return s
}
// Step 3: Verify the number of fields
func validate(s []string) (string, bool) {
if len(s) != 3 || s[0] == "" {
return "", false
}
return strings.Join(s, "|"), true
}
// Pipeline Function: Combining 3 Steps + Defer Report
func processPipeline(name string, data []string) (valid int, errors int) {
defer func() {
// Name the return value so that `defer` can modify the result
fmt.Printf("[%s] Completed: valid=%d errors=%d duration=%v\n",
name, valid, errors, time.Since(startTime))
}()
for _, raw := range data {
s := trim(raw)
if len(s) < 3 {
errors++
continue
}
s = upper(s)
if result, ok := validate(s); ok {
valid++
fmt.Printf(" -> %s\n", result)
} else {
errors++
}
}
return valid, errors
}
var startTime = time.Now()
func main() {
fmt.Println("=== Start of Data Pipeline ===")
// Combining Steps Using Functions as Arguments
data := extract()
valid, errors := processPipeline("ETL-1", data)
fmt.Printf("\nSummary: valid=%d, errors=%d\n", valid, errors)
}
Expected Output:
=== Start of Data Pipeline ===
-> ALICE|28|SHANGHAI
-> BOB|32|BEIJING
-> CHARLIE|45|GUANGZHOU
-> DAVE|ABC|ERRORCITY
[ETL-1] Completed: valid=4 errors=1 duration=2.5µs
Summary: valid=4, errors=1
startTime must be declared at the file level (you cannot use := inside init). This is the subtle difference between the init function and var declarations—var is package-level, while init is function-level.
❓ FAQ
func foo(a ...int) or different function names.return statements reduce code; (2) Return values can be modified in defer (Important! This is used in the comprehensive example in this lesson); (3) Documentation—function signatures serve as documentation.init function be called manually?init can only be called automatically by the Go runtime, and each package's init is executed only once. Attempting to call init manually will result in a compilation error.nums ...int is essentially an []int slice inside the function, but the calling conventions differ: foo(1,2,3) vs foo([]int{1,2,3}...). The former is syntactic sugar.📖 Summary
- Function definition consists of 4 elements:
func+ name + parameter list + return type - Multiple return values are a hallmark of Go and are commonly used in the
(value, error)error-handling pattern - Naming return values makes
returnstatements more concise and allowsdeferto modify the return value - Variadic arguments
...as an alternative to overloading; the slice is defined within the function - Closures capture outer variables, allowing for the implementation of stateful functions (such as counters and factory functions)
initis automatically executed when the package is imported and is used to initialize global state- Functions are first-class citizens: they can be assigned to variables, passed as arguments, and returned; they support higher-order functions (map/filter/reduce)
📝 Exercises
-
Basic Problem (Difficulty ⭐): Write a
max(nums ...int) intfunction that returns the maximum value among all arguments. The function must use variadic arguments; callingmax(1, 5, 3, 9, 2)should output 9. -
Advanced Problem (Difficulty ⭐⭐): Implement a
makeBankAccount(initial int) (deposit func(int) int, withdraw func(int) (int, bool), balance func() int)to simulate a bank account:depositincreases the balance and returns the new balance;withdrawdeducts funds (returnsfalseif the balance is insufficient);balancequeries the balance. You must use closures. -
Challenge Problem (Difficulty ⭐⭐⭐): Implement a functional data pipeline: Given an input of
[]string(a string of numbers), use higher-order functions to sequentially perform the four stepsparse -> filter(>10) -> map(*2) -> sum. Each step must be an independent function, and the combined functions must produce the final result. For example:["1", "15", "3", "20"]→parse [1,15,3,20]→filter>10 [15,20]→map*2 [30,40]→sum 70.