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 版後都將繼續編譯和執行,不會有變更。

語言變更

範圍迴圈

直到 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 中已不再被允許,這是一個不向下相容的變更,不過受到影響的程式非常少。

更新:依賴於舊的、有錯誤行為的程式碼將無法再編譯,但透過新增明確的解除參考可以輕鬆修正。

支援作業系統及架構變更

Android

Go 1.4 可以為執行 Android 作業系統的 ARM 處理器建立二進位檔。它也可以建立一個 .so 函式庫,可載入使用 mobile 子儲存庫中支援套件的 Android 應用程式。關於這個實驗性移植的計畫簡述可在此處 取得

ARM 上的 NaCl

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

AMD64 上的 Plan 9

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

相容性準則變更

unsafe 套件允許我們利用實作或資料的機器表示的內部詳細資料來破解 Go 的型別系統。從未明確指定使用 unsafe 對相容性的意義為何,正如 Go 相容性準則 中所說明的。答案當然是:我們無法保證執行不安全行為的程式的相容性。

我們已在版本隨附的文件中釐清了此情況。 Go 相容性指南unsafe 套件的文件現在明確表示保證不安全程式碼保持相容性。

更新中:技術上並無變更;這只是對文件說明的釐清。

實作和工具的變更

執行時期的變更

在 Go 1.4 之前,執行時期(垃圾收集器、並行支援、介面管理、映射、切片、字串 …)大部分以 C 編寫,並搭配一些組合語言支援。在 1.4 中,許多程式碼已轉換成 Go,讓垃圾收集器能掃描執行時期程式堆疊並取得哪些變數處於作用中的精確資訊。這項變更規模龐大,但應不會對程式產生語意上的影響。

這項重寫作業讓 1.4 中的垃圾收集器能進行完全精確的分析,這表示垃圾收集器知道程式中所有作用中指標的位置。這表示堆疊會更小,因為不會有誤認非指標的項目保持存活狀態。其他相關變更也減少了堆疊大小,整體而言比之前的版本減少了 10%-30%。

後果是堆疊不再區隔,消除了「熱區分段」的問題。當堆疊上限已達時,會配置一個較大的新堆疊,所有 goroutine 的作用中框架都會複製到該堆疊,任何指標進到堆疊中都將更新。在某些案例中,效能可顯著改善且總是更可預測。詳細資訊可見 設計文件

使用連續堆疊表示堆疊從較小的狀態開始也不會觸發效能問題,所以 goroutine 的堆疊在 1.4 中的預設起始大小已從 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 為根目錄的目錄樹狀結構中的程式可以匯入套件 .../a/b/c/internal/d/e/f.../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.go` 或 `amd64.go` 的檔案應在原始碼中加入明確的 build 標籤,或重新命名為類似於 `os_windows.go` 或 `support_amd64.go` 的名稱。

Go 指令的其他變更

cmd/go 指令中有很多值得注意的次要變更。

套件原始碼配置的變更

在 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 中,已將此支援從儲存庫中移除。我們另外在 Wiki 頁面 上建立了一個精選的詳細可得清單。

效能

大多數程式在 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 之上。

函式庫的次要變更

下列清單總結函式庫的多項次要變更,大部分為新增功能。請參閱相關套件文件以取得關於個別變更的詳細資訊。