Go 1.1 介紹

發表於2013-03-30

來源:mikespook

Go 1.1 介紹

Go 第一版(簡稱 Go 1 或 Go 1.0)釋出於 2012 年三月,這個版本提供了穩定的 Go 語言和庫。其穩定性讓全世界 Go 使用者社群和相關係統茁壯成長。從那時起,就釋出了若干個“關鍵點”——1.0.1、1.0.2 和 1.0.3。這些點的釋出修復了若干已知 bug,但是對於實現本身並沒有進行修改。

這個新的釋出版,Go 1.1,在保持相容性的前提下新增了若干重要的(當然,向後相容)語言變化,而庫變化的清單也很長(也向後相容),還有在編譯器、庫和執行時環境實現的主要工作。焦點是效能。測試並不是十分精確,但是對於許多測試程式來說都有著重要的、有時是戲劇性的效能改善。我們相信,通過升級 Go 的安裝包,並且重新編譯,許多使用者的程式也能讓人體會到這一改進。

這一文件彙總了從 Go 1 到 Go 1.1 的變化。雖然這個釋出版有一些極為罕見的錯誤情況,而當這些情況在發生時必須被處理。在 Go 1.1 下執行,幾乎不需要修改任何程式碼。下面描述了細節;參閱 64 位整數和 Unicode 文字的特別說明。

語言的變化

Go 的相容性文件保證了用 Go 1 語言規範編寫的程式仍然可以使用,並且可以會繼續被維護。儘管有一些細節的錯誤情況已經被指出,但是規範本身的完善還是相當有趣的。同時還增加了一些語言的新特性。

整數除以零

在 Go 1 中,整數被一個常量零整除會產生一個執行時 panic:

在 Go 1.1 中,一個整數被一個常量零整除不是合法的程式,因此這會是一個編譯時錯誤。

代用的 Unicode 文字

細化了 string 和 rune 文字的定義,以便將代用部分排除在合法的 Unicode 編碼值以外。參閱 Unicode 部分了解更多資訊。

方法值

現在 Go 1.1 實現了方法值,也就是將函式繫結在特定的接收者的值上。例如,有一個Writer 的值 w,表示式 w.Write,是一個方法值,作為用於向 w 寫入的函式;這與函式文法中對 w 進行閉包是等價的:

方法值與方法表示式是不同的,方法表示式從方法中利用指定的型別構造了一個函式;方法表示式 (*bufio.Writer).Write 與第一個引數型別指定為 (*bufio.Writer) 的函式等價:

更新:已有程式碼不受影響;這個變動是嚴格的向後相容。

Return requirements

在 Go 1.1 之前,一個函式返回一個值必須明確的在函式結束時“return”或呼叫 panic;這是一個讓程式明確函式的概念的簡單的途徑。但是,顯然有許多情況最後的“return”沒有必要,例如, 一個只有死迴圈“for”的函式。

在 Go 1.1 中,關於最後的“return”語句的規則更加寬鬆。它引入了一個終止語句的概念,它保證了在函式中這個語句總是最後被執行。例如在沒有條件的“for”迴圈中,也沒有“if-else”語句用來在中間通過“return”結束。那麼函式的最後一個語句可以在語法上被認為是終止語句,而不需要最後的“return”語句。

注意這個規則純粹是語法上的:它並不關注程式碼中的值,因此也沒有複雜的分析。

更新:這個變動是向後相容的,不過有著多餘“return”語句或呼叫 panic 的已有程式碼可能需要手工處理一下。這些程式碼可用 go vet 來標識。

實現和工具的變更

命令列引數解析

在 gc 工具鏈中,編譯器和連結器現在使用與 Go 的 flag 包一致的命令列引數解析規則,而與傳統的 Unix 引數解析背道而馳。這可能會對直接呼叫工具的指令碼產生影響。例如,go tool 6c -Fw -Dfoo 現在必須寫為 go tool 6c -F -w -D foo

在 64 位平臺上的整數大小

