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 现在实现了 方法值,它们是已绑定到特定接收器值的函数。例如,给定 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
的发布不可避免地存在一些偏差。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 实现使所有系统上的 int
和 uint
为 32 位。gc 和 gccgo 实现现在都使 AMD64/x86-64 等 64 位平台上的 int
和 uint
为 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/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 映射有一个新的实现,显着减少了内存占用和 CPU 时间。
- 垃圾回收器变得更加并行,这可以减少在多 CPU 上运行的程序的延迟。
- 垃圾回收器也更加精确,这会消耗少量 CPU 时间,但可以显着减少堆的大小,尤其是在 32 位架构上。
- 由于运行时和网络库的更紧密耦合,网络操作所需的上下文切换更少。
标准库的更改
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
创建一个包装函数,以便更轻松地使用现有的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
源代码子树(不包含在二进制发行版中)已移动到 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
包新增了两个函数,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
包包含一个新的类型Dialer
,用于向Dial
提供选项。net
包添加了对带有区域限定符的链接本地 IPv6 地址的支持,例如fe80::1%lo0
。地址结构IPAddr
、UDPAddr
和TCPAddr
在一个新字段中记录了区域,并且期望这些地址的字符串形式的函数(例如Dial
、ResolveIPAddr
、ResolveUDPAddr
和ResolveTCPAddr
)现在接受带区域限定符的形式。net
包在其解析函数集中添加了LookupNS
。LookupNS
返回主机名的 NS 记录。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
可以很容易地判断文件是否为普通文件。 os/signal
包新增了一个函数Stop
,用于停止该包将任何进一步的信号传递到通道。regexp
包现在通过Regexp.Longest
方法支持 Unix 原生的最左最长匹配,而Regexp.Split
根据正则表达式定义的分隔符将字符串切分成多个片段。runtime/debug
包新增了三个关于内存用量的函数。FreeOSMemory
函数触发垃圾回收的运行,然后尝试将未使用的内存返回给操作系统;ReadGCStats
函数检索关于垃圾回收器的统计信息;SetGCPercent
提供了一种以编程方式控制垃圾回收器运行频率的方法,包括完全禁用它。sort
包新增了一个函数Reverse
。使用Reverse
包装对sort.Sort
的调用的参数,会导致排序顺序反转。strings
包新增了两个函数TrimPrefix
和TrimSuffix
,功能不言自明,以及新的方法Reader.WriteTo
,因此Reader
类型现在实现了io.WriterTo
接口。- 在各种 BSD(包括 Darwin)上,
syscall
包的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
报告运行符是否为有效的 Unicode 代码点。要有效,运行符必须在范围内且不能是代理一半。