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 必须在范围内且不是代理半区。