Go 1.1 版本備註

Go 1.1 簡介

在 2012 年 3 月時發布的 Go 1 版(簡稱為 Go 1 或 Go 1.0)讓 Go 語言與函式庫進入了一段新的穩定時期。這項穩定性有助於培養全球日益龐大的 Go 使用者與系統社群。從此之後發布了許多「重點」版本,包括 1.0.1、1.0.2 和 1.0.3。這些重點版本修正了已知的錯誤,但沒有對實作進行任何非必要的更動。

這個新的版本 Go 1.1 維持了 相容性保證,不過為這項語言增加了一些重要的變更(當然是向下相容的),有一長串(也是相容的)函式庫變更,且包含了編譯器、函式庫和執行時間實作的大幅更新。重點在於效能。基準測試充其量只能算是一門不精確的科學,不過我們在許多測試程式中都看到速度有明顯,有時甚至大幅度的提升。我們確信我們的許多使用者程式只需更新 Go 安裝程式與重新編譯就能看到改善。

本文件彙整了 Go 1 和 Go 1.1 之間的變更。如果要使用 Go 1.1 執行程式碼時,幾乎不需要修改任何程式碼,儘管這項版本有一些罕見的錯誤案例浮上檯面,如果發生時需要加以處理。詳細資訊請見下方;請特別注意 64 位元整數Unicode 文字 的討論。

這項語言的變更

Go 相容性文件 保證寫入 Go 1 語言規格的程式會繼續執行,且這些承諾會持續維持。不過,為了加強規格,已釐清一些關於錯誤狀況的細節。另也有一些新的語言功能。

整數除以零

在 Go 1 中,整數除以常數零會產生執行時期的恐慌

func f(x int) int {
    return x/0
}

在 Go 1.1 中,整數除以常數零並非合法的程式,所以會產生編譯時期錯誤。

Unicode 字面中的代理

已改良字串和符文字面的定義以將代理半形從有效 Unicode 碼點的集合中移除。請參閱 Unicode 區段以取得更多資訊。

方法值

Go 1.1 現在已實作 方法值,它們是已繫結至特定接收器值的函式。例如,在提供 Writerw 的情況下,以方法值 w.Write 表示的運算式為一個永遠會寫入 w 的函式;此運算式等同於封閉 w 的函式文字

func (p []byte) (n int, err error) {
    return w.Write(p)
}

方法值與方法運算式不同,後者會由指定類型的方法產生函式;方法運算式 (*bufio.Writer).Write 等同於具備額外第一個參數、類型為 (*bufio.Writer) 的接收器的函式

func (w *bufio.Writer, p []byte) (n int, err error) {
    return w.Write(p)
}

更新: 不會影響任何現有程式碼;變更嚴格來說是向後相容的。

傳回需求

在 Go 1.1 之前,傳回值的函式需要在函式最後明確「傳回」或呼叫 panic;這是讓程式設計師明確函式意義的簡單方式。不過,在多數情況下並不需要最後的「傳回」,例如只有一個無限「for」迴圈的函式。

在 Go 1.1 中,最後「傳回」陳述式的規則較寬鬆。它引入了 結束陳述式 的概念,這是函式保證執行的最後一個陳述式。範例包括沒有條件的「for」迴圈,以及每個部分都以「傳回」作結的「if-else」陳述式。如果函式的最後一個陳述式在語法上可顯示為結束陳述式,則不需要最後的「傳回」陳述式。

請注意,這項規則純粹在語法上:它不會注意程式碼中的值,因此不需要複雜的分析。

更新: 此項變更向後相容,不過現有程式碼可能包含多餘的「傳回」陳述式和對 panic 的呼叫,可手動簡化。這樣的程式碼可由 go vet 識別。

實作和工具的變更

gccgo 狀態

GCC 版本時程未與 Go 版本時程同步,因此在 gccgo 版本中難免出現某些不一致。2013 年 3 月推出的 GCC 4.8.0 版本包含了幾乎接近 Go 1.1 版本的 gccgo。其函式庫在版本上稍有落後,但差異最大的部分在於未實作方法值。預計 2013 年 7 月左右,GCC 4.8.2 將推出提供完整 Go 1.1 實作的 gccgo

命令行旗標解析

