Go 1.1 發行說明

Go 1.1 簡介

Go 1 版本(簡稱 Go 1 或 Go 1.0)於 2012 年 3 月釋出,標誌著 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 中,整數除以常量零會產生執行時 panic。

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

在 Go 1.1 中,整數除以常量零不再是合法程式,因此它是一個編譯時錯誤。

Unicode 字面量中的代理項

字串和 rune 字面量的定義已經完善,排除了代理半區(surrogate halves)作為有效 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 之前,返回值的函式需要在函式末尾顯式地使用“return”或呼叫 panic;這是一種簡單的方式,讓程式設計師明確函式的含義。但有很多情況下,最後的“return”顯然是不必要的,例如只包含一個無限“for”迴圈的函式。

在 Go 1.1 中,關於最終“return”語句的規則更為寬鬆。它引入了終止語句的概念,這是一種保證是函式執行的最後一條語句。示例包括沒有條件的“for”迴圈和每半部分以“return”結尾的“if-else”語句。如果函式的最後一條語句可以在語法上被證明是終止語句,則不需要最終的“return”語句。

請注意,該規則是純粹的語法規則:它不關注程式碼中的值,因此不需要複雜的分析。

更新:此更改是向後相容的,但包含多餘“return”語句和 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 位。gc 和 gccgo 實現現在都在 AMD64/x86-64 等 64 位平臺上將 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 位架構上,最大堆大小已大幅增加,從幾千兆位元組增加到幾十千兆位元組。(具體細節取決於系統,並且可能會發生變化。)

在 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 序列(例如在 range 迴圈中)時,它只會產生 utf8.RuneError 值。

現在允許將 Unicode 位元組順序標記 U+FEFF(以 UTF-8 編碼)作為 Go 原始檔的第一個字元。儘管它在無位元組順序的 UTF-8 編碼中的出現顯然是不必要的,但一些編輯器會新增該標記作為一種“魔術數字”,用於識別 UTF-8 編碼的檔案。

更新:大多數程式不會受到代理更改的影響。依賴舊行為的程式應進行修改以避免此問題。位元組順序標記的更改嚴格向後相容。

競態檢測器

工具的一個主要新增功能是競態檢測器,這是一種查詢程式中由於同時訪問同一變數(其中至少一個訪問是寫入)而導致的錯誤的方法。這個新功能內建在 go 工具中。目前,它僅適用於 Linux、Mac OS X 和帶有 64 位 x86 處理器的 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

最後,由於上述更改,當 $GOPATH$GOROOT 設定為相同值時,go get 命令也將失敗。

$ 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 之前的程式碼以使用 Go 1 API。要將 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%,有時甚至更多,但偶爾也會更少甚至沒有。工具和庫中效能驅動的微小調整太多,無法在此一一列出,但以下主要更改值得注意:

  • gc 編譯器在許多情況下生成更好的程式碼,最明顯的是在 32 位 Intel 架構上的浮點數處理。
  • gc 編譯器進行更多內聯,包括執行時中的某些操作,例如append和介面轉換。
  • Go maps 採用了新的實現,顯著減少了記憶體佔用和 CPU 時間。
  • 垃圾回收器已變得更加並行,這可以減少在多個 CPU 上執行的程式的延遲。
  • 垃圾收集器也更精確,這會消耗少量的 CPU 時間,但可以顯著減少堆的大小,尤其是在 32 位架構上。
  • 由於執行時和網路庫的緊密耦合,網路操作所需的上下文切換更少。

標準庫的更改

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)提供了在 Value 上執行 Go 轉換或型別斷言操作(或測試其可能性)的功能。

新的函式 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,但在位置(時區)上下文中解析時間,忽略解析字串中的時區資訊。此函式解決了 time 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 和其他與文字相關的包。

新包

有三個新包。

  • go/format 包提供了一種方便的方式,讓程式訪問 go fmt 命令的格式化功能。它有兩個函式,Node 用於格式化 Go 解析器 Node,以及 Source 用於將任意 Go 原始碼重新格式化為 go fmt 命令提供的標準格式。
  • net/http/cookiejar 包提供了管理 HTTP cookie 的基本功能。
  • runtime/race 包提供了用於資料競爭檢測的低階工具。它是競態檢測器內部的,不匯出任何使用者可見的功能。

