Go 1.4 發行說明

Go 1.4 簡介

最新的 Go 版本 1.4 如期而至,距離 1.3 版本釋出已有六個月。

它只包含一個微小的語言更改,即對 for-range 迴圈的向後相容的簡單變體,以及一個可能破壞編譯器涉及指向指標的方法的更改。

此版本主要側重於實現工作,改進垃圾回收器併為未來幾個版本中推出的完全併發垃圾回收器做準備。堆疊現在是連續的,必要時重新分配,而不是連結新的“段”;因此,此版本消除了臭名昭著的“熱堆疊拆分”問題。還有一些新工具可用,包括 go 命令中對構建時原始碼生成的支援。此版本還增加了對 Android 和 Native Client (NaCl) 上的 ARM 處理器以及 Plan 9 上的 AMD64 的支援。

一如既往,Go 1.4 遵守了相容性承諾,幾乎所有內容在遷移到 1.4 時都將繼續編譯和執行,無需更改。

語言變化

For-range 迴圈

在 Go 1.3 之前,for-range 迴圈有兩種形式

for i, v := range x {
    ...
}

for i := range x {
    ...
}

如果一個人對迴圈值不感興趣,只對迭代本身感興趣,仍然需要提及一個變數(可能是空識別符號,如 for _ = range x),因為形式

for range x {
    ...
}

在語法上是不允許的。

這種情況看起來很尷尬,因此從 Go 1.4 開始,無變數形式現在是合法的。這種模式很少出現,但出現時程式碼可以更簡潔。

更新:此更改嚴格向後相容現有 Go 程式,但分析 Go 解析樹的工具可能需要修改以接受這種新形式,因為 RangeStmtKey 欄位現在可能為 nil

**T 上的方法呼叫

給定這些宣告,

type T int
func (T) M() {}
var x **T

gcgccgo 都接受方法呼叫

x.M()

這是對指向指標 x 的雙重解引用。Go 規範允許自動插入一個解引用,但不允許兩個,因此根據語言定義,此呼叫是錯誤的。因此,Go 1.4 中已禁止此呼叫,這是一項破壞性更改,儘管受影響的程式很少。

更新:依賴舊的錯誤行為的程式碼將不再編譯,但透過新增顯式解引用很容易修復。

支援的作業系統和架構的更改

安卓

Go 1.4 可以為執行 Android 作業系統的 ARM 處理器構建二進位制檔案。它還可以構建一個 .so 庫,該庫可以使用 mobile 子儲存庫中的支援包由 Android 應用程式載入。此實驗性埠的計劃的簡要說明可在此處檢視

ARM 上的 NaCl

上一個版本引入了對 32 位 x86 (GOARCH=386) 和使用 32 位指標的 64 位 x86 (GOARCH=amd64p32) 的 Native Client (NaCl) 支援。1.4 版本增加了對 ARM (GOARCH=arm) 的 NaCl 支援。

AMD64 上的 Plan9

此版本增加了對 AMD64 處理器上 Plan 9 作業系統的支援,前提是核心支援 nsec 系統呼叫並使用 4K 頁面。

相容性指南的更改

unsafe 包透過利用實現的內部細節或資料的機器表示來破壞 Go 的型別系統。關於 Go 相容性指南中規定的 unsafe 的使用意味著什麼,從未明確指定。當然,答案是,我們無法保證使用不安全程式碼的相容性。

我們已在版本中包含的文件中澄清了這種情況。Go 相容性指南unsafe 包的文件現在明確指出,不安全程式碼不保證保持相容。

更新:沒有技術上的改變;這只是文件的澄清。

實現和工具的更改

執行時的更改

在 Go 1.4 之前,執行時(垃圾回收器、併發支援、介面管理、對映、切片、字串等)主要用 C 編寫,並帶有一些彙編器支援。在 1.4 中,大部分程式碼已轉換為 Go,以便垃圾回收器可以掃描執行時中的程式堆疊,並獲取有關哪些變數處於活動狀態的準確資訊。此更改很大,但對程式不應產生語義影響。

此重寫使 1.4 中的垃圾回收器能夠完全精確,這意味著它知道程式中所有活動指標的位置。這意味著堆會更小,因為不會有假陽性使非指標保持活動狀態。其他相關更改也減少了堆大小,相對於上一個版本,總體上減少了 10%-30%。

