[翻译] Go 1.1 介绍

由于年后工作实在太忙,一直也没写点什么。不过这篇我觉得值得,所以……
原文:https://tip.golang.org/hg/doc/go1.1.html
原文链接我进行了替换,现在指向 tip 大多数应该正确吧。不过如果是 Go 1.1 正式发布半年后,我可不保证了。
————翻译分隔线————

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:

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 进行闭包是等价的:

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)
}

更新:已有代码不受影响;这个变动是严格的向后兼容。

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 位系统中会打印负数:

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))

Unicode

为了能够表达 UTF-16 中超过 65535 的编码值,Unicode 定义了代用部分,一个仅用于组装更大的值的编码值范围,且仅在 UTF-16 中。在这个代用范围内的编码值如果用于其他任何情况都是非法的,如作为 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 和 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 命令会给出更多的错误信息细节,当一个包无法被定位时,会列出搜索的路径清单。

$ 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

最后,作为前面变化的结果,go get 命令会在 $GOPATH 和 $GOROOT 设置为相同值的时候报错。

$ 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 参数。

$ 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 包中有多种方式获取文本输入,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 方法读写。

数据结构 IPAddr、TCPAddr 和 UDPAddr 添加了一个叫做 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 创建了一个使得在已有 Value 上调用函数更加容易的封装函数,可以用于标准的 Go 参数的转换,例如将一个 int 传递为 interface{}。

最后,新的函数 ChanOfMapOfSliceOf 可以从已有类型中构造新 Type,例如在仅提供 T 的情况下构造 []T。

time

之前的 time 包在 FreeBSD、Linux、NetBSD、OS X 和 OpenBSD 上精确到微秒。Go 1.1 在这些操作系统上的实现可以精确到纳秒。程序用微妙的精确度向外部写入再读出,若覆盖掉原有值的话,将会产生精度的损失。Time 有两个新方法,RoundTruncate,可以用来在向外部存储写入前,从时间里去除精度。

新方法 YearDay 返回指定 time 值在一年中的某天的唯一整数序数。

Timer 类型有一个新方法 Reset,让定时器在指定的间隔后过期。

最后,一个新函数 ParseInLocation 与已有的 Parse 类似,不过会忽略解析的字符串中的时区信息,而使用传入的位置(时区)来解析时间。这个函数解决了时间 API 中常见的混乱情况。

更新:对于那些使用更低精度的外部格式来读写时间的代码,应当修改用新的方法。

Exp 旧的代码树移动到 go.exp 和 go.text 子版本库

为了让使用二进制发布版的用户在需要的时候访问更加容易,不包含在二进制发布版的 exp 和旧的源码树被移动到新的子版本库 code.google.com/p/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 和其他文本相关的包。

库的微小变更

下面的清单列出了库的微小改动,大多数是一些增强。对于每个变更可参阅包相关的文档了解更多信息。

  • 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 and SetFloat64.
  • mime/multipart 包的 Writer 有了新的方法,SetBoundary 用来定义包输出的边界分隔。
  • net 包的 ListenUnixgram 函数修改了返回值的类型:现在它返回 UnixConn 而不是 UDPConn,这明显是 Go 1.0 的一个错误。因此这个 API 的变更修复了一个 bug,这符合 Go 1 的兼容性规则。
  • net 包包含了一个新函数,DialOpt,为 Dial 增加选项。每个选项都由新的接口 DialOption 体现。新的函数 DeadlineTimeoutNetwork 和 LocalAddress 然会一个 DialOption。
  • net 增加了带区域验证的本地 IPv6 地址的支持,如 fe80::1%lo0。地址结构体 IPAddrUDPAddr 和 TCPAddr 将区域信息记录在一个新的字段里,那些需要字符串格式作为地址的函数,例如 DialResolveIPAddrResolveUDPAddr 和 ResolveTCPAddr 现在接受带区域验证的格式。
  • net 包添加了 LookupNS 作为解析函数。LookupNS 根据主机名返回一个 NS records 。
  • net 包向 IPConnReadMsgIP 和 WriteMsgIP)和 UDPConnReadMsgUDP 和 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/http/cookiejar 包提供了基础的管理 HTTP cookie 的功能。
  • net/mail 包有了两个新函数,ParseAddress 和 ParseAddressList,来解析 RFC 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 版本。

————翻译分隔线————
时运不济,几片去痛片压制下才勉强撑了几天。只是看来去痛片解决不了发烧问题。回到家被强制休息了……
拖到今天才翻译完,而且我相信,质量一定很差很差很差……
大家自己看吧!有问题给我留言,我休眠了。
如果明天早上我没回来,请诸位向 God of Gopher 礼拜,我一定是去了那美丽的地方。

Join the Conversation

7 Comments

  1. 在 32 位系统中会打印复数: 复数-》负数?
    程序用微妙的精确度想外部写入再读: 想-》向?

Leave a comment

Your email address will not be published. Required fields are marked *