在 gc 工具鍊中,編譯器和連結程式現在採用與 Go flag 套件相同的命令行旗標解析規則,此做法與傳統 Unix 旗標解析方式不同。這可能會影響呼叫此項工具的腳本。例如,go tool 6c -Fw -Dfoo 必須改寫為 go tool 6c -F -w -D foo

64 位元平台上 int 的大小

程式語言可讓實作方式決定 int 類型和 uint 類型是 32 位元或 64 位元。先前的 Go 實作方式會在所有系統上將 intuint 設為 32 位元。在 64 位元平台(例如 AMD64/x86-64)上,gc 和 gccgo 實作方式目前將 intuint 設為 64 位元。這樣一來,就能在 64 位元平台上配置超過 20 億個元素的切片,而這只是其中一項功能。

更新:大多數程式會不受此變更影響。由於 Go 不允許在不同的 數字類型間進行隱含轉換,因此沒有任何程式會因為此變更而無法繼續編譯。不過,包含假設 int 只有 32 位元的程式可能會出現行為上的改變。例如,這段程式碼會在 64 位元系統上印出正數,而在 32 位元系統上則印出負數

x := ^uint32(0) // x is 0xffffffff
i := int(x)     // i is -1 on 32-bit systems, 0xffffffff on 64-bit
fmt.Println(i)

目的為 32 位元符號延伸的攜帶式程式碼(在所有系統上產生 -1)將改為撰寫

i := int(int32(x))

64 位元架構上的堆疊大小

在 64 位元架構上,最大堆疊大小已大幅增加,從數 GB 增加到數十 GB。(確切的詳細資料依系統而異,而且可能會變更。)

在 32 位元架構上,堆疊大小並未變更。

更新:此變更不應對現有程式造成任何影響,除了允許這些程式使用更大的堆疊大小。

Unicode

為了讓 UTF-16 能表現大於 65535 的碼位元,Unicode 定義了代理區段,一系列只能用於組合大型值且只能用於 UTF-16 的碼位元。該代理區段中的碼位元無法作它用。在 Go 1.1 中,編譯器、函式庫和執行時間皆符合此限制:代理區段不合法作為 rune 值、編碼為 UTF-8 時或孤立編碼為 UTF-16 時。例如,在從 rune 轉換為 UTF-8 時遇到代理區段,它將被視為編碼錯誤並會產生取代 rune,utf8.RuneError,U+FFFD。

此程式,

import "fmt"

func main() {
    fmt.Printf("%+q\n", string(0xD800))
}

在 Go 1.0 中印出 "\ud800",但在 Go 1.1 中印出 "\ufffd"

代理區段 Unicode 值現在在 rune 和字串常數中不合法,因此 '\ud800'"\ud800" 之類的常數現在會遭到編譯器拒絕。編寫為明確的 UTF-8 編碼位元時,這類字串仍可建立,例如 "\xed\xa0\x80"。然而,當這類字串解碼為一系列 rune(例如範圍迴圈),它將僅產生 utf8.RuneError 值。

以 UTF-8 編碼的 Unicode 字節順序標籤 U+FEFF,現在容許為 Go 原始碼檔案的第一個字元。雖然它出現在這些位元順序自由的 UTF-8 編碼中明顯不必要,有些編輯器仍將標籤新增為識別 UTF-8 編碼檔案的一種「魔術數字」。

更新:多數程式不受代理區段變更影響。仰賴舊有行為的程式應修改以避免問題。位元順序標籤變更絕對是回溯相容的。

競爭偵測器

此工具的一項重大新增功能為競爭偵測器,一種查找因對同一個變數進行同時存取而導致的程式錯誤的方法,其中至少一項存取為寫入。此新功能已整合至 go 工具中。就目前來看,它僅於 Linux、Mac OS X 和配備 64 位元處理器的 Windows 系統上可用。若要啟用此功能,請在建立或測試程式時設定 -race 旗標(例如 go test -race)。競爭偵測器已在另一篇文章中說明。

gc 組譯器

由於 int 改為 64 位元且函式的內部表現形式已變更,函式引數在堆疊上的配置已在 gc 工具鏈中變更。以組譯語言編寫的函式至少需要進行修改以調整框架指標偏移量。