結果是堆疊不再分段,消除了“熱拆分”問題。當達到堆疊限制時,會分配一個新的更大的堆疊,所有活動的 goroutine 幀都被複制到那裡,並且對堆疊的任何指標都會更新。在某些情況下,效能可能會明顯更好,並且總是更可預測。詳細資訊可在設計文件中找到。

使用連續堆疊意味著堆疊可以更小,而不會引發效能問題,因此 1.4 中 goroutine 堆疊的預設起始大小已從 8192 位元組減小到 2048 位元組。

為計劃在 1.5 版本中實現的併發垃圾回收器做準備,現在透過函式呼叫(稱為寫屏障)而不是直接從更新值的函式完成對堆中指標值的寫入。在下一個版本中,這將允許垃圾回收器在執行時協調對堆的寫入。此更改對 1.4 中的程式沒有語義影響,但已包含在此版本中以測試編譯器和由此產生的效能。

介面值的實現已修改。在早期版本中,介面包含一個字,根據儲存的具體物件的型別,該字可以是指標或一個字標量值。這種實現對垃圾回收器來說存在問題,因此從 1.4 開始,介面值總是包含一個指標。在執行程式中,大多數介面值無論如何都是指標,因此影響最小,但將整數(例如)儲存在介面中的程式將看到更多的分配。

從 Go 1.3 開始,如果執行時發現一個記憶體字應該包含一個有效指標但實際上包含一個明顯無效的指標(例如,值 3),它就會崩潰。將整數儲存在指標值中的程式可能會違反此檢查並崩潰。在 Go 1.4 中,將 GODEBUG 變數 invalidptr=0 設定為停用崩潰作為一種變通方法,但我們無法保證未來版本能夠避免崩潰;正確的修復方法是重寫程式碼,不要將整數和指標混淆。

彙編

彙編器 cmd/5acmd/6acmd/8a 接受的語言發生了一些更改,主要是為了更容易向執行時提供型別資訊。

首先,定義 TEXT 指令標誌的 textflag.h 檔案已從連結器源目錄複製到標準位置,因此可以使用簡單的指令包含它

#include "textflag.h"

更重要的更改在於彙編器源如何定義必要的型別資訊。對於大多數程式,將資料定義(DATAGLOBL 指令)從彙編移到 Go 檔案中,併為每個彙編函式編寫一個 Go 宣告就足夠了。彙編文件描述瞭如何操作。

更新:從舊位置包含 textflag.h 的彙編檔案仍將有效,但應進行更新。對於型別資訊,大多數彙編例程無需更改,但都應進行檢查。定義資料、具有非空堆疊幀的函式或返回指標的彙編原始檔需要特別注意。必要(但簡單)的更改的描述在彙編文件中。

有關這些更改的更多資訊,請參閱彙編文件

gccgo 的狀態

GCC 和 Go 專案的釋出時間表不一致。GCC 4.9 版本包含 gccgo 的 Go 1.2 版本。下一個版本 GCC 5 很可能將包含 gccgo 的 Go 1.4 版本。

內部包

Go 的包系統使得將程式結構化為具有清晰邊界的元件變得容易,但只有兩種訪問形式:本地(未匯出)和全域性(已匯出)。有時,人們希望擁有未匯出的元件,例如,為了避免為屬於公共儲存庫但並非旨在用於其所屬程式之外的程式碼獲取介面客戶端。

Go 語言沒有強制執行這種區別的能力,但從 Go 1.4 開始,go 命令引入了一種機制來定義“內部”包,這些包不能被其所在源子樹之外的包匯入。

要建立這樣的包,請將其放置在名為 internal 的目錄中或名為 internal 的目錄的子目錄中。當 go 命令看到匯入路徑中包含 internal 的包時,它會驗證執行匯入的包是否在以 internal 目錄的父目錄為根的樹中。例如,包 .../a/b/c/internal/d/e/f 只能由以 .../a/b/c 為根的目錄樹中的程式碼匯入。它不能被 .../a/b/g 中的程式碼或任何其他儲存庫匯入。

對於 Go 1.4,內部包機制在主 Go 儲存庫中強制執行;從 1.5 及以後,它將在任何儲存庫中強制執行。

該機制的完整詳細資訊可在設計文件中找到。

規範匯入路徑

