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 现在实现了方法值,它们是已绑定到特定接收器值的函数。例如,给定一个 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
。它的库稍落后于发布,但最大的区别在于方法值尚未实现。我们预计在 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 实现在所有系统上都将 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 位架构上,最大堆大小已大幅增加,从几千兆字节增加到几十千兆字节。(具体细节取决于系统,并且可能会发生变化。)
在 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/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 maps 采用了新的实现,显著减少了内存占用和 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
返回时间值指定的年份的基于一的整数日编号。
Timer
类型有一个新方法 Reset
,它修改计时器以在指定持续时间后过期。
最后,新的函数 ParseInLocation
类似于现有的 Parse
,但在位置(时区)上下文中解析时间,忽略解析字符串中的时区信息。此函数解决了 time 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
。将对sort.Sort
的调用参数用Reverse
调用包装,会导致排序顺序反转。strings
包有两个新函数,TrimPrefix
和TrimSuffix
,其属性不言而喻,以及新方法Reader.WriteTo
,因此Reader
类型现在实现了io.WriterTo
接口。syscall
包的Fchflags
函数在各种 BSD 系统(包括 Darwin)上的签名已更改。它现在将 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 必须在范围内且不是代理半区。