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 中,整数除以常量零会引发运行时恐慌:
func f(x int) int {
return x/0
}
在 Go 1.1 中,整数除以常量零不是合法的程序,因此这将导致编译时错误。
Unicode 字面量中的代理项
字符串和符文字面量的定义已得到完善,将代理对半部分从有效的 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)
}
更新指南:现有代码不受影响;此变更严格向后兼容。
返回语句要求
在 Go 1.1 之前,返回值的函数需要在函数末尾显式包含 “return” 语句或调用 panic;这是让程序员明确函数含义的一种简单方式。但在许多情况下,最后的 “return” 显然是不必要的,例如只包含无限 “for” 循环的函数。
在 Go 1.1 中,关于最后 “return” 语句的规则更加宽松。它引入了终止语句的概念,即保证函数最后执行的语句。示例包括没有条件的 “for” 循环,以及每个分支都以 “return” 结尾的 “if-else” 语句。如果函数的最后一条语句在语法上可以证明是终止语句,则不需要最后的 “return” 语句。
请注意,该规则纯粹是语法性的:它不关注代码中的值,因此不需要复杂的分析。
更新指南:此变更向后兼容,但包含多余 “return” 语句和 panic 调用的现有代码可以手动简化。可以使用 go vet 来识别此类代码。
实现和工具的变更
gccgo 的状态
GCC 的发布计划与 Go 的发布计划不一致,因此 gccgo 的发布版本不可避免地会有一些偏差。GCC 4.8.0 于 2013 年 3 月发布,包含了接近 Go 1.1 版本的 gccgo。其库版本略微落后于发布版本,但最大的区别是方法值尚未实现。我们预计在 2013 年 7 月左右,GCC 4.8.2 发布时,其中的 gccgo 将提供完整的 Go 1.1 实现。
命令行标志解析
在 gc 工具链中,编译器和链接器现在使用与 Go flag 包相同的命令行标志解析规则,这与传统的 Unix 标志解析不同。这可能会影响直接调用该工具的脚本。例如,go tool 6c -Fw -Dfoo 现在必须写成 go tool 6c -F -w -D foo。
64 位平台上 int 的大小
语言允许实现选择 int 和 uint 类型是 32 位还是 64 位。之前的 Go 实现在所有系统上都将 int 和 uint 设为 32 位。现在,在 AMD64/x86-64 等 64 位平台上,gc 和 gccgo 实现都将 int 和 uint 设为 64 位。这除了其他好处外,还使得在 64 位平台上可以分配超过 20 亿个元素的切片。更新说明:
大多数程序将不受此变更影响。
由于 Go 不允许不同数值类型之间的隐式转换,
因此没有程序会因该变更而停止编译。
然而,那些隐含假设 int 仅为 32 位的程序可能会改变行为。
例如,以下代码在 64 位系统上打印正数,在 32 位系统上打印负数:
x := ^uint32(0) // x 为 0xffffffff
i := int(x) // 在 32 位系统上 i 为 -1,在 64 位系统上为 0xffffffff
fmt.Println(i)
若要编写可移植代码以实现 32 位符号扩展(在所有系统上均得到 -1),则应改为:
i := int(int32(x))
64 位架构的堆大小
在 64 位架构上,最大堆大小已大幅增加, 从几 GB 增加到数十 GB。 (具体细节取决于系统,且可能变化。)
在 32 位架构上,堆大小未作更改。
更新说明: 此变更除了允许现有程序使用更大的堆运行外,不应产生其他影响。
Unicode
为了能够在 UTF-16 中表示大于 65535 的码位,
Unicode 定义了 代理对(surrogate halves),
这是一个仅用于组装大值且仅用于 UTF-16 的码位范围。
该代理范围内的码位用于任何其他目的均属非法。
在 Go 1.1 中,编译器、库和运行时均遵守此约束:
代理对作为 rune 值、编码为 UTF-8 或单独编码为 UTF-16 时均属非法。
当遇到代理对时(例如,在将 rune 转换为 UTF-8 时),它将被视为编码错误,
并返回替换字符
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 值。
编码为 UTF-8 的 Unicode 字节顺序标记 U+FEFF 现在允许作为 Go 源文件的首字符。 尽管其在无字节序的 UTF-8 编码中出现显然没有必要, 但一些编辑器会添加此标记作为一种标识 UTF-8 编码文件的“魔数”。
更新说明: 大多数程序不受代理对变更影响。 依赖旧行为的程序应进行修改以避免此问题。 字节顺序标记的变更是严格向后兼容的。
竞态检测器
工具的一个主要新增功能是 竞态检测器(race detector),
它用于发现由并发访问同一变量(其中至少有一个访问是写入操作)所导致的程序错误。
这一新功能内置于 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 在何处阻塞,即
它们倾向于在何处等待事件(如通道通信)而停滞。
该信息以 阻塞性能分析(blocking profile)的形式呈现,
通过 go test 的
-blockprofile
选项启用。
运行 go help test 获取更多信息。
go fix 命令的变更 {#gofix}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/arm、netbsd/386、netbsd/amd64、netbsd/arm、openbsd/386 和 openbsd/amd64 平台的实验性支持。
对于 freebsd/arm 或 netbsd/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 map 有了一种新的实现方式,显著降低了内存占用和 CPU 时间。
- 垃圾回收器(GC)的并行度得到提升,这可以减少在多个 CPU 上运行的程序的延迟。
- 垃圾回收器也更加精确,这会消耗少量 CPU 时间,但能显著减少堆的大小,尤其是在 32 位架构上。
- 由于运行时与网络库的耦合更加紧密,网络操作所需的上下文切换次数有所减少。
标准库变更
bufio.Scanner
[bufio](/pkg/bufio/) 包中用于扫描文本输入的各种例程,如 ReadBytes、ReadString,特别是 ReadLine,对于简单用途而言使用起来过于复杂。在 Go 1.1 中,新增了一个 Scanner 类型,使得执行简单的任务(如逐行读取输入或读取以空格分隔的单词)变得更加容易。它简化了问题:在遇到异常输入(如超长行)时会终止扫描,并提供了一个简单的默认行为:面向行的输入,每行会去除其终止符。以下是逐行复制输入的示例代码:
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fmt.Println(scanner.Text()) // Println 会重新添加末尾的 '\n'
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "读取标准输入时出错:", err)
}
扫描行为可以通过一个函数来调整输入的分割方式(参见 SplitFunc 的文档),但对于复杂的问题或需要在出错后继续处理的情况,可能仍需使用旧的接口。
net
[net](/pkg/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](/pkg/reflect/) 包增加了几项重要功能。
现在可以使用 reflect 包执行 “select” 语句;详情请参阅 Select 和 SelectCase 的说明。
新增方法 Value.Convert(或 Type.ConvertibleTo)提供了在 Value 上执行 Go 转换或类型断言操作(或测试其可能性)的功能。新增函数
MakeFunc
创建了一个包装函数,使得使用现有的
Values
更易调用函数,并执行标准的 Go 转换,例如将实际的 int 传递给形式上的 interface{}。
最后,新增函数
ChanOf、
MapOf
和
SliceOf
根据现有类型构造新的
Types,
例如在仅给定 T 的情况下构造类型 []T。
time
在 FreeBSD、Linux、NetBSD、OS X 和 OpenBSD 上,之前的
time 包
以微秒精度返回时间。Go 1.1 在这些系统上的实现现在以纳秒精度返回时间。将时间以微秒精度写入外部格式并读回、期望恢复原始值的程序,会受到精度损失的影响。新增了两个 Time 的方法,
Round
和
Truncate,
可用于在将时间传递给外部存储之前去除其精度。
新增方法
YearDay
返回时间值所指定年份中,以 1 开始计数的整数日期序号。
Timer 类型有一个新方法
Reset,
它修改计时器使其在指定的持续时间后到期。
最后,新增函数
ParseInLocation
类似于现有的
Parse,
但在某个位置(时区)的上下文中解析时间,忽略被解析字符串中的时区信息。此函数解决了时间 API 中一个常见的混淆来源。
更新: 需要使用较低精度的外部格式读写时间的代码应修改为使用新方法。
Exp 和 old 子树已移至 go.exp 和 go.text 子仓库
为了让二进制发行版在需要时更易于访问,exp 和 old 源代码子树(不包含在二进制发行版中)已移至新的 go.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 和其他与文本相关的包将在那里开发。
新包
新增了三个包。
go/format包提供了一种便捷的方式,让程序可以访问go fmt命令的格式化功能。它有两个函数:Node用于格式化 Go 解析器Node, 和Source用于将任意 Go 源代码重新格式化为go fmt命令提供的标准格式。net/http/cookiejar包提供了管理 HTTP cookie 的基础功能。runtime/race包提供了用于数据竞争检测的底层工具。它是竞态检测器的内部组件,并不导出任何其他用户可见的功能。
库的次要变更
以下列表总结了库的若干次要变更,主要是新增内容。有关每次变更的更多详细信息,请参阅相关包的文档。- bytes 包新增了两个函数:TrimPrefix 和 TrimSuffix,其功能不言自明。此外,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类型新增了MarshalJSON和UnmarshalJSON方法,用于与 JSON 表示形式进行转换。此外,Int现在可通过Uint64和SetUint64直接与uint64进行转换;而Rat则可通过Float64和SetFloat64与float64进行转换。 -
mime/multipart包的Writer新增了SetBoundary方法,用于定义打包输出的边界分隔符。Reader现在也能透明地解码任何quoted-printable部分,并在解码时移除Content-Transfer-Encoding头部。 -
net包的ListenUnixgram函数的返回类型已更改:现在返回UnixConn而非UDPConn,这显然是 Go 1.0 中的一个错误。由于此 API 变更修复了一个缺陷,因此被 Go 1 兼容性规则所允许。 -
net包新增了对带区域限定符的链路本地 IPv6 地址(如fe80::1%lo0)的支持。地址结构体IPAddr、UDPAddr和TCPAddr在新字段中记录区域信息,而期望接收这些地址字符串形式的函数(如Dial、ResolveIPAddr、ResolveUDPAddr和ResolveTCPAddr)现在也接受带区域限定符的形式。 -
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/mail包新增了两个函数:ParseAddress和ParseAddressList,用于将 RFC 5322 格式的邮件地址解析为Address结构体。 -
net/smtp包的Client类型新增了一个Hello方法,用于向服务器发送HELO或EHLO消息。 -
net/textproto包新增了两个函数:TrimBytes和TrimString,用于对前导和尾随空格进行仅 ASCII 的修剪。 -
新方法
os.FileMode.IsRegular使得判断一个文件是否为普通文件变得简单。 -
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包在各种 BSD(包括 Darwin)上的Fchflags函数签名已更改。它现在将int作为第一个参数,而不是字符串。由于此 API 变更修复了一个缺陷,因此被 Go 1 兼容性规则所允许。 -
syscall包也接收了大量更新,以使其更全面地包含每个受支持操作系统的常量和系统调用。 -
testing包现在使用新的AllocsPerRun函数,在测试和基准测试中自动生成分配统计信息。testing.B上的ReportAllocs方法将为调用它的基准测试启用内存分配统计信息的打印。它还引入了BenchmarkResult的AllocsPerOp方法。此外,还有一个新的Verbose函数用于测试-v命令行标志的状态,以及testing.B和testing.T上的新方法Skip,用于简化跳过不适当的测试。 -
在
text/template和html/template包中,模板现在可以使用括号对管道元素进行分组,从而简化复杂管道的构建。此外,作为新解析器的一部分,Node接口新增了两个方法以提供更好的错误报告。尽管这违反了 Go 1 兼容性规则,但不应影响任何现有代码,因为该接口明确仅用于text/template和html/template包,并且有保障措施确保这一点。 -
unicode包的实现已更新至 Unicode 6.2.0 版本。 -
在
unicode/utf8包中,新函数ValidRune报告该 rune 是否是有效的 Unicode 码点。一个 rune 要有效,必须处于范围内且不能是代理项的一半。