程式碼通常位於 github.com 等公共服務託管的儲存庫中,這意味著包的匯入路徑以託管服務的名稱開頭,例如 github.com/rsc/pdf。可以使用現有機制提供“自定義”或“虛榮”匯入路徑,例如 rsc.io/pdf,但這會為包建立兩個有效的匯入路徑。這是一個問題:人們可能無意中透過單個程式中的兩個不同的路徑匯入包,這是浪費的;因為使用的路徑未被識別為過時而錯過包的更新;或者透過將包移動到不同的託管服務來破壞使用舊路徑的客戶端。

Go 1.4 引入了一種用於 Go 源中包子句的註釋,用於標識包的規範匯入路徑。如果嘗試使用非規範路徑進行匯入,go 命令將拒絕編譯匯入包。

語法很簡單:在包行上放置一個標識註釋。對於我們的示例,包子句將讀取

package pdf // import "rsc.io/pdf"

有了這個,go 命令將拒絕編譯匯入 github.com/rsc/pdf 的包,確保程式碼可以移動而不會破壞使用者。

檢查是在構建時進行的,而不是下載時,因此如果 go get 由於此檢查而失敗,則已將錯誤匯入的包複製到本地計算機,應手動刪除。

為了補充這項新功能,已在更新時添加了一個檢查,以驗證本地包的遠端儲存庫是否與其自定義匯入的遠端儲存庫匹配。如果包的遠端儲存庫自首次下載以來已更改,go get -u 命令將無法更新包。新的 -f 標誌會覆蓋此檢查。

更多資訊請參見設計文件

子儲存庫的匯入路徑

Go 專案子儲存庫(code.google.com/p/go.tools 等)現在可在自定義匯入路徑下使用,將 code.google.com/p/go. 替換為 golang.org/x/,例如 golang.org/x/tools。我們將在 2015 年 6 月 1 日左右將規範匯入註釋新增到程式碼中,屆時 Go 1.4 及更高版本將停止接受舊的 code.google.com 路徑。

更新:所有從子儲存庫匯入的程式碼都應更改為使用新的 golang.org 路徑。Go 1.0 及更高版本可以解析和匯入新路徑,因此更新不會破壞與舊版本的相容性。未更新的程式碼將在 2015 年 6 月 1 日左右停止使用 Go 1.4 編譯。

go generate 子命令

go 命令有一個新的子命令 go generate,用於在編譯之前自動執行工具生成原始碼。例如,它可用於在 .y 檔案上執行 yacc 編譯器-編譯器以生成實現語法的 Go 原始檔,或使用 golang.org/x/tools 子儲存庫中新的 stringer 工具自動生成型別化常量的 String 方法。

有關更多資訊,請參閱設計文件

檔名稱處理的更改

構建約束(也稱為構建標籤)透過包含或排除檔案來控制編譯(請參閱文件 /go/build)。編譯也可以透過檔案本身的名稱來控制,透過使用下劃線和架構或作業系統的名稱作為字尾(在 .go.s 副檔名之前)來“標記”檔案。例如,檔案 gopher_arm.go 將僅在目標處理器是 ARM 時編譯。

在 Go 1.4 之前,名為 arm.go 的檔案也同樣被標記,但當新增新架構時,這種行為可能會破壞原始檔,導致檔案突然被標記。因此,在 1.4 中,檔案將僅在標籤(架構或作業系統名稱)前面有下劃線時才以這種方式標記。

更新:依賴舊行為的包將不再正確編譯。名稱類似於 windows.goamd64.go 的檔案應新增顯式構建標籤到源中,或重新命名為類似於 os_windows.gosupport_amd64.go 的名稱。

go 命令的其他更改

cmd/go 命令有一些值得注意的次要更改。

  • 除非使用 cgo 構建包,否則 go 命令現在拒絕編譯 C 原始檔,因為相關的 C 編譯器(6c 等)計劃在未來版本中從安裝中移除。(它們現在僅用於構建執行時的一部分。)在任何情況下,正確使用它們都很困難,因此任何現有的使用都可能不正確,因此我們已停用它們。
  • go test 子命令有一個新的標誌 -o,用於設定生成二進位制檔案的名稱,對應於其他子命令中的相同標誌。非功能性 -file 標誌已移除。
  • go test 子命令將編譯和連結包中所有 *_test.go 檔案,即使它們中沒有 Test 函式。它以前會忽略此類檔案。
  • 對於非開發安裝,go build 子命令的 -a 標誌的行為已更改。對於執行已釋出發行版的安裝,-a 標誌將不再重新構建標準庫和命令,以避免覆蓋安裝的檔案。

