Go 1.1 发布说明

Go 1.1 简介

2012 年 3 月发布的 Go 1 版(简称 Go 1 或 Go 1.0)开启了 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 字面量中的代理

字符串和 rune 字面量的定义已得到改进,从有效 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。其库略微落后于发布版,但最大的区别在于未实现方法值。我们预计 GCC 的 4.8.2 版本将在 2013 年 7 月左右发布,其中包含提供完整 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 位架构上,最大堆大小已大大增加,从几 GB 增加到几十 GB。(确切的细节取决于系统,可能会更改。)

在 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 时(例如在范围循环中),它只会产生 utf8.RuneError 值。

现在允许将以 UTF-8 编码的 Unicode 字节顺序标记 U+FEFF 作为 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 命令的更改

通常作为 go fix 运行的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/armcgo 的实验性支持。

交叉编译

交叉编译时,go 工具默认会禁用 cgo 支持。

要显式启用 cgo,请设置 CGO_ENABLED=1

性能

使用 Go 1.1 gc 工具套件编译的代码的性能对于大多数 Go 程序来说应该会有明显提升。相对于 Go 1.0,典型的改进似乎约为 30%-40%,有时甚至更多,但偶尔也会更少甚至没有。工具和库中有很多小的性能驱动的调整,这里无法一一列出,但以下主要更改值得注意

标准库的更改

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 返回时间值指定的年份的从 1 开始的整数天数。

Timer 类型有一个新的方法Reset,用于修改计时器以在指定的持续时间后到期。

最后,新函数ParseInLocation 类似于现有的Parse,但它在位置(时区)的上下文中解析时间,忽略解析字符串中的时区信息。此函数解决了时间 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 和其他与文本相关的包将在其中开发。

新包

有三个新包。

库的次要更改

以下列表总结了库的一些次要更改,主要是添加。有关每个更改的更多信息,请参阅相关的包文档。