
go 禁止子包直接导入 main 包以避免循环依赖,正确做法是将 main 中的函数作为参数(回调)传入子包函数,利用 go 的函数一等公民特性实现解耦调用。
在 Go 语言中,main 包具有特殊地位:它不能被其他包导入(否则会触发编译错误 import "xxx": cannot import main package)。这是 Go 编译器强制实施的设计约束,目的是防止循环依赖、确保程序入口清晰且可独立构建。因此,你无法、也不应试图在 subpackage/lib.go 中通过 import "github.com/ddavison/project" 来调用 main.go 中的 DoSomething()。
✅ 正确且符合 Go 惯例的解决方案是:将函数作为值传递(即“回调”)。Go 中函数是一等公民,可赋值给变量、作为参数传入、或作为返回值,这为跨包协作提供了简洁优雅的解耦机制。
✅ 实现步骤
-
在子包中定义接受函数类型的参数
修改 subpackage/lib.go,让 Hello 接收一个无参无返回值的函数类型:
// subpackage/lib.go
package subpackage
import "fmt"
// Complete 是一个函数类型别名,表示无参数、无返回值的函数
type Complete func()
func Hello(complete Complete) {
fmt.Println("hello")
if complete != nil {
complete() // 安全调用回调
}
}? 提示:添加 nil 检查可提升健壮性,避免 panic。
-
在 main.go 中传入函数引用
注意:传入的是函数名(不带括号),即函数值本身;不是调用结果:
// main.go
package main
import (
"fmt"
"github.com/ddavison/project/subpackage"
)
func main() {
subpackage.Hello(DoSomething) // ✅ 传函数值,非调用
}
func DoSomething() {
fmt.Println("done!")
}⚠️ 注意事项
- ❌ 不要写 subpackage.Hello(DoSomething()) —— 这会先执行 DoSomething 并尝试传入其返回值(此处为 nil),导致编译失败或运行时 panic。
- ? main 包不可被导入,因此任何依赖 main 的设计(如导出变量、接口实现绑定)均违反 Go 架构原则。
- ? 若逻辑复杂需深度交互,建议重构:将 DoSomething 及相关功能提取至独立工具包(如 github.com/ddavison/project/pkg/util),由 main 和 subpackage 共同导入——这才是长期可维护的工程化方案。
✅ 总结
Go 通过禁止 main 包被导入,强制开发者面向接口/函数进行松耦合设计。回调模式不仅解决了跨包调用问题,还提升了测试性(例如单元测试中可传入 mock 函数)。记住口诀:“main 不导出,函数当参数;解耦靠类型,扩展靠抽象。”










