Go中函数作参数需显式定义类型,如type Filter func(int) bool;传参前须判nil;闭包捕获循环变量要绑定局部变量或传参;sort.Slice等要求严格匹配func(i,j int) bool签名。

Go 里函数能当参数传,但不是写个 func(...) 就完事——类型不匹配、签名不对、nil panic、闭包捕获错变量,全在编译或运行时等着你。
函数类型必须显式声明,不能靠“看着像”蒙混过关
Go 不会把两个签名相同的函数字面量自动当成同一类型。哪怕你写了两次 func(int) bool,它们在类型系统里仍是不兼容的裸类型。
正确做法是提前用 type 定义函数类型:
type Filter func(int) bool type Comparator func(i, j int) bool type Processor func(string) string
之后所有参数、变量、返回值都统一用这个类型名。好处有三:
立即学习“go语言免费学习笔记(深入)”;
- 调用方一眼看懂契约,不用数参数猜语义
- 多个地方复用同一函数签名时,改一处全生效
- 避免把
func(int) bool直接塞进参数位导致后期无法加文档或扩展(比如加 context)
传进去之前,一定要检查是否为 nil
Go 不会帮你跳过 nil 函数调用。一旦你在高阶函数里直接执行 f(x) 而 f == nil,运行时就 panic。
常见场景如可选回调、策略模式中的 fallback:
- 不要写:
if f != nil { f(x) }—— 这只是补救,不是设计 - 应该在函数签名里明确可选性,比如用指针:
func DoSomething(data []int, filter *Filter),然后内部判空 - 更推荐:默认提供一个 noop 实现,比如
func(_ int) bool { return true },让调用方按需覆盖
闭包捕获外部变量时,注意循环变量陷阱
在 for 循环里创建多个函数并捕获循环变量,很容易所有函数都指向最后一次迭代的值。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
错误写法:
for _, v := range []int{1, 2, 3} {
handlers = append(handlers, func() { fmt.Println(v) })
}
// 执行后全输出 3
正确解法只有两种:
- 用局部变量绑定:
for _, v := range data { v := v; handlers = append(..., func() { fmt.Println(v) }) } - 把变量作为参数传入闭包:
func(val int) func() { return func() { fmt.Println(val) } }(v)
别依赖编辑器自动修复——Go 编译器不会报错,但结果一定不对。
sort.Slice 等标准库函数对函数签名极其敏感
sort.Slice 的第二个参数必须是 func(i, j int) bool,它传进来的是下标,不是元素本身。
典型翻车现场:
- 误写:
sort.Slice(users, func(a, b User) bool { ... })→ 编译失败:参数类型不匹配 - 误写:
sort.Slice(users, func(i, j int) bool { return i.Age → <code>i是 int,没有Age字段 - 正确写法:
sort.Slice(users, func(i, j int) bool { return users[i].Age
如果逻辑复杂,建议先定义类型:type ByAge func(i, j int) bool,再传 ByAge(func(i, j int) bool { ... }),既清晰又防错。
最常被忽略的其实是函数类型的“不可隐式转换”特性:哪怕签名一模一样,func(int) bool 和你定义的 Filter 在 Go 类型系统里就是两个独立类型,强制转型或适配绕不开。

















