第16章 函数类型

第16章 函数类型

“函数是 Go 的一等公民(first-class citizen)。” —— 这是 Go 语言的设计哲学之一。

在 Go 里,函数不只是用来调用的代码块——函数本身也是一种类型,可以像变量一样赋值、传递、作为参数、作为返回值。听起来有点抽象,但它带来的编程范式革命是巨大的:你可以在 Go 里写高阶函数、装饰器、回调、策略模式——这些在其他语言里需要特殊语法才能做到的事情,在 Go 里只是普通代码。


16.1 函数类型定义

16.1.1 参数列表

函数类型由它的参数列表和结果列表决定:

1
2
3
4
5
6
7
8
// 参数列表决定函数类型的签名
type AddFunc func(a, b int) int

// 使用
var add AddFunc = func(a, b int) int {
    return a + b
}
fmt.Println(add(1, 2))  // 3

AddFunc 是一个函数类型:接收两个 int,返回一个 int。所有满足这个签名的函数都可以赋值给 AddFunc 类型的变量。

16.1.2 结果列表

函数类型的结果列表也是类型签名的一部分:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type Processor func(input string) (output string, err error)

// processor1 和 processor2 可以赋给 Processor 类型的变量
processor1 := func(input string) (string, error) {
    return strings.ToUpper(input), nil
}
processor2 := func(input string) (string, error) {
    return strings.ToLower(input), nil
}

var proc Processor = processor1
fmt.Println(proc("Hello"))  // HELLO <nil>

16.2 函数值

16.2.1 函数变量

函数可以像普通变量一样赋值、传递:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import "strings"

// 标准库函数可以直接赋给同签名的函数类型变量
var toUpper func(string) string = strings.ToUpper
fmt.Println(toUpper("hello"))  // HELLO

// 函数变量之间也可以互相赋值(只要签名一致)
toUpper2 := toUpper
fmt.Println(toUpper2("world"))  // WORLD

// 不同签名的函数不能互相赋值
// var bad func(string) (string, error) = strings.ToUpper  // 编译错误:类型不匹配

函数变量本质上是一个指向代码的指针加上捕获的闭包环境,所以它的零值是 nil,而且不能用作 map 的键、也不能用 == 比较(见 16.3 节)。

16.2.2 nil 函数值

声明但未赋值的函数类型变量默认是 nil:

1
2
3
4
5
var f func(int) int
fmt.Println(f == nil)  // true

// 调用 nil 函数会 panic
// f(1) // panic: runtime error: invalid memory address or nil pointer dereference

这意味着在调用函数值之前,一定要检查它是否为 nil:

1
2
3
4
5
6
7
var f func(int) int

if f != nil {
    fmt.Println(f(10))
} else {
    fmt.Println("f is nil, skip calling")
}

16.3 函数不能比较

Go 里函数值之间不能比较——== 和 != 都不行。唯一的例外是和 nil 比较:

1
2
3
4
5
6
7
8
9
add := func(a, b int) int { return a + b }
add2 := add
add3 := func(a, b int) int { return a + b }

fmt.Println(add == nil)    // false —— 和 nil 比较是允许的
fmt.Println(add2 == nil)   // false

// fmt.Println(add == add2)  // 编译错误:invalid operation: add == add2 (func can only be compared to nil)
// fmt.Println(add == add3)  // 同样是编译错误,跟两个函数是不是同一段代码无关

为什么不允许?因为闭包让“相等”没有合理的定义:两个由同一个字面量生成的函数,捕获的环境可能不同,底层实现也可能不同。语言干脆规定函数类型不可比较(因此也不能做 map 的键),只留下“是不是 nil”这一个判断。

如果你确实需要区分两个函数,通常是给它加一个可比较的标识(比如 ID 或名字),比较那个标识,而不是比较函数本身。


16.4 函数模式

16.4.1 回调模式

把函数作为参数传递给另一个函数,实现"回调":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import "fmt"

type VisitFunc func(int)

// 对切片中每个元素执行回调
func visit(nums []int, fn VisitFunc) {
    for _, n := range nums {
        fn(n)
    }
}

nums := []int{1, 2, 3, 4, 5}

visit(nums, func(n int) {
    fmt.Printf("%d*2=%d\n", n, n*2)
})
// 1*2=2
// 2*2=4
// 3*2=6
// 4*2=8
// 5*2=10

16.4.2 策略模式

把不同的算法封装成函数,根据需要选择不同的策略:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
type SortStrategy func(items []int) []int

func bubbleSort(items []int) []int {
    // 冒泡排序实现...
    result := make([]int, len(items))
    copy(result, items)
    for i := 0; i < len(result); i++ {
        for j := 1; j < len(result)-i; j++ {
            if result[j] < result[j-1] {
                result[j], result[j-1] = result[j-1], result[j]
            }
        }
    }
    return result
}

func quickSort(items []int) []int {
    // 快速排序实现...
    if len(items) < 2 {
        return items
    }
    pivot := items[0]
    var left, right []int
    for _, v := range items[1:] {
        if v < pivot {
            left = append(left, v)
        } else {
            right = append(right, v)
        }
    }
    left = quickSort(left)
    right = quickSort(right)
    return append(append(left, pivot), right...)
}

func sortItems(items []int, strategy SortStrategy) []int {
    return strategy(items)
}

data := []int{5, 2, 8, 1, 9}
fmt.Println(sortItems(data, bubbleSort))  // [1 2 5 8 9]
fmt.Println(sortItems(data, quickSort))   // [1 2 5 8 9]
fmt.Println(data)                         // [5 2 8 1 9] —— 原切片没被动过

注意这两个排序函数都先复制了输入再排序(bubbleSort 用 copy,quickSort 用 append 生成新切片),所以调用 sortItems 不会修改调用方的 data。这是作为“策略”传进来的函数应该遵守的约定之一。

16.4.3 装饰器模式

用一个函数包装另一个函数,在其执行前后加上额外的行为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
type Handler func() string

// 装饰器:计时
func withTimer(next Handler) Handler {
    return func() string {
        start := time.Now()
        result := next()
        fmt.Printf("took %v\n", time.Since(start))
        return result
    }
}

// 装饰器:重试
func withRetry(next Handler) Handler {
    return func() string {
        for i := 0; i < 3; i++ {
            result := next()
            if result != "" {
                return result
            }
            fmt.Println("retry...", i+1)
        }
        return ""
    }
}

handler := func() string {
    return "done"
}

// 链式装饰
wrapped := withRetry(withTimer(handler))
fmt.Println(wrapped())

16.4.4 中间件模式

在 web 服务中,中间件就是一个接收 http.HandlerFunc、返回 http.HandlerFunc 的函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import (
    "fmt"
    "net/http"
    "time"
)

func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        fmt.Printf("%s %s took %v\n", r.Method, r.URL.Path, time.Since(start))
    }
}

func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("Authorization") == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    }
}

helloHandler := func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello, World!")
}

// 中间件的包裹顺序 = 执行顺序的外到内:
// 请求先被 loggingMiddleware 打点计时,再交给 authMiddleware 鉴权,最后才到业务处理
wrapped := loggingMiddleware(authMiddleware(helloHandler))
// http.HandleFunc("/hello", wrapped)
fmt.Println("Middleware chain ready!")

顺带一提:http.HandlerFunc 本身就是一个“把普通函数包装成接口实现”的适配器——它是函数类型,同时实现了 http.Handler 的 ServeHTTP 方法。所以这里的 next.ServeHTTP(w, r) 和 next(w, r) 完全等价。


最后修改 September 19, 2026: 更新 (3489033b1)