該語言允許根據具體實現選擇 int 型別和 uint 型別是 32 或 64 位的。之前 Go 的實現是在所有系統上都讓 int 和 uint 是 32 位的。現在 gc 和 gccgo 的實現都讓 int 和 uint 在如 AMD64/x86-64 這樣的平臺上是 64 位的。拋開別的不說,單這個就使得 slice 在 64 位平臺上可以分配超過 20 億的元素。

更新:大多數程式不會受到這個的影響。 由於 Go 不允許不同數字型別之間的隱式轉換,不會有程式在編譯時報錯。然而,那些隱式假設 int 是 32 位的程式,在行為上可能發生變化。例如,這個程式在 64 位系統中會列印正數,在 32 位系統中會列印複數:

要保留 32 位的符號(在所有系統上都是 -1)應該用下面的具有可移植性的程式碼代替:

Unicode

為了能夠表達 UTF-16 中超過 65535 的編碼值,Unicode 定義了代用部分,一個僅用於組裝更大的值的編碼值範圍,且僅在 UTF-16 中。在這個代用範圍內的編碼值如果用於其他任何情況都是非法的,如作為 UTF-8 編碼,或作為獨立的 UTF-16 編碼。例如在遇到將一個 rune 轉換成 UTF-8 時,它被當作一個編碼錯誤對待,併產生一個替代的 rune,utf8.RuneError, U+FFFD。

這個程式,

在 Go 1.0 中列印“\ud800”,但在 Go 1.1 中列印“\ufffd”。

半個代用 Unicode 值現在在 rune 和 string 常量中都是非法的,因此如“\ud800”和“\ud800”的常量現在會被編譯器拒絕。當編寫為獨立的 UTF-8 編碼的位元組時,這樣字串還是可以被建立的,例如“\xed\xa0\x80”。然而,當這個字串被作為一個 rune 序列解碼時,比如在 range 迴圈中,它只會生成 utf8.RuneError 值。

Unicode 位元組順序讓 U+FFFE 和 U+FEFF 在 UTF-8 編碼下可以作為 Go 原始碼的第一個字元出現。雖然在位元組順序未設定的 UTF-8 編碼中,它是完全不必要的,不過有些編輯器會將其作為“魔法數值”新增進去,用來標識一個 UTF-8 編碼的檔案。

更新:大多數程式不會受到代用變更的影響。基於舊的行為的程式應當通過修改來避免問題。位元組順序標識的變更是嚴格向後相容的。

gc 彙編

基於如 int 到 64 位和其他一些變化,在 gc 工具鏈的函式引數的棧佈局發生了變化。使用匯編編寫的函式至少需要一個 frame 指標偏移量。

更新:現在 go vet 命令可以檢查用匯編實現的函式是否匹配 Go 的函式原型。

go 命令的變化

為了讓新的 Go 使用者獲得更好的體驗,go 命令做了若干改動。

首先,當編譯、測試或執行 Go 程式碼的時候,go 命令會給出更多的錯誤資訊細節,當一個包無法被定位時,會列出搜尋的路徑清單。

其次,go get 命令不再允許下載包原始碼時,將 $GOROOT 作為預設的目的路徑。要使用 go get 命令,必須有一個合法的 $GOPATH。

最後,作為前面變化的結果,go get 命令會在 $GOPATH 和 $GOROOT 設定為相同值的時候報錯。

go test 命令的變化

go test 命令在進行效能測試時不再刪除二進位制內容,以便更容易的分析效能測試。實現上是在執行的時候設定了 -c 引數。

在 go test 執行之後,mypackage.test 將會留在目錄中。

go test 命令現在可以報告 goroutine 在哪裡阻塞的測試資訊,也就是說,它們在哪一直等著某個事件,例如一個 channel 通訊之類的。當用 -blockprofile 開啟 go test 的阻塞測試時,就會展示這些資訊。 執行 go help test 瞭解更多資訊。

go fix 命令的變化

fix 命令通常以 go fix 執行,不再提供從 Go1 之前的版本升級到 Go 1 API 的功能。如果要升級 Go 1 之前的程式碼到 Go 1.1,首先應當使用 Go 1.0 的工具鏈,將程式碼轉化到 Go 1.0。

效能

