Go 中 func 类型必须显式声明别名,因无隐式类型推导,相同签名的函数值若非同一别名则类型不兼容;sort.Slice 的比较函数需接收索引 i、j 并返回 bool。

func 类型必须显式声明,不能省略——这是 Go 里传函数最常卡住的地方。不定义类型,编译器直接报错:cannot use function value as type xxx in argument to yyy。
为什么必须先定义 func 类型
Go 没有 JavaScript 那样的隐式函数类型推导。每个函数值都有唯一类型,哪怕签名完全一样,func(int) bool 和另一个 func(int) bool(没用同一类型别名)也不能互相赋值或传参。
所以你得先写:
type Filter func(int) bool type Mapper func(string) string type BinaryOp func(float64, float64) float64
之后才能用它们声明参数、变量或字段。否则编译器不认识这个“形状”。
sort.Slice 里的比较函数怎么写
标准库 sort.Slice 是最典型的函数参数使用场景,它的第二个参数是:
立即学习“go语言免费学习笔记(深入)”;
func(i, j int) bool
注意两点:
- 参数是两个索引
i和j,不是元素本身;返回true表示i对应的元素该排在j前面 - 不能直接传
func(a, b int) bool { return a —— 因为类型不匹配(参数名无关,但类型签名必须一致)
正确写法:
sort.Slice(users, func(i, j int) bool {
return users[i].Age < users[j].Age
})
或者提前定义类型再复用:
type ByAge func(i, j int) bool
sort.Slice(users, ByAge(func(i, j int) bool {
return users[i].Name < users[j].Name
}))
匿名函数传参时的常见坑
传匿名函数没问题,但要注意作用域和闭包变量生命周期:
- 不要在循环里直接捕获循环变量(如
for i := range xs),容易所有闭包都引用最后一个i值 - 如果函数要长期持有(比如注册为回调),确保它引用的局部变量不会提前被 GC(例如不要捕获大 struct 的指针,除非真需要)
- 函数类型不可比较、不能作
map键、不能当struct字段的类型(除非是已命名的func类型)
例如这个错例:
m := make(map[func(int) bool]int) // 编译错误:invalid map key type func(int) bool
得改成:
type Predicate func(int) bool m := make(map[Predicate]int) // OK
回调函数里 panic 怎么处理
如果你写的高阶函数会调用用户传入的函数(比如 forEach、transform),而用户函数 panic 了,默认会向上冒泡崩掉整个调用栈。
想兜住它,得用 recover,但注意:recover 只在 defer 中有效,且只对当前 goroutine 生效。
典型安全包装写法:
func safeCall(f func(), name string) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in %s: %v", name, r)
}
}()
f()
}
然后在你的高阶函数里这样用:
for _, item := range items {
safeCall(func() { handler(item) }, "user handler")
}
不过多数时候,让 panic 暴露出来反而是更清晰的调试方式——掩盖它反而会让问题更难定位。
真正容易被忽略的是:函数类型一旦定义,就和底层实现完全解耦。你改了函数体不影响类型兼容性,但改了签名(比如加个error 返回)就必须同步更新类型定义,否则所有调用点全报错。这不是语法糖,是类型系统在强制你显式表达契约。


















