Go 語言編譯期斷言

Josh Bleecher Snyder發表於2017-03-28

這篇文章是關於一個鮮為人知的讓 Go 在編譯期斷言的方法。你可能不會使用它,但是瞭解一下也很有趣。

作為一個熱身,來看一個在 Go 中熟知的編譯期斷言:介面滿意度檢查。

在這段程式碼(playground)中,var _ = 行確保型別 W 是一個 stringWriter,其由 io.WriteString 檢查。

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")
}

如果你註釋掉了 WWriteString 方法,程式碼將無法編譯:

main.go:14: cannot use W literal (type W) as type stringWriter in assignment:
    W does not implement stringWriter (missing WriteString method)

這是很有用的。對於大多數同時滿足 io.WriterstringWriter 的型別,如果你刪除 WriteString 方法,一切都會像以前一樣繼續工作,但效能較差。

你可以使用編譯期斷言保護你的程式碼,而不是試圖使用`testing.T.AllocsPerRun'為效能迴歸編寫一個脆弱的測試。

這是一個實際的 io 包中的技術例子


好的,讓我們低調一點!

介面滿意檢查是很棒的。但是如果你想檢查一個簡單的布林表示式,如 1 + 1 == 2

考慮這個程式碼(playground):

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 一起工作。如果你改變 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一個沒有函式體的宣告。編譯器假定別人將提供一個實現,也許是一個彙編程式。

當編譯器可以證明 len(Hash {})< md5.Size 時,它消除了 if 語句中的程式碼。結果,沒有人使用函式 hashIsTooSmall,所以連結器會消除它。沒有其他損害。一旦斷言失敗,if 語句中的程式碼將被保留。不會消除 hashIsTooSmall。連結器然後注意到沒有人提供了函式的實現然後連結失敗,並出現錯誤,這是我們的目標。

最後一個奇怪的點:為什麼是 import "C"? go 工具知道在正常的 Go 程式碼中,所有函式都必須有主體,並指示編譯器強制執行。通過切換到 cgo,我們刪除該檢查。(如果你在上面的程式碼中執行 go build -x,而沒有新增 import "C" 這行,你會看到編譯器是用 -complete 標誌呼叫的。)另一種方法是新增 import "C"向包中新增一個名為 foo.s 的空檔案

我僅見過一次這種技術的使用,是在編譯器測試套件中。還有其他可以發揮想象力的使用,但我還沒見到過。

可能就是這樣吧。 :)


via: http://commaok.xyz/post/compile-time-assertions

作者:Josh Bleecher Snyder 譯者:geekpi 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

相關文章