用 Go 1.1 的 gc 工具集編譯出來的程式碼的效能對於大多數 Go 程式來說應當有顯著的提升。一般來說,與 Go 1.0 相比,大約有 30%-40% 的提升,有時甚至更高,當然也會比這個值低,甚至沒有提升。對於工具和庫來說,有太多的小的效能驅使的改動,以至於無法將它們全部列在這裡。不過下面的主要變更還是有必要留意的:

  • gc 編譯器在大多數情況下都會生成較好的程式碼,尤其是在 32 位 Intel 架構下的浮點值。
  • gc 編譯器做了更多的內連,包括在執行時的一些操作,例如 append 和介面轉換。
  • Go 的 map 有了新的實現,在記憶體複製和 CPU 時間上有了重大的改進。
  • 垃圾回收實現了更多的並行,這可以降低在多 CPU 環境下執行的程式的延遲。
  • 垃圾回收同時也更加精準,這增加了一點 CPU 時間開銷,但是極大的降低了堆的大小,尤其是在 32 位的架構下。
  • 通過緊密結合執行時和網路庫,在網路操作時需要的上下文切換會更少。

標準庫的變化

bufio.Scanner

在 bufio 包中有多種方式獲取文字輸入,ReadBytesReadString 和特別的 ReadLine,對於簡單的目的這些都有些過於複雜了。在 Go 1.1 中,新增了一個新型別,Scanner,以便更容易的處理如按行讀取輸入序列或空格分隔的詞等,這類簡單的任務。它終結了如輸入一個很長的有問題的行這樣的輸入錯誤,並且提供了簡單的預設行為:基於行的輸入,每行都剔除分隔標識。這裡的程式碼展示來一次輸入一行:

輸入的行為可以通過一個函式控制,來控制輸入的每個部分(參閱 SplitFunc 的文件),但是對於複雜的問題或持續傳遞錯誤的,可能還是需要原有介面。

net

在 net 包中的協議特定的解析器之前對傳遞入的網路名很寬鬆。雖然文件明確指出對於ResolveTCPAddr 合法的網路名只有“tcp”,“tcp4”和“tcp6”,Go 1.0 的實現對於任何字串都會接受。而 Go 1.1 的實現,如果網路名不在這些字串中,就會返回一個錯誤。這對於其他協議特定的解析器 ResolveIPAddrResolveUDPAddr 和ResolveUnixAddr 也是一樣。

之前的的實現,ListenUnixgram 返回一個 UDPConn 作為接收連線的端點。在 Go 1.1 的實現裡,用 UnixConn 來代替,這允許用它的 ReadFrom 和 WriteTo 方法讀寫。

資料結構 IPAddr、TCPAddr 和 UDPAddr 新增了一個叫做 Zone 的新字串欄位。由於新的欄位,使用沒有標籤的複合文法(例如 net.TCPAddr{ip, port})的程式碼代替有標籤的文法(net.TCPAddr{IP: ip, Port: port})會出錯。Go 1 的相容性規則允許這個變化:客戶端程式碼必須使用標籤化的文法以避免這種破壞。

更新:為了修正由於新的結構體欄位帶來的破壞,go fix 將會重寫這些型別的程式碼以新增標籤。更通用的是,go vet 將會標識出所有應當使用欄位標籤的複合文法。

reflect

reflect 包有若干重大改進。

現在用 reflect 包返回一個“select”語句是可能的;參閱 Select 和 SelectCase 瞭解更多細節。

新的方法 Value.Convert(或 Type.ConvertibleTo)提供了對一個 Value 進行 Go 的轉換和型別斷言操作(或者是檢測這種可能性)的函式方式。

新的函式 MakeFunc 建立了一個使得在已有 Value 上呼叫函式更加容易的封裝函式,可以用於標準的 Go 引數的轉換,例如將一個 int 傳遞為 interface{}。

最後,新的函式 ChanOfMapOf 和 SliceOf 可以從已有型別中構造新 Type,例如在僅提供 T 的情況下構造 []T。

time