對庫的微小更改

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

  • bytes 包有兩個新函式,TrimPrefixTrimSuffix,其屬性不言而喻。此外,Buffer 型別有一個新方法 Grow,提供對緩衝區內記憶體分配的一些控制。最後,Reader 型別現在有一個 WriteTo 方法,因此它實現了 io.WriterTo 介面。
  • compress/gzip 包為其 Writer 型別提供了一個新的 Flush 方法,該方法重新整理其底層的 flate.Writer
  • crypto/hmac 包有一個新函式 Equal,用於比較兩個 MAC。
  • crypto/x509 包現在支援 PEM 塊(例如參見 DecryptPEMBlock),以及一個新函式 ParseECPrivateKey 用於解析橢圓曲線私鑰。
  • database/sql 包為其 DB 型別添加了一個新的 Ping 方法,用於測試連線的健康狀況。
  • database/sql/driver 包有一個新的 Queryer 介面,Conn 可以實現該介面以提高效能。
  • encoding/json 包的 Decoder 有一個新方法 Buffered,用於提供對其緩衝區中剩餘資料的訪問,以及一個新方法 UseNumber,用於將值解組到新型別 Number(一個字串),而不是 float64。
  • encoding/xml 包有一個新函式 EscapeText,用於寫入轉義的 XML 輸出,以及 Encoder 上的一個方法 Indent,用於指定縮排輸出。
  • go/ast 包中,新的型別 CommentMap 及相關方法使得在 Go 程式中提取和處理註釋變得更加容易。
  • go/doc 包中,解析器現在更好地跟蹤程式碼中諸如 TODO(joe) 之類的風格化註解,godoc 命令可以根據 -notes 標誌的值過濾或呈現這些資訊。
  • 已刪除 html/template 包中未文件化且僅部分實現的“noescape”功能;依賴它的程式將中斷。
  • image/jpeg 包現在可以讀取漸進式 JPEG 檔案,並處理更多子取樣配置。
  • io 包現在匯出了 io.ByteWriter 介面,以捕獲一次寫入一個位元組的常見功能。它還匯出了一個新的錯誤 ErrNoProgress,用於指示 Read 實現正在迴圈而未提供資料。
  • log/syslog 包現在為特定於作業系統的日誌功能提供了更好的支援。
  • math/big 包的 Int 型別現在具有 MarshalJSONUnmarshalJSON 方法,用於與 JSON 表示形式相互轉換。此外,Int 現在可以使用 Uint64SetUint64 直接與 uint64 相互轉換,而 Rat 可以使用 Float64SetFloat64float64 進行相同的操作。
  • mime/multipart 包為其 Writer 添加了一個新方法 SetBoundary,用於定義用於打包輸出的邊界分隔符。Reader 現在還透明地解碼任何 quoted-printable 部分,並在這樣做時刪除 Content-Transfer-Encoding 標頭。
  • net 包的 ListenUnixgram 函式的返回型別已更改:它現在返回 UnixConn 而不是 UDPConn,這顯然是 Go 1.0 中的一個錯誤。由於此 API 更改修復了一個錯誤,因此 Go 1 相容性規則允許此更改。
  • net 包包含一個新型別 Dialer,用於向 Dial 提供選項。
  • net 包增加了對帶有區域限定符的鏈路本地 IPv6 地址(例如 fe80::1%lo0)的支援。地址結構 IPAddrUDPAddrTCPAddr 在一個新欄位中記錄區域,並且期望這些地址字串形式的函式(例如 DialResolveIPAddrResolveUDPAddrResolveTCPAddr)現在接受帶區域限定符的形式。
  • net 包在其解析函式套件中添加了 LookupNSLookupNS 返回主機名的 NS 記錄
  • net 包為 IPConn (ReadMsgIPWriteMsgIP) 和 UDPConn (ReadMsgUDPWriteMsgUDP) 添加了協議特定的包讀寫方法。這些是 PacketConnReadFromWriteTo 方法的專用版本,它們提供對與資料包相關的帶外資料的訪問。
  • net 包為 UnixConn 添加了方法,以允許關閉連線的一半(CloseReadCloseWrite),與 TCPConn 的現有方法匹配。
  • net/http 包包含了幾個新增功能。ParseTime 解析時間字串,嘗試幾種常見的 HTTP 時間格式。RequestPostFormValue 方法類似於 FormValue,但忽略 URL 引數。CloseNotifier 介面提供了一種機制,供伺服器處理程式在客戶端斷開連線時發現。ServeMux 型別現在有一個 Handler 方法,用於訪問路徑的 Handler 而不執行它。Transport 現在可以使用 CancelRequest 取消正在進行的請求。最後,當 Response.Body 在完全消耗之前關閉時,Transport 現在更積極地關閉 TCP 連線。
  • net/mail 包有兩個新函式,ParseAddressParseAddressList,用於將 RFC 5322 格式的郵件地址解析為 Address 結構。
  • net/smtp 包的 Client 型別有一個新方法 Hello,用於向伺服器傳送 HELOEHLO 訊息。
  • net/textproto 包有兩個新函式,TrimBytesTrimString,它們執行 ASCII 字元集的前導和尾隨空格修剪。
  • 新的方法 os.FileMode.IsRegular 使得判斷檔案是否為普通檔案變得容易。
  • os/signal 包有一個新函式 Stop,它停止該包向通道傳送任何後續訊號。
  • regexp 包現在透過 Regexp.Longest 方法支援 Unix 最初的最左最長匹配,而 Regexp.Split 則根據正則表示式定義的分隔符將字串分割成多個部分。
  • runtime/debug 包有三個關於記憶體使用的新函式。FreeOSMemory 函式觸發垃圾回收器執行,然後嘗試將未使用的記憶體返回給作業系統;ReadGCStats 函式檢索有關回收器的統計資訊;SetGCPercent 提供了一種以程式設計方式控制回收器執行頻率(包括完全停用它)的方法。
  • sort 包有一個新函式 Reverse。將對 sort.Sort 的呼叫引數用 Reverse 呼叫包裝,會導致排序順序反轉。
  • strings 包有兩個新函式,TrimPrefixTrimSuffix,其屬性不言而喻,以及新方法 Reader.WriteTo,因此 Reader 型別現在實現了 io.WriterTo 介面。
  • syscall 包的 Fchflags 函式在各種 BSD 系統(包括 Darwin)上的簽名已更改。它現在將 int 作為第一個引數而不是字串。由於此 API 更改修復了一個錯誤,因此 Go 1 相容性規則允許此更改。
  • syscall 包也收到了許多更新,以使其更全面地包含每個受支援作業系統的常量和系統呼叫。
  • testing 包現在使用新的 AllocsPerRun 函式自動化生成測試和基準測試中的分配統計資訊。而 testing.B 上的 ReportAllocs 方法將啟用列印呼叫基準測試的記憶體分配統計資訊。它還引入了 BenchmarkResultAllocsPerOp 方法。還有一個新的 Verbose 函式用於測試 -v 命令列標誌的狀態,以及 testing.Btesting.T 的新 Skip 方法,用於簡化跳過不合適的測試。
  • text/templatehtml/template 包中,模板現在可以使用括號來分組管道元素,從而簡化複雜管道的構建。此外,作為新解析器的一部分,Node 介面獲得了兩個新方法,以提供更好的錯誤報告。儘管這違反了 Go 1 相容性規則,但現有程式碼不應受到影響,因為此介面明確僅供 text/templatehtml/template 包使用,並且有保障措施來確保這一點。
  • unicode 包的實現已更新至 Unicode 6.2.0 版本。
  • unicode/utf8 包中,新函式 ValidRune 報告 rune 是否是有效的 Unicode 碼點。為了有效,rune 必須在範圍內且不是代理半區。