一、机制

包的定义与语义

  • 每个 .go 文件的第一行必须声明 package
  • 同目录下的所有文件必须属于同一个包
  • 包名决定文件编译归属,不等于导入路径
  • 包名与文件夹名可以不同,例如在目录 github.com/foo/bar 下声明 package baz
project/
└── math/
    ├── add.go       package math
    ├── sub.go       package math
    └── internal.go  package math

这三个文件的包名都必须是 math。如果某个文件写成:

package calc

会报错:

found packages math (add.go) and calc (internal.go) in /path/to/project/math

同包文件的作用域共享

  • 同一个包的所有 .go 文件共享同一作用域
  • 全局变量、常量、类型、函数在整个包内可见
  • 包内标识符可以互相访问,无需导入

导出规则

  • 首字母大写:导出
  • 首字母小写:仅包内可见

二、加载、初始化与生命周期

包加载顺序

当你执行 go run main.go

  1. 解析 import 依赖图;
  2. 按拓扑排序加载包;
  3. 检测循环依赖(有则报错);
  4. 依次初始化每个包:
    • 全局变量初始化;
    • 执行 init() 函数;
    • 初始化完成后才能被依赖包使用。

初始化顺序规则

  • 依赖优先原则:先初始化被导入的包
  • 同一包内:
    • 文件按字母序加载
    • 变量初始化顺序按代码出现顺序
    • init() 顺序与变量顺序一致

包初始化的陷阱

  • 初始化时引用未初始化变量 → 零值;
  • 包之间相互依赖初始化可能导致意外零值;
  • 多个 init() 按文件顺序执行;
  • init() 不能被显式调用。

三、导入机制

导入路径规则

  • 导入路径是模块路径 + 相对目录。
  • 相对路径导入在模块模式下被禁止:
import "../foo" //  不允许

导入别名

import io "io/ioutil"

点导入

import . "fmt"
Println("Hello") // 无需 fmt.

匿名导入

import _ "net/http/pprof"

执行 init() 但不导入符号。常用于:

  • 注册插件;
  • 数据库驱动;
  • HTTP 路由注册

四、编译原理

编译单元

Go 的编译单元是包,编译器不会单独编译文件,而是一次性编译整个包

例如:

go build ./utils

会编译 utils 包下所有 .go 文件为.a 对象文件

编译产物

  • 每个包被编译为 .a 文件
  • 包含:
    • 符号表;
    • 类型信息;
    • 编译后中间码;
    • 依赖包索引。

缓存位置:

$GOCACHE or $HOME/Library/Caches/go-build/

符号解析

编译时,Go 为每个包生成独立的符号表。包名是符号命名空间,防止冲突:

fmt.Println  // 表示 fmt 包下的 Println 符号

五、模块系统

GOPATH

GOPATH 是 Go 语言早期管理工作空间和依赖的核心环境变量。它指向一个目录,Go 工具链会在这个目录下寻找:

  1. 源码:存放 Go 源码文件
  2. 包对象:编译生成的包对象文件
  3. 可执行文件:编译生成的可执行程序
GOPATH/
├── src/      # 所有源码包和项目都在这里
   └── github.com/user/project
├── pkg/      # 编译生成的包文件
   └── linux_amd64/github.com/user/project.a
└── bin/      # 可执行程序
    └── myapp

GOPATH 有些缺点:

  • 项目目录必须在 GOPATH 下,灵活性差。以前没有版本管理
  • go get 下载的是最新 commit,无法固定版本
  • 不方便跨项目引用本地包,容易出现路径冲突

Go 1.11+ 引入 Go Modules,彻底取代 GOPATH

Modules 的核心概念

模块

  • 一个模块就是一个包含 go.mod 文件的代码集合
  • go.mod 定义了模块路径、Go 版本、依赖关系
  • 每个模块可以包含多个包
  • 模块路径就是导入路径的前缀

版本管理

  • Go Modules 使用语义化版本号(Semantic Versioning, SemVer)。
  • 依赖会固定到特定版本(v1.2.3),确保可复现构建。

go.mod 文件详解

module github.com/bear/project

go 1.22

require (
    github.com/gin-gonic/gin v1.9.0
    github.com/jmoiron/sqlx v1.3.5
)

replace github.com/old/lib => ../local/lib
exclude github.com/buggy/lib v1.0.0
  • module:模块路径,也是导入路径的前缀。
  • go 1.22:表示使用的 Go 版本。
  • require:声明依赖模块及版本。
  • replace:替换模块源路径(常用于本地开发或 fork)。
  • exclude:排除某个版本的模块,避免使用。

go.sum 文件详解

  • go.sum 是 Go Modules 的依赖校验文件
  • 记录每个依赖模块的校验和

常用命令

命令 功能
go mod init <module> 初始化模块,生成 go.mod
go mod tidy 清理无用依赖,下载缺失依赖
go mod download 下载模块依赖到缓存
go get <module>@<version> 添加或升级依赖模块
go list -m all 查看模块及版本列表
go mod verify 校验模块完整性
go mod graph 查看依赖关系图

六、封装与 internal 机制

internal 是 Go 独特的封装机制:

project/
└── internal/
    └── db/
        └── mysql.go

规则:

  • 只能被同一模块下的包导入;
  • 外部模块导入时报错:
use of internal package not allowed

用法:

  • 实现包级别私有;
  • 封装核心逻辑,防止外部依赖

七、包与接口设计哲学

Go 鼓励:

  • 小包哲学:每个包只做一件事;
  • 稳定包依赖:底层包不依赖上层;
  • 接口定义在使用方不是提供方;
  • 最小导出原则:只导出必须公开的符号