之前的 time 包在 FreeBSD、Linux、NetBSD、OS X 和 OpenBSD 上精確到微秒。Go 1.1 在這些作業系統上的實現可以精確到納秒。程式用微妙的精確度想外部寫入再讀出,若覆蓋掉原有值的話,將會產生精度的損失。Time 有兩個新方法,Round 和Truncate,可以用來在向外部儲存寫入前,從時間裡去除精度。

新方法 YearDay 返回指定 time 值在一年中的某天的唯一整數序數。

Timer 型別有一個新方法 Reset,讓定時器在指定的間隔後過期。

最後,一個新函式 ParseInLocation 與已有的 Parse 類似,不過會忽略解析的字串中的時區資訊,而使用傳入的位置(時區)來解析時間。這個函式解決了時間 API 中常見的混亂情況。

更新:對於那些使用更低精度的外部格式來讀寫時間的程式碼,應當修改用新的方法。

Exp 舊的程式碼樹移動到 go.exp 和 go.text 子版本庫

為了讓使用二進位制釋出版的使用者在需要的時候訪問更加容易,不包含在二進位制釋出版的 exp 和舊的原始碼樹被移動到新的子版本庫 code.google.com/p/go.exp。舉例來說,如果要訪問 ssa 包,執行

然後在 Go 程式碼中,

舊的包 exp/norm 也遷移到了新的版本庫 go.text,這裡包含了正在開發的 Unicode API 和其他文字相關的包。

庫的微小變更

