Go 1.1 版本更新
来源:mikespook
Go 1.1 版本更新
Go 第一版(简称 Go 1 或 Go 1.0)发布于 2012 年三月,这个版本提供了稳定的 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:
1
2
3
|
func f(x int ) int { return x/ 0 } |
在 Go 1.1 中,一个整数被一个常量零整除不是合法的程序,因此这会是一个编译时错误。
代用的 Unicode 文字
细化了 string 和 rune 文字的定义,以便将代用部分排除在合法的 Unicode 编码值以外。参阅 Unicode 部分了解更多信息。
方法值
现在 Go 1.1 实现了方法值,也就是将函数绑定在特定的接收者的值上。例如,有一个Writer 的值 w,表达式 w.Write,是一个方法值,作为用于向 w 写入的函数;这与函数文法中对 w 进行闭包是等价的:
1
2
3
|
func (p [] byte ) (n int , err error) { return w.Write(p) } |
方法值与方法表达式是不同的,方法表达式从方法中利用指定的类型构造了一个函数;方法表达式 (*bufio.Writer).Write 与第一个参数类型指定为 (*bufio.Writer) 的函数等价:
1
2
3
|
func (w *bufio.Writer, p [] byte ) (n int , err error) { return w.Write(p) } |
更新:已有代码不受影响;这个变动是严格的向后兼容。
Return requirements
在 Go 1.1 之前,一个函数返回一个值必须明确的在函数结束时“return”或调用 panic;这是一个让程序明确函数的概念的简单的途径。但是,显然有许多情况最后的“return”没有必要,例如, 一个只有死循环“for”的函数。
在 Go 1.1 中,关于最后的“return”语句的规则更加宽松。它引入了一个终止语句的概念,它保证了在函数中这个语句总是最后被执行。例如在没有条件的“for”循环中,也没有“if-else”语句用来在中间通过“return”结束。那么函数的最后一个语句可以在语法上被认为是终止语句,而不需要最后的“return”语句。
注意这个规则纯粹是语法上的:它并不关注代码中的值,因此也没有复杂的分析。
更新:这个变动是向后兼容的,不过有着多余“return”语句或调用 panic 的已有代码可能需要手工处理一下。这些代码可用 go vet
来标识。
实现和工具的变更
命令行参数解析
在 gc 工具链中,编译器和链接器现在使用与 Go 的 flag
包一致的命令行参数解析规则,而与传统的 Unix 参数解析背道而驰。这可能会对直接调用工具的脚本产生影响。例如,go tool 6c -Fw -Dfoo
现在必须写为 go tool 6c -F -w -D foo
。
在 64 位平台上的整数大小
该语言允许根据具体实现选择 int 类型和 uint 类型是 32 或 64 位的。之前 Go 的实现是在所有系统上都让 int 和 uint 是 32 位的。现在 gc 和 gccgo 的实现都让 int 和 uint 在如 AMD64/x86-64 这样的平台上是 64 位的。抛开别的不说,单这个就使得 slice 在 64 位平台上可以分配超过 20 亿的元素。
更新:大多数程序不会受到这个的影响。 由于 Go 不允许不同数字类型之间的隐式转换,不会有程序在编译时报错。然而,那些隐式假设 int 是 32 位的程序,在行为上可能发生变化。例如,这个程序在 64 位系统中会打印正数,在 32 位系统中会打印复数:
1
2
3
|
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)应该用下面的具有可移植性的代码代替:
1
|
i := int (int32(x)) |
Unicode
为了能够表达 UTF-16 中超过 65535 的编码值,Unicode 定义了代用部分,一个仅用于组装更大的值的编码值范围,且仅在 UTF-16 中。在这个代用范围内的编码值如果用于其他任何情况都是非法的,如作为 UTF-8 编码,或作为独立的 UTF-16 编码。例如在遇到将一个 rune 转换成 UTF-8 时,它被当作一个编码错误对待,并产生一个替代的 rune,utf8.RuneError, U+FFFD。
这个程序,
1
2
3
4
5
|
import "fmt" func main() { fmt.Printf( "%+q\n" , string( 0xD800 )) } |
在 Go 1.0 中打印“\ud800”,但在 Go 1.1 中打印“\ufffd”。
半个代用 Unicode 值现在在 rune 和 string 常量中都是非法的,因此如“\ud800”和“\ud800”的常量现在会被编译器拒绝。当编写为独立的 UTF-8 编码的字节时,这样字符串还是可以被创建的,例如“\xed\xa0\x80”。然而,当这个字符串被作为一个 rune 序列解码时,比如在 range 循环中,它只会生成 utf8.RuneError 值。
Unicode 字节顺序让 U+FFFE 和 U+FEFF 在 UTF-8 编码下可以作为 Go 源码的第一个字符出现。虽然在字节顺序未设定的 UTF-8 编码中,它是完全不必要的,不过有些编辑器会将其作为“魔法数值”添加进去,用来标识一个 UTF-8 编码的文件。
更新:大多数程序不会受到代用变更的影响。基于旧的行为的程序应当通过修改来避免问题。字节顺序标识的变更是严格向后兼容的。
gc 汇编
基于如 int 到 64 位和其他一些变化,在 gc 工具链的函数参数的栈布局发生了变化。使用汇编编写的函数至少需要一个 frame 指针偏移量。
更新:现在 go vet
命令可以检查用汇编实现的函数是否匹配 Go 的函数原型。
go 命令的变化
为了让新的 Go 用户获得更好的体验,go 命令做了若干改动。
首先,当编译、测试或运行 Go 代码的时候,go 命令会给出更多的错误信息细节,当一个包无法被定位时,会列出搜索的路径清单。
1
2
3
4
|
$ 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。
1
2
|
$ 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 |
最后,作为前面变化的结果,go get
命令会在 $GOPATH 和 $GOROOT 设置为相同值的时候报错。
1
2
3
|
$ GOPATH = $GOROOT go get code.google.com / p / foo / quxx warning: GOPATH set to GOROOT ( / home / User / 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 参数。
1
|
$ go test - cpuprofile cpuprof.out mypackage |
在 go test
运行之后,mypackage.test 将会留在目录中。
go test
命令现在可以报告 goroutine 在哪里阻塞的测试信息,也就是说,它们在哪一直等着某个事件,例如一个 channel 通讯之类的。当用 -blockprofile
开启 go test
的阻塞测试时,就会展示这些信息。 运行 go help test
了解更多信息。
go fix 命令的变化
fix 命令通常以 go fix
执行,不再提供从 Go1 之前的版本升级到 Go 1 API 的功能。如果要升级 Go 1 之前的代码到 Go 1.1,首先应当使用 Go 1.0 的工具链,将代码转化到 Go 1.0。
性能
用 Go 1.1 的 gc 工具集编译出来的代码的性能对于大多数 Go 程序来说应当有显著的提升。一般来说,与 Go 1.0 相比,大约有 30%-40% 的提升,有时甚至更高,当然也会比这个值低,甚至没有提升。对于工具和库来说,有太多的小的性能驱使的改动,以至于无法将它们全部列在这里。不过下面的主要变更还是有必要留意的:
- gc 编译器在大多数情况下都会生成较好的代码,尤其是在 32 位 Intel 架构下的浮点值。
- gc 编译器做了更多的内连,包括在运行时的一些操作,例如 append 和接口转换。
- Go 的 map 有了新的实现,在内存复制和 CPU 时间上有了重大的改进。
- 垃圾回收实现了更多的并行,这可以降低在多 CPU 环境下运行的程序的延迟。
- 垃圾回收同时也更加精准,这增加了一点 CPU 时间开销,但是极大的降低了堆的大小,尤其是在 32 位的架构下。
- 通过紧密结合运行时和网络库,在网络操作时需要的上下文切换会更少。
标准库的变化
bufio.Scanner
在 bufio 包中有多种方式获取文本输入,ReadBytes、ReadString 和特别的 ReadLine,对于简单的目的这些都有些过于复杂了。在 Go 1.1 中,添加了一个新类型,Scanner,以便更容易的处理如按行读取输入序列或空格分隔的词等,这类简单的任务。它终结了如输入一个很长的有问题的行这样的输入错误,并且提供了简单的默认行为:基于行的输入,每行都剔除分隔标识。这里的代码展示来一次输入一行:
1
2
3
4
5
6
7
|
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 创建了一个使得在已有 Value 上调用函数更加容易的封装函数,可以用于标准的 Go 参数的转换,例如将一个 int 传递为 interface{}。
最后,新的函数 ChanOf、MapOf 和 SliceOf 可以从已有类型中构造新 Type,例如在仅提供 T 的情况下构造 []T。
time
之前的 time 包在 FreeBSD、Linux、NetBSD、OS X 和 OpenBSD 上精确到微秒。Go 1.1 在这些操作系统上的实现可以精确到纳秒。程序用微妙的精确度想外部写入再读出,若覆盖掉原有值的话,将会产生精度的损失。Time 有两个新方法,Round 和Truncate,可以用来在向外部存储写入前,从时间里去除精度。
新方法 YearDay 返回指定 time 值在一年中的某天的唯一整数序数。
Timer 类型有一个新方法 Reset,让定时器在指定的间隔后过期。
最后,一个新函数 ParseInLocation 与已有的 Parse 类似,不过会忽略解析的字符串中的时区信息,而使用传入的位置(时区)来解析时间。这个函数解决了时间 API 中常见的混乱情况。
更新:对于那些使用更低精度的外部格式来读写时间的代码,应当修改用新的方法。
Exp 旧的代码树移动到 go.exp 和 go.text 子版本库
为了让使用二进制发布版的用户在需要的时候访问更加容易,不包含在二进制发布版的 exp 和旧的源码树被移动到新的子版本库 code.google.com/p/go.exp。举例来说,如果要访问 ssa 包,执行
1
|
$ go get code.google.com / p / go.exp / ssa |
然后在 Go 代码中,
1
|
import "code.google.com/p/go.exp/ssa" |
旧的包 exp/norm 也迁移到了新的版本库 go.text,这里包含了正在开发的 Unicode API 和其他文本相关的包。
库的微小变更
下面的清单列出了库的微小改动,大多数是一些增强。对于每个变更可参阅包相关的文档了解更多信息。
bytes
包有两个新函数,TrimPrefix
和TrimSuffix,含义不言而喻。同样,
Buffer
类型有一个新方法Grow,提供了一些控制缓存内部内存分配的能力。最后,
Reader
类型现在有WriteTo
方法,因此它也实现了io.WriterTo
接口。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,用于输出
escape 过的 XML,Encoder 的方法
Inden 则专门用于输出带缩进的格式。
- 在
go/ast
包中,新类型CommentMap
和其关联的方法使得从 Go 程序中分离和处理注释变得更加容易。 - 在
go/doc
包中,解析器现在可以更好的跟踪一些如 TODO 这样的标识,godoc
命令可以根据 -notes 参数选择过滤或呈现这些信息。 - 一个新的包,
go/format,为程序提供了更加方便的方式来获得 gofmt 的格式化的能力。它有两个函数,
Node
用来格式化 Go 的解析Node,而
Source
用来格式化 Go 的源代码。 html/template 包中
没有文档并且只部分实现的“noescape”特性被移除;那些依赖它的程序会被破坏。io
包现在将io.ByteWriter
接口导出,用以满足一次写一个字节这样的常见功能。log/syslog
包现在更好的提供了系统特定的日志功能。math/big
包的Int
类型现在有了方法MarshalJSON
和UnmarshalJSON
用以转换到或从 JSON 格式转换。同样,Int
现在可以通过Uint64
和SetUint64
直接转换到 uint64 或从 uint64 转换,而Rat
通过Float64
andSetFloat64
.mime/multipart
包的Writer 有了新的方法,
SetBoundary 用来定义包输出的边界分隔。
net
包的ListenUnixgram
函数修改了返回值的类型:现在它返回UnixConn
而不是UDPConn,这明显是 Go 1.0 的一个错误。因此这个 API 的变更修复了一个 bug,这符合 Go 1 的兼容性规则。
net
包包含了一个新函数,DialOpt,为
Dial 增加选项。每个选项都由新的接口
DialOption
体现。新的函数Deadline、
Timeout、
Network 和
LocalAddress
然会一个DialOption。
net
增加了带区域验证的本地 IPv6 地址的支持,如fe80::1%lo0。地址结构体
IPAddr、
UDPAddr 和
TCPAddr
将区域信息记录在一个新的字段里,那些需要字符串格式作为地址的函数,例如Dial、
ResolveIPAddr、
ResolveUDPAddr 和
ResolveTCPAddr 现在接受带区域验证的格式。
net
包添加了LookupNS
作为解析函数。LookupNS
根据主机名返回一个 NS records 。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 在完全被处理之前被关闭的话,T
ransport 现在会对关闭 TCP 连接保持更乐观的态度。- 新的
net/http/cookiejar
包提供了基础的管理 HTTP cookie 的功能。 net/mail
包有了两个新函数,ParseAddress
和ParseAddressList,来解析 R
FC 5322 格式化的地址到Address
结构体。net/smtp
包的Client
类型有了一个新的方法,Hello
,用于向服务器发送HELO
或EHLO
消息。net/textproto
有两个新函数,TrimBytes
和TrimString,用来仅在
ASCII 下进行前后空符的切除。- 新方法
os.FileMode.IsRegular
让了解一个文件是否是普通文件变得更加简单。 image/jpeg
现在可以读取预加载 JPEG 文件,并且处理某些二次取样配置信息。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
包的有许多更新,包括对每个支持的操作系统的系统调用进行加固。testing
包现在可以在性能测试中使用AllocsPerRun
函数和BenchmarkResult 的
AllocsPerOp
方法自动生成内存分配统计。还有Verbose
函数来检测 -v 的命令行参数状态,和testing.B
和testing.T 的
新方法Skip
来简单跳过一些不必要的测试。- 在
text/template
和html/template
包中,模板现在可以用圆括号来对字符序列分组,这简化了创建复杂的字符序列的过程。TODO:链接到一个实例。同时,作为新的解析器的一部分,Node
接口有两个方法用来提供更好的错误报告。这同样遵循 Go 1 兼容性规则,由于这个接口被明确期望只有text/template
和html/template 包使用,而其安全机制保证了这点,所以应当
没有代码会受到影响。 - 在
unicode/utf8
包中,新函数ValidRune
报告了一个 rune 是否是一个合法的 Unicode 编码值。为了确保合法,rune 的值必须在范围内,且不能为半个代用符。 unicode
包的实现被更新到 Unicode 7.2.0 版本。