包源佈局的更改

在主要的 Go 源儲存庫中,包的原始碼儲存在 src/pkg 目錄中,這很有意義,但與其他儲存庫(包括 Go 子儲存庫)不同。在 Go 1.4 中,原始碼樹的 pkg 級別已消失,因此例如 fmt 包的原始碼,曾經儲存在 src/pkg/fmt 目錄中,現在位於 src/fmt 中,高一級。

更新:像 godoc 這樣發現原始碼的工具需要了解新位置。Go 團隊維護的所有工具和服務都已更新。

SWIG

由於此版本中的執行時更改,Go 1.4 需要 SWIG 3.0.3。

雜項

標準儲存庫的頂級 misc 目錄曾經包含對編輯器和 IDE 的 Go 支援:外掛、初始化指令碼等。維護這些變得耗時且需要外部幫助,因為許多列出的編輯器未被核心團隊成員使用。它還需要我們決定哪個外掛最適合給定編輯器,即使是我們不使用的編輯器。

Go 社群總體上更適合管理這些資訊。因此,在 Go 1.4 中,此支援已從儲存庫中移除。取而代之的是,在維基頁面上有一個精選的、資訊豐富的可用列表。

效能

大多數程式在 1.4 中執行速度與 1.3 相同或略快;有些會略慢。有很多變化,很難精確預測會發生什麼。

如上所述,大部分執行時已從 C 翻譯為 Go,這導致堆大小有所減少。它還稍微提高了效能,因為 Go 編譯器在最佳化方面比用於構建執行時的 C 編譯器更好,例如內聯。

垃圾回收器加速了,導致垃圾密集型程式的效能顯著提高。另一方面,新的寫屏障再次減慢了速度,通常減慢相同量,但根據它們的行為,一些程式可能會稍慢或稍快。

影響效能的庫更改記錄在下面。

標準庫的更改

新包

此版本中沒有新包。

庫的主要更改

bufio.Scanner

bufio 包中的 Scanner 型別已修復了一個錯誤,這可能需要更改自定義拆分函式。該錯誤導致無法在 EOF 處生成空令牌;修復更改了拆分函式看到的結束條件。以前,如果沒有更多資料,掃描會在 EOF 處停止。從 1.4 開始,在輸入耗盡後,拆分函式將在 EOF 處被呼叫一次,因此拆分函式可以生成最終的空令牌,正如文件已經承諾的那樣。

更新:自定義拆分函式可能需要修改以根據需要處理 EOF 處的空令牌。

syscall

syscall 包現在已凍結,除了維護核心儲存庫所需的更改。特別是,它將不再擴充套件以支援核心未使用的新或不同的系統呼叫。原因已在單獨的文件中詳細描述。

已建立一個新的子儲存庫 golang.org/x/sys,作為支援所有核心上的系統呼叫的新開發的位置。它具有更好的結構,有三個包,每個包都包含 UnixWindowsPlan 9 中一個系統呼叫的實現。這些包將得到更慷慨的維護,接受反映這些作業系統中核心介面的所有合理更改。有關更多資訊,請參閱文件和上面提到的文章。

更新:現有程式不受影響,因為 syscall 包與 1.3 版本相比基本沒有變化。未來需要 syscall 包中沒有的系統呼叫的開發應基於 golang.org/x/sys

對庫的微小更改

