Go 语言编译期断言 ============================================================ 这篇文章是关于一个鲜为人知的方法让 Go 在编译期断言。你可能不会使用它,但是了解一下也很有趣。 作为一个热身,这里是一个在 Go 中相当知名的编译时断言:接口满意度检查。 在这段代码([playground][1])中,`var _ =` 行确保类型 `W` 是一个 `stringWriter`,由 [`io.WriteString`][2] 检查。 ``` package main import "io" type W struct{} func (w W) Write(b []byte) (int, error) { return len(b), nil } func (w W) WriteString(s string) (int, error) { return len(s), nil } type stringWriter interface { WriteString(string) (int, error) } var _ stringWriter = W{} func main() { var w W io.WriteString(w, "very long string") } ``` 如果你注释掉了 `W` 的 `WriteString` 方法,代码将无法编译: ``` main.go:14: cannot use W literal (type W) as type stringWriter in assignment: W does not implement stringWriter (missing WriteString method) ``` 这是很有用的。对于大多数同时满足 `io.Writer` 和 `stringWriter` 的类型,如果你删除 `WriteString` 方法,一切都会像以前一样继续工作,但性能较差。 你可以使用编译时断言保护你的代码,而不是试图使用[`testing.T.AllocsPerRun'][3]为性能回归编写一个脆弱的测试。 这是[一个实际的 io 包中的技术例子][4]。 * * * 好的,让我们隐晦一点! 接口满意检查是很棒的。但是如果你想检查一个简单的布尔表达式,如 `1 + 1 == 2` ? 考虑这个代码([playground] [5]): ``` package main import "crypto/md5" type Hash [16]byte func init() { if len(Hash{}) < md5.Size { panic("Hash is too small") } } func main() { // ... } ``` `Hash` 可能是某种抽象的哈希结果。`init` 函数确保它将与[crypto/md5][6]一起工作。如果你改变 `Hash` 为(也就是)`[8]byte`,它会在进程启动时发生混乱。但是,这是一个运行时检查。如果我们想要早点发现怎么办? 就是这样。(没有 playground 链接,因为这在 playground 上不起作用。) ``` package main import "C" import "crypto/md5" type Hash [16]byte func hashIsTooSmall() func init() { if len(Hash{}) < md5.Size { hashIsTooSmall() } } func main() { // ... } ``` 现在如果你改变 `Hash` 为 `[8]byte`,它将在编译过程中失败。(实际上,它在链接过程中失败。足够接近我们的目标了。) ``` $ go build . # demo main.hashIsTooSmall: call to external function main.init.1: relocation target main.hashIsTooSmall not defined main.init.1: undefined: "main.hashIsTooSmall" ``` 这里发生了什么? `hashIsTooSmall` 是[一个没有函数体的声明][7]。编译器假定别人将提供一个实现,也许是一个汇编程序。 当编译器可以证明 `len(Hash {})