更新:go vet 指令現在會檢查以組譯語言實作的函式是否與其執行的 Go 函式原型相符。

go 指令的變更

go 指令已經獲取數項變更,其用意在於提升新進 Go 使用者的體驗。

首先,在編譯、測試或執行 Go 程式碼時,現在當找不到套件時,go 指令會提供更詳細的錯誤訊息,其中會包含已搜尋路徑清單。

$ go build foo/quxx
can't load package: package foo/quxx: cannot find package "foo/quxx" in any of:
        /home/you/go/src/pkg/foo/quxx (from $GOROOT)
        /home/you/src/foo/quxx (from $GOPATH)

其次,下載套件原始程式碼時,go get 指令不再允許 $GOROOT 作為預設目的地。若要使用 go get 指令,現在需要 有效的 $GOPATH

$ GOPATH= go get code.google.com/p/foo/quxx
package code.google.com/p/foo/quxx: cannot download, $GOPATH not set. For more details see: go help gopath

最後,由於先前的變更,go get 指令也會在 $GOPATH$GOROOT 設定為相同值時失敗。

$ GOPATH=$GOROOT go get code.google.com/p/foo/quxx
warning: GOPATH set to GOROOT (/home/you/go) has no effect
package code.google.com/p/foo/quxx: cannot download, $GOPATH must not be set to $GOROOT. For more details see: go help gopath

對 go test 指令的變更

在啟用剖析時執行 go test 指令時,不再會刪除二進位檔,如此一來可以更容易分析剖析結果。實作會自動設定 -c 旗標,因此執行後,

$ go test -cpuprofile cpuprof.out mypackage

檔案 mypackage.test 會留在執行 go test 的目錄中。

go test 指令現在可以產生剖析資訊,其中會指出 goroutine 遭封鎖的位置,亦即它們容易暫停等待某個事件(例如通道通訊)的位置。這些資訊會以啟用 go test-blockprofile 選項的封鎖剖析形式呈現。執行 go help test 以取得更多資訊。

對 go fix 指令的變更

fix 指令(通常以 go fix 執行)不再套用修正程式碼以使用 Go 1 API,以將 Go 1 之前的程式碼更新為 Go 1。若要將 Go 1 之前的程式碼更新為 Go 1.1,請先使用 Go 1.0 工具鏈將程式碼轉換為 Go 1.0。

建置約束

已將「go1.1」標記新增至預設 建置約束清單。如此一來,各套件便能利用 Go 1.1 中的新功能,同時仍與早期版本的 Go 相容。

若要僅使用 Go 1.1 和更新版本建置檔案,請新增此建置約束

// +build go1.1

若要僅使用 Go 1.0.x 建置檔案,請使用相反約束

// +build !go1.1

額外平台

Go 1.1 工具鏈新增對 freebsd/armnetbsd/386netbsd/amd64netbsd/armopenbsd/386openbsd/amd64 平台的試驗性支援。

對於 freebsd/armnetbsd/arm,需要 ARMv6 或更新版本的處理器。

Go 1.1 在 linux/arm 上新增對 cgo 的試驗性支援。

交叉編譯

在進行交叉編譯時,go 工具會預設停用 cgo 支援。

若要明確啟用 cgo,請設定 CGO_ENABLED=1

效能

使用 Go 1.1 gc 工具套組編譯的程式碼效能對於大多數 Go 程式應有明顯的改善。相對於 Go 1.0 的一般改良幅度約為 30%-40%,有時會更多,但偶爾也會更少,甚至沒有改善。透過工具和函式庫中眾多的微小效能調整,不過若要逐一列出,則篇幅將過於龐大,然而以下主要變更值得注意

標準函式庫的變更

bufio.Scanner

若要使用 bufio 套件中的各種常式,來掃描文字輸入,例如 ReadBytesReadString,尤其是 ReadLine,若用於簡單的目的,則無用地複雜。在 Go 1.1 中,已新增一個新的類型 Scanner,這使得執行簡單的作業更為容易,例如將輸入讀取為一連串的行程或以空白分隔的字詞。這簡化了問題解決,例如在碰到像異常長的行等有問題的輸入時,就中斷掃描,並使用一個簡單的預設值:以行程為導向的輸入,而且每一行程的終結符號都已移除。以下的程式碼可逐行複製輸入

scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
    fmt.Println(scanner.Text()) // Println will add back the final '\n'
}
if err := scanner.Err(); err != nil {
    fmt.Fprintln(os.Stderr, "reading standard input:", err)
}

掃描行為可透過一個函數進行調整,來控制輸入的細分(請參閱 SplitFunc 文件),但對於艱難的問題或需要繼續執行錯誤後的作業,可能仍需要使用較早的介面。

net

net 套件中,特定於通訊協定的解析器之前會忽略傳入的網路名稱。儘管文件已明確說明,ResolveTCPAddr 唯一有效的網路是 "tcp""tcp4""tcp6",但 Go 1.0 的實作卻會靜默地接受任何字串。Go 1.1 的實作會傳回錯誤,如果網路並非這些字串之一的話。其他特定於通訊協定的解析器 ResolveIPAddrResolveUDPAddrResolveUnixAddr 也是如此。

先前的 ListenUnixgram 實作會傳回 UDPConn 作為連線端點的表示。Go 1.1 的實作改為傳回 UnixConn,以透過 ReadFromWriteTo 方法進行讀寫。

資料結構 IPAddrTCPAddrUDPAddr 新增一個名為 Zone 的字串欄位。使用未標記複合字面值(例如 net.TCPAddr{ip, port})取代標記字面值(net.TCPAddr{IP: ip, Port: port})的程式碼會因為新的欄位而中斷。Go 1 相容性規則允許此變更:用戶端程式碼必須使用標記字面值才能避免此類中斷。

更新:若要修正由新的結構欄位所造成的程式中斷,go fix 會重寫程式碼,以針對這些型別新增標記。更明確地說,go vet 會辨識應該修改為使用欄位標記的複合字面值。

reflect

reflect 套件有數項重大的新增功能。

現在可以使用 reflect 套件執行「select」陳述式;查看 SelectSelectCase 的說明,以取得詳細資料。

新的 Value.Convert 方法(或 Type.ConvertibleTo)提供執行 Go 轉換或型別斷言作業的執行方式(或測試其可能性)於 Value(或測試其可能性)。

新的 MakeFunc 函式建立包裝函式以簡化使用現有的 Values 呼叫函式,對參數進行標準的 Go 轉換,例如將實際的 int 傳遞給正式的 interface{}

最後,新的 ChanOfMapOfSliceOf 函式會使用現有的型別建立新的 Types,例如僅針對 T 建立型別 []T

time

在 FreeBSD、Linux、NetBSD、OS X 和 OpenBSD 上,先前版本的 time 套件會傳回具微秒精度的時間。這些系統中的 Go 1.1 實作現在會傳回具奈秒精度的時間。以微秒精度寫入外部格式並讀回的程式,預期會復原原始值,會受到精度的影響。現在有兩種 Time 的新方法,RoundTruncate,可用於在傳遞給外部儲存之前移除時間的精度。

新方法 YearDay 傳回時間值指定的年分之一索引整數日號。

Timer 類型有一個新的方法 Reset,會修改定時器在指定時間後過期。

最後,新的函式 ParseInLocation 類似於現有的 Parse,但在位置 (時區) 的脈絡下解析時間,會忽略已解析字串中的時區資訊。此函式可解決時間 API 中的常見混淆點。

更新:需要使用低精確度外部分解式來讀取及寫入時間的程式碼,應修改成使用新的方法。

已將舊的子樹 exp 和 old 移到 go.exp 和 go.text 子儲存庫

為了讓二進位發行版 (如有需要) 更容易存取,未包含在二進位發行版中的來源子樹 expold 已移至位於 code.google.com/p/go.exp 的新 go.exp 子儲存庫。例如,若要存取 ssa 套件,請執行

$ go get code.google.com/p/go.exp/ssa

然後在 Go 來源中,

import "code.google.com/p/go.exp/ssa"

舊套件 exp/norm 也已移至新的儲存庫 go.text,Unicode API 和其他與文字相關的套件將會在那裡開發。

新的套件

有三個新的套件。

對程式庫的輕微變更

下列清單摘要了對程式庫所做的許多輕微變更,主要是新增。請參閱相關套件文件,深入瞭解每個變更。