Package(包)
包(package)是 Go 组织和复用代码的基本单位。每个 .go 文件都必须先声明自己所属的包;同一目录中的普通 Go 源文件通常属于同一个包,并共同形成一套包级作用域。
最小包示例
下面创建一个模块,其中 greeting 包负责生成问候语,main 包负责启动程序:
package-demo/
├── go.mod
├── greeting/
│ └── greeting.go
└── main.go
在 package-demo 目录初始化模块:
go mod init example.com/package-demo
greeting/greeting.go:
package greeting
func Message(name string) string {
return "Hello, " + name
}
main.go:
package main
import (
"fmt"
"example.com/package-demo/greeting"
)
func main() {
fmt.Println(greeting.Message("Go"))
}
在模块根目录运行:
go run .
输出如下:
Hello, Go
包声明与目录
包声明必须出现在源文件的非注释内容开头:
package greeting
需要区分三个名称:
| 名称 | 示例 | 作用 |
|---|---|---|
| 包名 | greeting | 在 Go 代码中限定包内名称 |
| 导入路径 | example.com/package-demo/greeting | 唯一定位要导入的包 |
| 模块路径 | example.com/package-demo | 在 go.mod 中标识整个模块 |
包名通常与目录最后一段一致,但语言并不要求两者必须相同。调用代码使用的是被导入包声明的包名,而不是自行截取导入路径。为了降低理解成本,除版本目录等惯例场景外,应让它们保持一致。
同一目录中的普通 .go 文件不能混用多个包名。测试文件是一个常见例外:它可以使用当前包名,也可以使用带 _test 后缀的外部测试包名,例如 greeting_test。
包级作用域
同一个包可以拆分为多个文件。某个文件声明的包级类型、变量、常量和函数,可以被该包的其他文件直接使用:
// config.go
package greeting
const defaultName = "Go"
// greeting.go
package greeting
func DefaultMessage() string {
return "Hello, " + defaultName
}
文件名不形成命名空间。相反,import 声明只在当前源文件中有效:即使同包的另一个文件已经导入 fmt,当前文件要使用 fmt 时仍需自行导入。
main 包与程序入口
库包用于提供可复用能力;名称为 main 的包用于构建可执行程序。可执行程序还必须提供签名固定的 main 函数:
package main
func main() {
// 程序从这里开始执行。
}
main.main 不接收参数,也不返回值,不能由其他代码显式调用。命令行参数可通过标准库的 os.Args 获取。
包初始化
程序启动时会先初始化完整依赖图,再调用 main.main;包不是等到第一次调用其函数时才懒初始化。一个包会先等待它导入的包完成初始化,再按依赖关系计算包级变量初始值,最后运行无参数、无返回值的 init 函数。同一源文件中的多个 init 按出现顺序执行,但业务逻辑不应依赖不同文件之间的排列顺序。
package settings
import "fmt"
var Mode = loadMode()
func loadMode() string {
fmt.Println("initialize Mode")
return "development"
}
func init() {
fmt.Println("run init")
}
一个包可以声明多个 init 函数,程序不能主动调用它们。工程代码应优先使用显式构造函数和依赖注入;在 init 中执行网络请求、读取不稳定外部资源或启动后台任务,会使失败处理和测试变得困难。
依赖规则
- Go 不允许包之间形成导入环。例如
order导入user时,user不能再直接或间接导入order。 - 导入包后必须实际使用;未使用的导入会导致编译失败。
- 包名应简短、全小写,并避免下划线或重复上层目录含义。
- 一个包应围绕一组内聚职责组织,不要只按“工具类”或文件数量拆包。
导入环通常说明职责边界需要调整。可以把双方共同依赖的抽象移到更基础的包,或让高层包通过接口依赖调用方提供的实现,但不应为了消除环而随意创建含义模糊的公共包。
与 TypeScript 模块的区别
TypeScript 文件通常自身就是模块,并通过 export 逐项公开名称。Go 则以目录中的包作为主要编译和封装单位:同包文件共享包级声明,名称能否被其他包访问由标识符首字母决定。Go 模块(module)又位于包之上,负责记录模块路径、Go 版本和依赖关系。
模块版本选择、go.mod、语义导入版本和私有模块配置见 Module Manager(模块管理)。
常见错误
- 把包名写成完整导入路径;
package后只写包名。 - 认为同包其他文件的导入可以复用;每个源文件都要声明自己的导入。
- 在库目录中使用
package main,导致它无法作为普通库包导入。 - 依赖
init隐式完成关键业务配置,使初始化失败无法明确返回给调用方。 - 让两个包互相导入;Go 编译器会直接拒绝导入环。
小结
- 每个 Go 源文件都必须声明包,同目录的普通源文件通常属于同一个包。
- 包名、导入路径和模块路径相互关联,但不是同一个概念。
- 同包文件共享包级声明,导入声明则只在当前文件中有效。
main包和main函数组成可执行程序入口。- 包初始化由依赖初始化、包级变量初始化和
init函数组成,关键流程宜保持显式。