下面的清單列出了庫的微小改動,大多數是一些增強。對於每個變更可參閱包相關的文件瞭解更多資訊。

  • bytes 包有兩個新函式,TrimPrefix 和 TrimSuffix,含義不言而喻。同樣,Buffer 型別有一個新方法 Grow,提供了一些控制快取內部記憶體分配的能力。最後,Reader 型別現在有 WriteTo 方法,因此它也實現了 io.WriterTo 介面。
  • 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,用於輸出 escape 過的 XML,Encoder 的方法 Inden 則專門用於輸出帶縮排的格式。
  • 在 go/ast 包中,新型別 CommentMap 和其關聯的方法使得從 Go 程式中分離和處理註釋變得更加容易。
  • 在 go/doc 包中,解析器現在可以更好的跟蹤一些如 TODO 這樣的標識,godoc 命令可以根據 -notes 引數選擇過濾或呈現這些資訊。
  • 一個新的包,go/format,為程式提供了更加方便的方式來獲得 gofmt 的格式化的能力。它有兩個函式,Node 用來格式化 Go 的解析 Node,而 Source 用來格式化 Go 的原始碼。
  • html/template 包中沒有文件並且只部分實現的“noescape”特性被移除;那些依賴它的程式會被破壞。
  • io 包現在將 io.ByteWriter 介面匯出,用以滿足一次寫一個位元組這樣的常見功能。
  • log/syslog 包現在更好的提供了系統特定的日誌功能。
  • math/big 包的 Int 型別現在有了方法 MarshalJSON 和 UnmarshalJSON 用以轉換到或從 JSON 格式轉換。同樣,Int 現在可以通過 Uint64 和 SetUint64 直接轉換到 uint64 或從 uint64 轉換,而 Rat 通過 Float64 and SetFloat64.
  • mime/multipart 包的 Writer 有了新的方法,SetBoundary 用來定義包輸出的邊界分隔。
  • net 包的 ListenUnixgram 函式修改了返回值的型別:現在它返回 UnixConn 而不是 UDPConn,這明顯是 Go 1.0 的一個錯誤。因此這個 API 的變更修復了一個 bug,這符合 Go 1 的相容性規則。
  • net 包包含了一個新函式,DialOpt,為 Dial 增加選項。每個選項都由新的介面 DialOption 體現。新的函式 DeadlineTimeoutNetwork 和 LocalAddress 然會一個 DialOption。
  • net 增加了帶區域驗證的本地 IPv6 地址的支援,如 fe80::1%lo0。地址結構體 IPAddrUDPAddr 和 TCPAddr 將區域資訊記錄在一個新的欄位裡,那些需要字串格式作為地址的函式,例如 DialResolveIPAddrResolveUDPAddr 和 ResolveTCPAddr 現在接受帶區域驗證的格式。
  • net 包新增了 LookupNS 作為解析函式。LookupNS 根據主機名返回一個 NS records 。
  • net 包向 IPConnReadMsgIP 和 WriteMsgIP)和 UDPConnReadMsgUDP 和 WriteMsgUDP)加了指定協議的讀寫方法。還有個 PacketConn 的特別版本的 ReadFrom 和 WriteTo 方法,提供了訪問資料包的帶外資料的能力。
  • net 為 UnixConn 新增了方法以便半關閉連線(CloseRead 和 CloseWrite),這與 TCPConn 的已有方法匹配。
  • net/http 包包含了若干新增。ParseTime 解析一個時間字串,會嘗試若干種常見的 HTTP 時間格式。Request 的 PostFormValue 方法與 FormValue 類似,不過忽略了 URL 引數。CloseNotifier 介面提供了伺服器端處理程式發現客戶端斷開連線的一種機制。ServeMux 型別現在有了 Handler 方法來訪問 Handler 的路徑而不需要執行它。Transport 現在可以通過 CancelRequest 取消一個正在進行的請求。最後, 當 Response.Body 在完全被處理之前被關閉的話,Transport 現在會對關閉 TCP 連線保持更樂觀的態度。
  • 新的 net/http/cookiejar 包提供了基礎的管理 HTTP cookie 的功能。
  • net/mail 包有了兩個新函式,ParseAddress 和 ParseAddressList,來解析 RFC 5322 格式化的地址到 Address 結構體。
  • net/smtp 包的 Client 型別有了一個新的方法,Hello,用於向伺服器傳送 HELO 或 EHLO 訊息。
  • net/textproto 有兩個新函式,TrimBytes 和 TrimString,用來僅在 ASCII 下進行前後空符的切除。
  • 新方法 os.FileMode.IsRegular 讓瞭解一個檔案是否是普通檔案變得更加簡單。
  • image/jpeg 現在可以讀取預載入 JPEG 檔案,並且處理某些二次取樣配置資訊。
  • regexp 包現在通過 Regexp.Longest 可以支援 Unix 原生的最左最長匹配,而 Regexp.Split 使用正規表示式定義的分離器將字串分解成組的。
  • runtime/debug 有三個關於記憶體使用的新函式。FreeOSMemory 函式觸發垃圾回收,並嘗試將未使用的記憶體退回作業系統;ReadGCStats 獲得控制器的統計資訊;而 SetGCPercent 提供了一個可程式設計的途徑來控制控制器執行頻率,包括永遠禁止其執行。
  • sort 包有一個新函式,Reverse。作為呼叫 sort.Sort 的引數的包裹,通過呼叫 Reverse 可以讓排序結果反續。
  • strings 包有兩個新函式,TrimPrefix 和 TrimSuffix 含義不言而喻,還有 Reader.WriteTo 方法,因此 Reader 現在實現了 io.WriterTo 介面。
  • syscall 包的有許多更新,包括對每個支援的作業系統的系統呼叫進行加固。
  • testing 包現在可以在效能測試中使用 AllocsPerRun 函式和 BenchmarkResult 的 AllocsPerOp 方法自動生成記憶體分配統計。還有 Verbose 函式來檢測 -v 的命令列引數狀態,和testing.B 和 testing.T 的新方法 Skip 來簡單跳過一些不必要的測試。
  • 在 text/template 和 html/template 包中,模板現在可以用圓括號來對字元序列分組,這簡化了建立複雜的字元序列的過程。TODO:連結到一個例項。同時,作為新的解析器的一部分,Node 介面有兩個方法用來提供更好的錯誤報告。這同樣遵循 Go 1 相容性規則,由於這個介面被明確期望只有text/template 和 html/template 包使用,而其安全機制保證了這點,所以應當沒有程式碼會受到影響。
  • 在 unicode/utf8 包中,新函式 ValidRune 報告了一個 rune 是否是一個合法的 Unicode 編碼值。為了確保合法,rune 的值必須在範圍內,且不能為半個代用符。
  • unicode 包的實現被更新到 Unicode 7.2.0 版本。

相關文章