以下列表總結了庫的一些次要更改,主要是新增功能。有關每個更改的更多資訊,請參閱相關包文件。

  • archive/zip 包的 Writer 現在支援 Flush 方法。
  • compress/flatecompress/gzipcompress/zlib 包現在支援解壓縮器的 Reset 方法,允許它們重用緩衝區並提高效能。compress/gzip 包還具有 Multistream 方法來控制對多流檔案的支援。
  • crypto 包現在有一個 Signer 介面,由 crypto/ecdsacrypto/rsa 中的 PrivateKey 型別實現。
  • crypto/tls 包現在支援 RFC 7301 中定義的 ALPN。
  • crypto/tls 包現在透過 Config 結構體的新 CertificateForName 函式支援伺服器證書的程式設計選擇。
  • 同樣在 crypto/tls 包中,伺服器現在支援 TLS_FALLBACK_SCSV 以幫助客戶端檢測回退攻擊。(Go 客戶端根本不支援回退,因此它不受這些攻擊的影響。)
  • database/sql 包現在可以列出所有註冊的 Drivers
  • debug/dwarf 包現在支援 UnspecifiedTypes。
  • encoding/asn1 包中,具有預設值的可選元素現在僅在其具有該值時才被省略。
  • encoding/csv 包不再引用空字串,但引用資料結束標記 \.(反斜槓點)。這由 CSV 定義允許,並使其與 Postgres 更好地協作。
  • encoding/gob 包已重寫,以消除對不安全操作的使用,使其可在不允許使用 unsafe 包的環境中使用。對於典型用途,它將慢 10-30%,但增量取決於資料型別,在某些情況下,尤其涉及陣列時,它可能會更快。沒有功能上的變化。
  • encoding/xml 包的 Decoder 現在可以報告其輸入偏移量。
  • fmt 包中,指向對映的格式化已更改,以與指向結構、陣列等的格式化保持一致。例如,&map[string]int{"one": 1} 現在預設列印為 &map[one: 1],而不是十六進位制指標值。
  • image 包的 Image 實現,例如 RGBAGray,除了通用 At 方法外,還具有專門的 RGBAAtGrayAt 方法。
  • image/png 包現在有一個 Encoder 型別來控制用於編碼的壓縮級別。
  • math 包現在有一個 Nextafter32 函式。
  • net/http 包的 Request 型別有一個新的 BasicAuth 方法,該方法從使用 HTTP 基本身份驗證方案的已驗證請求返回使用者名稱和密碼。
  • net/http 包的 Transport 型別有一個新的 DialTLS 鉤子,允許自定義出站 TLS 連線的行為。
  • net/http/httputil 包的 ReverseProxy 型別有一個新欄位 ErrorLog,它提供使用者對日誌記錄的控制。
  • os 包現在透過 Symlink 函式在 Windows 作業系統上實現了符號連結。其他作業系統已經具有此功能。還有一個新的 Unsetenv 函式。
  • reflect 包的 Type 介面有一個新方法 Comparable,該方法報告型別是否實現通用比較。
  • 同樣在 reflect 包中,由於執行時介面實現的更改,Value 介面現在是三個字而不是四個字。這節省了記憶體,但沒有語義影響。
  • runtime 包現在在 Windows 上實現了單調時鐘,就像它在其他系統上一樣。
  • runtime 包的 Mallocs 計數器現在計算 Go 1.3 中遺漏的非常小的分配。這可能會由於更準確的結果而破壞使用 ReadMemStatsAllocsPerRun 的測試。
  • runtime 包中,一個數組 PauseEnd 已新增到 MemStatsGCStats 結構體中。此陣列是一個迴圈緩衝區,記錄垃圾回收暫停結束的時間。相應的暫停持續時間已記錄在 PauseNs
  • runtime/race 包現在支援 FreeBSD,這意味著 go 命令的 -race 標誌現在在 FreeBSD 上有效。
  • sync/atomic 包有一個新型別 ValueValue 為任意型別值的原子載入和儲存提供了一種高效的機制。
  • syscall 包在 Linux 上的實現中,Setuidtesting 包有一個新功能,可以更好地控制一組測試的執行。如果測試程式碼包含一個函式
    func TestMain(m *testing.M)
    
    該函式將被呼叫,而不是直接執行測試。M 結構包含訪問和執行測試的方法。
  • 同樣在 testing 包中,一個新的 Coverage 函式報告當前的測試覆蓋率分數,使單個測試能夠報告它們對整體覆蓋率的貢獻程度。
  • text/scanner 包的 Scanner 型別有一個新函式 IsIdentRune,允許在掃描時控制識別符號的定義。
  • text/template 包的布林函式 eqlt 等已通用化,允許比較有符號和無符號整數,從而簡化了它們在實踐中的使用。(以前只能比較具有相同符號性的值。)所有負值都小於所有無符號值。
  • time 包現在使用微字首的標準符號,即微符號(U+00B5 'µ'),來列印微秒持續時間。ParseDuration 仍然接受 us,但該包不再將微秒列印為 us
    更新:依賴持續時間輸出格式但未使用 ParseDuration 的程式碼需要更新。