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:
- func f(x int) int {
- return x/0
- }
在 Go 1.1 中,一个整数被一个常量零整除不是合法的程序,因此这会是一个编译时错误。
代用的 Unicode 文字
细化了 string 和 rune 文字的定义,以便将代用部分排除在合法的 Unicode 编码值以外。参阅 Unicode 部分了解更多信息。
方法值
现在 Go 1.1 实现了方法值,也就是将函数绑定在特定的接收者的值上。例如,有一个Writer 的值 w,表达式 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)
- }
更新:已有代码不受影响;这个变动是严格的向后兼容。
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 位系统中会打印复数:
- 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))
Unicode
为了能够表达 UTF-16 中超过 65535 的编码值,Unicode 定义了代用部分,一个仅用于组装更大的值的编码值范围,且仅在 UTF-16 中。在这个代用范围内的编码值如果用于其他任何情况都是非法的,如作为 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 和 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 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/User/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
在 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 位的架构下。
·通过紧密结合运行时和网络库,在网络操作时需要的上下文切换会更少。
#p#
标准库的变化
bufio.Scanner
在 bufio 包中有多种方式获取文本输入,ReadBytes、ReadString 和特别的 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 的实现,如果网络名不在这些字符串中,就会返回一个错误。这对于其他协议特定的解析器 ResolveIPAddr、ResolveUDPAddr 和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{}。
***,新的函数 ChanOf、MapOf 和 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 get code.google.com/p/go.exp/ssa
然后在 Go 代码中,
- import "code.google.com/p/go.exp/ssa"
旧的包 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 体现。新的函数 Deadline、Timeout、Network 和 LocalAddress 然会一个 DialOption。
net 增加了带区域验证的本地 IPv6 地址的支持,如 fe80::1%lo0。地址结构体 IPAddr、UDPAddr 和 TCPAddr 将区域信息记录在一个新的字段里,那些需要字符串格式作为地址的函数,例如 Dial、ResolveIPAddr、ResolveUDPAddr 和 ResolveTCPAddr 现在接受带区域验证的格式。
net 包添加了 LookupNS 作为解析函数。LookupNS 根据主机名返回一个 NS records 。
net 包向 IPConn(ReadMsgIP 和 WriteMsgIP)和 UDPConn(ReadMsgUDP 和 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 版本。
原文链接:http://www.mikespook.com/2013/03/%E7%BF%BB%E8%AF%91-go-1-1-%E4%BB%8B%E7%BB%8D/