在 Go 1.27.1 里验证错误包装:fmt.Errorf 的 %w 形成 *fmt.wrapError 链,errors.Is 沿链比较而 == 不行,%v 不包装;一个 Errorf 里两个 %w 得到 *fmt.wrapErrors;errors.As 取出沿链第一个匹配类型(os.Open 的 *fs.PathError);自定义 Is 方法;不可比较的错误类型不 panic;errors.As 的三种 panic;errors.AsType 泛型版本;errors.Is 每层约 2.8 ns。
在 Go 1.27.1 里验证 errors.Join:返回 *errors.joinError,Error() 用换行连接,Is 与 As 遍历所有子错误(As 深度优先取第一个匹配),errors.Unwrap 对它返回 nil,nil 参数被丢弃、全是 nil 时返回 nil;与同一个 Errorf 里的两个 %w 对比;循环里收集错误的写法;Join 本身 1 次分配,Error() 分配 4 到 6 次。
用一个真实的多包多文件模块验证 Go 的初始化顺序:依赖的包先初始化(b → a → c → main);一个包内先按“声明顺序里最靠前的就绪变量”初始化所有变量、再按文件名顺序执行所有 init(同一文件里按书写顺序);go run 命令行里文件顺序会改变 init 顺序;GODEBUG=inittrace=1 打印每个包的 init 耗时;初始化循环与对 init 的非法使用的编译错误。
在 Go 1.27.1 里验证常量与 iota:iota 的跳过、位标志、一行多个、隐式重复;无类型常量精确计算(常量 0.1 + 0.2 == 0.3 为 true,变量为 false,1 << 100 >> 98 = 4);整数常量除法;1.5 * time.Second 是编译错误(truncated to int64);无类型整数常量的上限用二分法实测是 1 << 511;溢出、类型不匹配、非常量移位等编译错误。
在 Go 1.27.1 里验证 unsafe.Pointer 的用法与风险:合法的类型转换、unsafe.Add 与 Offsetof;uintptr 不是引用——递归撑大 goroutine 栈后局部变量的地址变了,unsafe.Pointer 被同步改写而 uintptr 变成悬空地址,堆对象 3 次 GC 后地址不变;go vet 的 unsafeptr 警告与 -d=checkptr=1 的运行时检查(未对齐、指针运算越界会 fatal,目标类型不含指针的未对齐与跨原对象的转换不报)。
在 Go 1.27.1(arm64)上量 reflect 的成本:按下标读字段 2.26–3.61 ns(直接读 1.90 ns),按名字 FieldByName 29.61 ns;reflect.Value.Call 调函数 121.20 ns(直接调 1.96 ns,约 62 倍)、调方法 140.11 ns,每次 MethodByName 335.39 ns;reflect.DeepEqual 180.26 ns(手写比较 4.55 ns);只取类型信息的 reflect.TypeOf().Kind() 与类型断言一样快;并统计各操作的分配次数。
在 Go 1.27.1(arm64 macOS,系统 clang)上量 cgo 的成本:一次空的 C 函数调用 21.22 ns(Go 调用 1.97 ns,约 10.8 倍);批量处理才划算(C 对 4096 字节求和 72.03 ns,Go 循环 1266.25 ns);go tool cgo 生成的包装只是调用 runtime.cgocall;100 个 goroutine 阻塞在 C 里会多创建 98 个线程(Go 的 time.Sleep 只多 1 个);CGO_ENABLED=0、交叉编译与 Go 指针传递规则的限制。
在 Go 1.27.1 里验证 go:embed:string、[]byte、embed.FS 三种变量;目录嵌入默认跳过 . 与 _ 开头的文件,all: 前缀才包含;embed.FS 只读、按名字排序、修改时间为零(http.FS 因此没有 Last-Modified);ReadFile 拷贝内容(2 次分配,57 ns),string 变量零成本;嵌入的数据在可执行文件里:[]byte 在数据段、string 在只读段,各占一份(1 MiB 文件两种声明共增加 2.02 MiB);各种写错时的编译错误。
用 go list 在不同 GOOS / GOARCH / -tags 下验证 Go 1.27.1 的构建约束:_GOOS、_GOARCH 文件名后缀与 //go:build 表达式共同决定哪些文件参与编译;名字里没有下划线的 windows.go 不受约束;//go:build ignore 的文件只在 go run 显式给出时才被使用;旧的 // +build 仍然生效;unix 标签覆盖哪些系统;go vet 能发现写错位置和两种写法不一致。
在一个本机 file:// GOPROXY 里搭出 4 个模块的多个版本,真实验证最小版本选择:构建列表取“需求图里出现过的最大版本”(d v1.4.0),而不是仓库里最新的版本(e 有 v1.3.0 却选 v1.2.0);go mod tidy 写出间接依赖;go get 升级与降级会牵连其它模块(降级 d 到 v1.1.0 使 b 降级、c 被移除);go mod why、exclude 与 replace 的效果。
用本机 file:// 模块仓库与两个本地模块验证 go.work 工作区:不动 app/go.mod 就让 app 使用本地修改过的 lib(replace 方案要改 go.mod);go work init/use/edit/sync 的用法;工作区模式不允许 GOFLAGS=-mod=mod;工作区里版本选择取所有模块需求的并集最大值(e v1.3.0),lib 单独构建却是 e v1.1.0;GOWORK=off 回到发布的版本;go mod tidy 不受工作区影响。
在 Go 1.27.1 里实测二进制体积:hello world 1.75–2.32 MiB,加 encoding/json 3.59 MiB,加 net/http 约 7.9 MiB;-ldflags="-s -w" 统一小 33%–35%;pclntab 与代码一样大;panic 的栈回溯靠 pclntab,所以剥离符号表后仍有函数名;按包统计 runtime 占 42%;通过 reflect.Value.MethodByName 调用方法会一次多约 0.9 MiB 并保留所有导出方法(每个约 470 字节);各平台 hello world 的大小。