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 分析樹的工具可能需要修改,才能接受這種新形式,因為 RangeStmt
的 Key
欄位現在可以是 nil
。
**T** 上的方法呼叫
在這些宣告之下,
type T int
func (T) M() {}
var x **T
gc
和 gccgo
都接受方法呼叫
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/5a
、cmd/6a
和 cmd/8a
組譯器所接受的語言已經過幾次更動,主要是為了讓更容易向執行時期環境傳送類型資訊。
首先,定義 TEXT
指令旗標的 textflag.h
檔案已經從連結器原始碼目錄複製到標準位置,因此它可以使用簡單的指令納入
#include "textflag.h"
最重要的變更在於組譯器原始碼如何定義必要的類型資訊。對於大多數程式來說,將資料定義(DATA
和 GLOBL
指令)從組譯移到 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
指令中有很多值得注意的次要變更。
- 除非使用
cgo
編譯套件,否則 `go` 指令現在拒絕編譯 C 原始碼檔案,因為相關的 C 編譯器 (6c
等) 應在未來的版本中從安裝中移除。(它們目前僅用於編譯執行時期的一部分。) 無論如何,正確地使用它們很困難,因此任何現有的用法很可能都不正確,所以我們已停用它們。 - 在
go
test
子指令中,有一個新的標籤 `-o`,用於設定結果二進位檔的名稱,與其他子指令中的標籤相同。已移除無法運作的 `-file` 標籤。 - 在
go
test
子指令中,即使套件中沒有 `Test` 函數,它也會編譯並連結所有 `*_test.go` 檔案。它之前會忽略這些檔案。 - 在
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 中,已將此支援從儲存庫中移除。我們另外在 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,做為所有核心開發支援系統呼叫的位置。它結構較為完善,包含三個套件,每個套件都將系統呼叫的實作分別維護於 Unix、Windows 和 Plan 9 之中。這些套件將更能配合核心介面,接受所有適當的變更,以反映這些作業系統中的核心介面。請參閱文件和上述文章以取得更多資訊。
更新: 現有程式不會受到影響,因為 syscall
套件與 1.3 版相比幾乎沒有變動。未來需要 syscall
套件中所沒有系統呼叫的開發,應改為建立於 golang.org/x/sys
之上。
函式庫的次要變更
下列清單總結函式庫的多項次要變更,大部分為新增功能。請參閱相關套件文件以取得關於個別變更的詳細資訊。
archive/zip
套件的Writer
現在支援Flush
方法。compress/flate
、compress/gzip
和compress/zlib
套件現在支援針對解壓縮器的Reset
方法,讓它們可以重複使用緩衝區,進而提升效能。compress/gzip
套件也有一個Multistream
方法,用於控制對多串流檔案的支援。crypto
套件現在具有一個Signer
介面,由crypto/ecdsa
和crypto/rsa
中的PrivateKey
類型實作。crypto/tls
套件現在支援 RFC 7301 中定義的 ALPN。crypto/tls
套件現在支援透過Config
結構中新的CertificateForName
函數,以程式化方式選取伺服器憑證。- 一樣位於 crypto/tls 套件中,伺服器現在支援 TLS_FALLBACK_SCSV,協助用戶端偵測遞降攻擊。(Go 用戶端完全不支援遞降,因此不會受到這些攻擊的影響。)
database/sql
套件現在可以列出所有已註冊的Drivers
。debug/dwarf
套件現在支援UnspecifiedType
s。- 在
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
實作,例如RGBA
和Gray
,除了通用At
方法外,已有專門的RGBAAt
和GrayAt
方法。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 中遺漏的極小配置。這可能會因較為精確的答案,而使使用ReadMemStats
或AllocsPerRun
的測試中斷。- 在
runtime
套件中,陣列PauseEnd
已加入MemStats
和GCStats
結構體。此陣列是垃圾回收暫停結束時間的循環緩衝區。對應的暫停時間已記錄在PauseNs
runtime/race
套件現在支援 FreeBSD,這表示go
命令的-race
旗標現在可以在 FreeBSD 中執行。sync/atomic
套件具有新類型Value
。Value
提供一種有效率的機制,用於指定類型值的原子載入和儲存。- 在 Linux 上實施之
syscall
套件中,Setuid
和Setgid
已停用,原因是這些系統呼叫在呼叫執行緒而非整個程序作用,這與其他平台不同,也不是預期結果。 testing
套件具有新的功能,用來提供更多對執行一組合測試的控管。如果測試程式碼包含函數func TestMain(m *
函式將會在直接跑測試之前被呼叫。testing.M
)M
struct 包含存取和跑測試的方法。- 同樣是
testing
套件,新的Coverage
函式回報目前的測試涵蓋率,讓個別的測試能夠回報它們對於整體涵蓋率的貢獻。 text/scanner
套件的Scanner
型別有一個新的函式IsIdentRune
,讓人在掃描時可以控制識別字元的定義。text/template
套件的布林函式eq
、lt
等,已經被化為一般化,允許有號和無號整數的比較,簡化實際用途。(以前只能比較具有相同有號特性的值。)所有負值都小於所有正值。time
套件現在使用微符號 (U+00B5 ‘µ’),也就是標準的微前綴符號,來列印微秒的持續時間。ParseDuration
仍然接受us
,但是這個套件不再將微秒印為us
。
更新:仰賴持續時間輸出格式,但沒有使用ParseDuration
的程式碼,需要進行更新。