Go 1 发行说明
Go 1 简介
Go 版本 1,简称 Go 1,定义了一种语言和一套核心库,为创建可靠的产品、项目和出版物提供了稳定的基础。
Go 1 的主要动机是为用户提供稳定性。人们应该能够编写 Go 程序,并期望它们在数年内(包括在 Google App Engine 等生产环境中)无需更改即可继续编译和运行。同样,人们应该能够编写关于 Go 的书籍,能够说明书籍描述的是哪个 Go 版本,并且该版本号在很久以后仍然有意义。
在 Go 1 中编译的代码,除了少数例外,在 Go 1 版本的整个生命周期内应继续编译和运行,即使我们发布了诸如 Go 版本 1.1、1.2 等更新和错误修复。除了关键修复外,Go 1 后续版本对语言和库所做的更改可能会增加功能,但不会破坏现有的 Go 1 程序。Go 1 兼容性文档更详细地解释了兼容性准则。
Go 1 代表了 Go 当前的使用方式,而不是对语言的彻底重新思考。我们避免设计新功能,而是专注于清理问题和不一致之处,并提高可移植性。我们曾考虑并原型化了一些 Go 语言和包的更改,但主要因为它们重大且向后不兼容而未发布。Go 1 提供了一个机会来发布它们,这对于长期发展是有益的,但这也意味着 Go 1 引入了与旧程序的不兼容性。幸运的是,go fix 工具可以自动完成将程序升级到 Go 1 标准所需的大部分工作。
本文档概述了 Go 1 中影响更新现有代码的程序员的主要更改;其参考点是之前的版本 r60(标记为 r60.3)。它还解释了如何更新 r60 代码以在 Go 1 下运行。
语言变化
Append
append 预声明的可变参数函数使得通过在切片末尾添加元素来增长切片变得容易。常见的用法是在生成输出时向字节切片末尾添加字节。然而,append 没有提供将字符串追加到 []byte 的方法,这是另一个常见情况。
greeting := []byte{}
greeting = append(greeting, []byte("hello ")...)
类比 copy 的类似属性,Go 1 允许将字符串(按字节)直接追加到字节切片,减少了字符串和字节切片之间的摩擦。不再需要转换。
greeting = append(greeting, "world"...)
更新:这是一个新功能,因此现有代码无需更改。
Close
close 预声明函数提供了一种机制,供发送方发出不再发送值的信号。它对通道上的 for range 循环的实现很重要,并且在其他情况下也很有用。部分是出于设计,部分是由于可能发生的竞争条件,它旨在仅供在通道上发送的 goroutine 使用,而不是供接收数据的 goroutine 使用。然而,在 Go 1 之前,没有编译时检查 close 是否正确使用。
为了弥补这一缺陷,至少部分地,Go 1 禁止在只接收通道上使用 close。尝试关闭此类通道是编译时错误。
var c chan int
var csend chan<- int = c
var crecv <-chan int = c
close(c) // legal
close(csend) // legal
close(crecv) // illegal
更新:尝试关闭只接收通道的现有代码即使在 Go 1 之前也是错误的,应该修复。编译器现在将拒绝此类代码。
复合字面量
在 Go 1 中,如果数组、切片或映射类型的复合字面量的元素初始化器是指针类型,则可以省略类型规范。本示例中的所有四个初始化都是合法的;最后一个在 Go 1 之前是非法的。
type Date struct {
month string
day int
}
// Struct values, fully qualified; always legal.
holiday1 := []Date{
Date{"Feb", 14},
Date{"Nov", 11},
Date{"Dec", 25},
}
// Struct values, type name elided; always legal.
holiday2 := []Date{
{"Feb", 14},
{"Nov", 11},
{"Dec", 25},
}
// Pointers, fully qualified, always legal.
holiday3 := []*Date{
&Date{"Feb", 14},
&Date{"Nov", 11},
&Date{"Dec", 25},
}
// Pointers, type name elided; legal in Go 1.
holiday4 := []*Date{
{"Feb", 14},
{"Nov", 11},
{"Dec", 25},
}
更新:此更改对现有代码没有影响,但是对现有源代码应用 gofmt -s 命令,除其他外,将在允许的情况下省略显式元素类型。
init 期间的 Goroutine
旧语言规定在初始化期间执行的 go 语句创建 goroutine,但它们直到整个程序初始化完成才开始运行。这在许多地方引入了笨拙,并实际上限制了 init 构造的实用性:如果另一个包可以在初始化期间使用库,则库被迫避免使用 goroutine。这种设计是为了简单和安全,但是,随着我们对语言的信心增长,它似乎没有必要。在初始化期间运行 goroutine 与在正常执行期间运行它们没有更复杂或更不安全。
在 Go 1 中,使用 goroutine 的代码可以从 init 例程和全局初始化表达式中调用,而不会引入死锁。
var PackageGlobal int
func init() {
c := make(chan int)
go initializationFunction(c)
PackageGlobal = <-c
}
更新:这是一个新功能,因此现有代码无需更改,尽管依赖于 goroutine 在 main 之前不启动的代码可能会中断。标准存储库中没有这样的代码。
rune 类型
语言规范允许 int 类型为 32 或 64 位宽,但当前的实现将 int 设置为 32 位,即使在 64 位平台上也是如此。最好在 64 位平台上将 int 设置为 64 位。(这对大型切片的索引有重要影响。)然而,如果 int 从 32 位增长到 64 位,这将浪费旧语言处理 Unicode 字符时的空间,因为 int 类型也用于存储 Unicode 码点:每个码点将浪费额外的 32 位存储空间。
为了使更改为 64 位 int 可行,Go 1 引入了一个新的基本类型 rune,用于表示单个 Unicode 码点。它是 int32 的别名,类似于 byte 作为 uint8 的别名。
字符字面量,例如 'a'、'語' 和 '\u0345',现在默认类型为 rune,类似于 1.0 默认类型为 float64。因此,除非另有说明,否则初始化为字符常量的变量将具有 rune 类型。
库已更新,在适当的时候使用 rune 而不是 int。例如,函数 unicode.ToLower 及其相关函数现在接受并返回 rune。
delta := 'δ' // delta has type rune.
var DELTA rune
DELTA = unicode.ToUpper(delta)
epsilon := unicode.ToLower(DELTA + 1)
if epsilon != 'δ'+1 {
log.Fatal("inconsistent casing for Greek")
}
更新:大多数源代码不会受到影响,因为 := 初始化器的类型推断会默默地引入新类型,并从那里传播。某些代码可能会出现类型错误,可以通过简单的转换来解决。
error 类型
Go 1 引入了一个新的内置类型 error,其定义如下:
type error interface {
Error() string
}
由于此类型的所有影响都在包库中,因此将在下面讨论。
从映射中删除
在旧语言中,要从映射 m 中删除键为 k 的条目,需要编写以下语句:
m[k] = value, false
这种语法是一个特殊的特例,是唯一的二对一赋值。它需要传递一个被求值但丢弃的值(通常被忽略),以及一个几乎总是常量 false 的布尔值。它完成了任务,但很奇怪且有争议。
在 Go 1 中,该语法已消失;取而代之的是一个新的内置函数 delete。调用:
delete(m, k)
将删除由表达式 m[k] 检索到的映射条目。没有返回值。删除不存在的条目是空操作。
更新:运行 go fix 将把形式为 m[k] = value, false 的表达式转换为 delete(m, k),当明确忽略的值可以安全地从程序中丢弃,并且 false 指的是预定义的布尔常量时。fix 工具将标记语法在其他用途,供程序员检查。
在映射中迭代
旧语言规范没有定义映射的迭代顺序,实际上在不同硬件平台之间存在差异。这导致迭代映射的测试不稳定且不可移植,具有令人不快的特性,即测试可能在一台机器上总是通过,但在另一台机器上却会中断。
在 Go 1 中,当使用 for range 语句迭代映射时,元素的访问顺序被定义为不可预测的,即使相同的循环在相同的映射上运行多次也是如此。代码不应假定元素以任何特定顺序访问。
此更改意味着依赖迭代顺序的代码很可能在成为问题之前就早期中断并得到修复。同样重要的是,它允许映射实现即使在程序使用 range 循环从映射中选择元素时也能确保更好的映射平衡。
m := map[string]int{"Sunday": 0, "Monday": 1}
for name, value := range m {
// This loop should not assume Sunday will be visited first.
f(name, value)
}
更新:这是工具无法提供帮助的一项更改。大多数现有代码不会受到影响,但有些程序可能会中断或出现异常行为;我们建议手动检查所有映射上的 range 语句,以验证它们不依赖迭代顺序。标准存储库中存在一些此类示例;它们已得到修复。请注意,依赖迭代顺序(未指定)已经是错误的。此更改将不可预测性编入规范。
多重赋值
语言规范长期以来一直保证在赋值中,所有右侧表达式都在任何左侧表达式赋值之前进行求值。为了保证可预测的行为,Go 1 进一步完善了规范。
如果赋值语句的左侧包含需要求值的表达式,例如函数调用或数组索引操作,则这些都将使用通常的从左到右规则完成,然后才将值赋给任何变量。一旦所有内容都求值完毕,实际的赋值将按从左到右的顺序进行。
这些示例说明了此行为。
sa := []int{1, 2, 3}
i := 0
i, sa[i] = 1, 2 // sets i = 1, sa[0] = 2
sb := []int{1, 2, 3}
j := 0
sb[j], j = 2, 1 // sets sb[0] = 2, j = 1
sc := []int{1, 2, 3}
sc[0], sc[0] = 1, 2 // sets sc[0] = 1, then sc[0] = 2 (so sc[0] = 2 at end)
更新:这是工具无法提供帮助的一项更改,但发生中断的可能性很小。标准存储库中没有代码因此更改而中断,并且依赖于先前未指定行为的代码已经是错误的。
返回值和影子变量
一个常见的错误是在对与结果变量同名但不是同一个变量的变量赋值后使用 return(不带参数)。这种情况称为影子:结果变量被内部作用域中声明的另一个同名变量所遮蔽。
在具有命名返回值的函数中,Go 1 编译器不允许在返回语句处任何命名返回值被遮蔽的情况下使用不带参数的 return 语句。(这不是规范的一部分,因为这是我们仍在探索的一个领域;情况类似于编译器拒绝不以显式 return 语句结束的函数。)
此函数隐式返回一个被遮蔽的返回值,将被编译器拒绝。
func Bug() (i, j, k int) {
for i = 0; i < 5; i++ {
for j := 0; j < 5; j++ { // Redeclares j.
k += i * j
if k > 100 {
return // Rejected: j is shadowed here.
}
}
}
return // OK: j is not shadowed here.
}
更新:以这种方式遮蔽返回值的代码将被编译器拒绝,需要手动修复。标准存储库中出现的少数情况大多是错误。
复制包含未导出字段的结构体
旧语言不允许一个包复制包含属于不同包的未导出字段的结构体值。但是,方法接收器有一个必需的例外;此外,copy 和 append 的实现从未遵守此限制。
Go 1 将允许包复制包含来自其他包的未导出字段的结构体值。除了解决不一致性之外,此更改还引入了一种新的 API:一个包可以返回不透明值,而无需诉诸指针或接口。time.Time 和 reflect.Value 的新实现是利用此新属性的类型示例。
例如,如果包 p 包含以下定义:
type Struct struct {
Public int
secret int
}
func NewStruct(a int) Struct { // Note: not a pointer.
return Struct{a, f(a)}
}
func (s Struct) String() string {
return fmt.Sprintf("{%d (secret %d)}", s.Public, s.secret)
}
导入 p 的包可以随意赋值和复制 p.Struct 类型的值。在幕后,未导出字段将被赋值和复制,就像它们是导出的一样,但客户端代码永远不会知道它们。代码:
import "p"
myStruct := p.NewStruct(23)
copyOfMyStruct := myStruct
fmt.Println(myStruct, copyOfMyStruct)
将显示结构体的秘密字段已复制到新值。
更新:这是一个新功能,因此现有代码无需更改。
相等性
在 Go 1 之前,该语言没有定义结构体和数组值的相等性。这意味着,除其他外,结构体和数组不能用作映射键。另一方面,Go 定义了函数和映射值的相等性。函数相等性在闭包存在的情况下存在问题(两个闭包何时相等?),而映射相等性比较的是指针,而不是映射的内容,这通常不是用户想要的。
Go 1 解决了这些问题。首先,结构体和数组可以进行相等性和不等性比较(== 和 !=),因此可以用作映射键,前提是它们由也定义了相等性的元素组成,使用逐元素比较。
type Day struct {
long string
short string
}
Christmas := Day{"Christmas", "XMas"}
Thanksgiving := Day{"Thanksgiving", "Turkey"}
holiday := map[Day]bool{
Christmas: true,
Thanksgiving: true,
}
fmt.Printf("Christmas is a holiday: %t\n", holiday[Christmas])
其次,Go 1 除了与 nil 比较之外,取消了函数值的相等性定义。最后,映射相等性也消失了,同样除了与 nil 比较之外。
请注意,切片的相等性仍然未定义,因为计算通常不可行。另请注意,结构体和数组的有序比较运算符(< < = > >=)仍然未定义。
更新:结构体和数组相等性是一个新功能,因此现有代码无需更改。依赖函数或映射相等性的现有代码将被编译器拒绝,需要手动修复。受影响的程序很少,但修复可能需要一些重新设计。
包层次结构
Go 1 解决了旧标准库中的许多缺陷,并清理了许多包,使它们更具内部一致性和可移植性。
本节描述了 Go 1 中包的重新排列方式。有些已移动,有些已重命名,有些已删除。新包将在后续章节中描述。
包层次结构
Go 1 具有重新排列的包层次结构,将相关项分组到子目录中。例如,utf8 和 utf16 现在位于 unicode 的子目录中。此外,一些包已移至 code.google.com/p/go 的子存储库,而其他包已被彻底删除。
| 旧路径 | 新路径 |
|---|---|
| asn1 | encoding/asn1 |
| csv | encoding/csv |
| gob | encoding/gob |
| json | encoding/json |
| xml | encoding/xml |
| exp/template/html | html/template |
| big | math/big |
| cmath | math/cmplx |
| rand | math/rand |
| http | net/http |
| http/cgi | net/http/cgi |
| http/fcgi | net/http/fcgi |
| http/httptest | net/http/httptest |
| http/pprof | net/http/pprof |
| net/mail | |
| rpc | net/rpc |
| rpc/jsonrpc | net/rpc/jsonrpc |
| smtp | net/smtp |
| url | net/url |
| exec | os/exec |
| scanner | text/scanner |
| tabwriter | text/tabwriter |
| template | text/template |
| template/parse | text/template/parse |
| utf8 | unicode/utf8 |
| utf16 | unicode/utf16 |
请注意,旧的 cmath 和 exp/template/html 包的包名已更改为 cmplx 和 template。
更新:运行 go fix 将更新所有仍在标准存储库中的包的导入和包重命名。导入不再在标准存储库中的包的程序需要手动编辑。
exp 包树
由于它们不是标准化的,exp 目录下的包将不会在标准的 Go 1 发行版中提供,尽管它们将以源代码形式在存储库中提供给希望使用它们的开发人员。
Go 1 发布时,有几个包已移至 exp 目录下:
ebnfhtml†go/types
(†EscapeString 和 UnescapeString 类型仍保留在 html 包中。)
所有这些包都以相同的名称提供,前缀为 exp/:exp/ebnf 等。
此外,utf8.String 类型已移至其自己的包 exp/utf8string。
最后,gotype 命令现在位于 exp/gotype 中,而 ebnflint 现在位于 exp/ebnflint 中。如果它们已安装,则现在位于 $GOROOT/bin/tool 中。
更新:使用 exp 中包的代码需要手动更新,或者从具有可用 exp 的安装中编译。go fix 工具或编译器将抱怨此类使用。
旧包树
由于它们已被弃用,old 目录下的包将不会在标准的 Go 1 发行版中提供,尽管它们将以源代码形式提供给希望使用它们的开发人员。
这些包的新位置是:
old/netchan
更新:使用现在位于 old 中的包的代码需要手动更新,或者从具有可用 old 的安装中编译。go fix 工具将警告此类使用。
已删除的包
Go 1 彻底删除了几个包:
container/vectorexp/datafmtgo/typecheckerold/regexpold/templatetry
以及命令 gotry。
更新:使用 container/vector 的代码应更新为直接使用切片。请参阅Go 语言社区 Wiki 获取一些建议。使用其他包的代码(应该几乎没有)需要重新考虑。
迁移到子存储库的包
Go 1 已将许多包移到其他存储库中,通常是主要 Go 存储库的子存储库。此表列出了旧的和新的导入路径:
| 旧路径 | 新路径 |
|---|---|
| crypto/bcrypt | code.google.com/p/go.crypto/bcrypt |
| crypto/blowfish | code.google.com/p/go.crypto/blowfish |
| crypto/cast5 | code.google.com/p/go.crypto/cast5 |
| crypto/md4 | code.google.com/p/go.crypto/md4 |
| crypto/ocsp | code.google.com/p/go.crypto/ocsp |
| crypto/openpgp | code.google.com/p/go.crypto/openpgp |
| crypto/openpgp/armor | code.google.com/p/go.crypto/openpgp/armor |
| crypto/openpgp/elgamal | code.google.com/p/go.crypto/openpgp/elgamal |
| crypto/openpgp/errors | code.google.com/p/go.crypto/openpgp/errors |
| crypto/openpgp/packet | code.google.com/p/go.crypto/openpgp/packet |
| crypto/openpgp/s2k | code.google.com/p/go.crypto/openpgp/s2k |
| crypto/ripemd160 | code.google.com/p/go.crypto/ripemd160 |
| crypto/twofish | code.google.com/p/go.crypto/twofish |
| crypto/xtea | code.google.com/p/go.crypto/xtea |
| exp/ssh | code.google.com/p/go.crypto/ssh |
| image/bmp | code.google.com/p/go.image/bmp |
| image/tiff | code.google.com/p/go.image/tiff |
| net/dict | code.google.com/p/go.net/dict |
| net/websocket | code.google.com/p/go.net/websocket |
| exp/spdy | code.google.com/p/go.net/spdy |
| encoding/git85 | code.google.com/p/go.codereview/git85 |
| patch | code.google.com/p/go.codereview/patch |
| exp/wingui | code.google.com/p/gowingui |
更新:运行 go fix 将更新这些包的导入以使用新的导入路径。依赖这些包的安装需要使用 go get 命令来安装它们。
库的主要更改
本节描述核心库的重大更改,这些更改影响了大多数程序。
错误类型和 errors 包
os.Error 放在 os 包中主要是历史原因:错误最初是在实现 os 包时出现的,当时它们看起来与系统相关。从那时起,很明显错误比操作系统更基础。例如,在 os 依赖的包(如 syscall)中使用 Errors 会很好。此外,将 Error 放在 os 中会引入许多对 os 的依赖,否则这些依赖将不存在。
Go 1 通过引入一个内置的 error 接口类型和一个单独的 errors 包(类似于 bytes 和 strings)来解决这些问题,该包包含实用函数。它用 errors.New 替换了 os.NewError,使错误在环境中占据了更重要的位置。
因此,为了避免广泛使用的 String 方法导致意外满足 error 接口,error 接口改为使用 Error 作为该方法的名称。
type error interface {
Error() string
}
fmt 库会自动调用 Error,就像它已经为 String 所做的那样,以便轻松打印错误值。
type SyntaxError struct {
File string
Line int
Message string
}
func (se *SyntaxError) Error() string {
return fmt.Sprintf("%s:%d: %s", se.File, se.Line, se.Message)
}
所有标准包都已更新为使用新接口;旧的 os.Error 已消失。
一个新包,errors,包含函数:
func New(text string) error
将字符串转换为错误。它替换了旧的 os.NewError。
var ErrSyntax = errors.New("syntax error")
更新:运行 go fix 将更新受此更改影响的几乎所有代码。使用 String 方法定义错误类型的代码需要手动更新,将方法重命名为 Error。
系统调用错误
旧的 syscall 包,它早于 os.Error(以及几乎所有其他东西),将错误作为 int 值返回。反过来,os 包转发了许多这些错误,例如 EINVAL,但每个平台使用不同的错误集。这种行为令人不快且不可移植。
在 Go 1 中,syscall 包改为返回 error 用于系统调用错误。在 Unix 上,实现由 syscall.Errno 类型完成,该类型满足 error 并替换了旧的 os.Errno。
影响 os.EINVAL 及其相关内容的更改在其他地方描述。
更新:运行 go fix 将更新受此更改影响的几乎所有代码。无论如何,大多数代码应该使用 os 包而不是 syscall,因此不会受到影响。
时间
在编程语言中,很好地支持时间总是一个挑战。旧的 Go time 包具有 int64 单位,没有真正的类型安全,也没有区分绝对时间和持续时间。
因此,Go 1 库中最彻底的更改之一是对 time 包的彻底重新设计。不再将纳秒数作为 int64,以及单独的 *time.Time 类型来处理小时和年等人为单位,现在有两种基本类型:time.Time(一个值,所以 * 消失了),它表示一个时间点;和 time.Duration,它表示一个时间间隔。两者都具有纳秒分辨率。Time 可以表示远古过去和遥远未来的任何时间,而 Duration 只能跨越大约 290 年的正负。这些类型上都有方法,还有许多有用的预定义常量持续时间,例如 time.Second。
新方法包括 Time.Add(将 Duration 添加到 Time)和 Time.Sub(减去两个 Time 以产生 Duration)等。
最重要的语义更改是,Unix 纪元(1970 年 1 月 1 日)现在仅与那些提及 Unix 的函数和方法相关:time.Unix 以及 Time 类型的 Unix 和 UnixNano 方法。特别是,time.Now 返回一个 time.Time 值,而不是旧 API 中的自 Unix 纪元以来的整数纳秒计数。
// sleepUntil sleeps until the specified time. It returns immediately if it's too late. func sleepUntil(wakeup time.Time) { now := time.Now() // A Time. if !wakeup.After(now) { return } delta := wakeup.Sub(now) // A Duration. fmt.Printf("Sleeping for %.3fs\n", delta.Seconds()) time.Sleep(delta) }
新类型、方法和常量已通过所有使用时间的标准包(例如 os 及其文件时间戳表示)传播。
更新:go fix 工具将更新许多旧 time 包的使用以使用新类型和方法,尽管它不替换诸如 1e9 之类表示每秒纳秒的值。此外,由于某些值发生类型更改,fix 工具重写的一些表达式可能需要进一步手动编辑;在这种情况下,重写将包括旧功能的正确函数或方法,但可能类型错误或需要进一步分析。
对库的微小更改
本节描述较小的更改,例如那些对不常用包的更改,或者除了运行 go fix 之外只影响少数程序的更改。此类别包括 Go 1 中新增的包。它们共同改进了可移植性,规范了行为,并使接口更加现代化和 Go 风格。
archive/zip 包
在 Go 1 中,*zip.Writer 不再具有 Write 方法。它的存在是一个错误。
更新:受影响的少量代码将被编译器捕获,并且必须手动更新。
bufio 包
在 Go 1 中,bufio.NewReaderSize 和 bufio.NewWriterSize 函数不再因无效大小而返回错误。如果参数大小太小或无效,它将被调整。
更新:运行 go fix 将更新将错误分配给 _ 的调用。未修复的调用将被编译器捕获,并且必须手动更新。
compress/flate、compress/gzip 和 compress/zlib 包
在 Go 1 中,compress/flate、compress/gzip 和 compress/zlib 中的 NewWriterXxx 函数如果接受压缩级别,则都返回 (*Writer, error),否则返回 *Writer。gzip 包的 Compressor 和 Decompressor 类型已重命名为 Writer 和 Reader。flate 包的 WrongValueError 类型已删除。
更新:运行 go fix 将更新旧名称和将错误分配给 _ 的调用。未修复的调用将被编译器捕获,并且必须手动更新。
crypto/aes 和 crypto/des 包
在 Go 1 中,Reset 方法已被删除。Go 不保证内存不会被复制,因此此方法具有误导性。
特定于密码的类型 *aes.Cipher、*des.Cipher 和 *des.TripleDESCipher 已被删除,取而代之的是 cipher.Block。
更新:删除对 Reset 的调用。将特定密码类型的使用替换为 cipher.Block。
crypto/elliptic 包
在 Go 1 中,elliptic.Curve 已被设计为接口,以允许替代实现。曲线参数已移至 elliptic.CurveParams 结构。
更新:*elliptic.Curve 的现有用户需要更改为简单的 elliptic.Curve。对 Marshal、Unmarshal 和 GenerateKey 的调用现在是 crypto/elliptic 中的函数,它们将 elliptic.Curve 作为第一个参数。
crypto/hmac 包
在 Go 1 中,哈希特定函数(例如 hmac.NewMD5)已从 crypto/hmac 中删除。相反,hmac.New 接受一个返回 hash.Hash 的函数,例如 md5.New。
更新:运行 go fix 将执行所需的更改。
crypto/x509 包
在 Go 1 中,crypto/x509 中的 CreateCertificate 函数和 CreateCRL 方法已更改为接受 interface{},而它们以前接受 *rsa.PublicKey 或 *rsa.PrivateKey。这将允许将来实现其他公钥算法。
更新:无需更改。
encoding/binary 包
在 Go 1 中,binary.TotalSize 函数已被 Size 替换,该函数接受 interface{} 参数而不是 reflect.Value。
更新:受影响的少量代码将被编译器捕获,并且必须手动更新。
encoding/xml 包
在 Go 1 中,xml 包的设计已更接近于其他编组包,例如 encoding/gob。
旧的 Parser 类型已重命名为 Decoder 并具有新的 Decode 方法。还引入了 Encoder 类型。
函数 Marshal 和 Unmarshal 现在与 []byte 值一起工作。要使用流,请使用新的 Encoder 和 Decoder 类型。
在编组或解组值时,字段标签中支持的标志格式已更改,更接近于 json 包(`xml:"name,flag"`)。字段标签、字段名称以及 XML 属性和元素名称之间的匹配现在区分大小写。XMLName 字段标签(如果存在)也必须与要编组的 XML 元素的名称匹配。
更新:运行 go fix 将更新包的大多数用法,除了对 Unmarshal 的一些调用。必须特别注意字段标签,因为 fix 工具不会更新它们,如果手动不修复,在某些情况下它们会默默地出现错误行为。例如,旧的 "attr" 现在写为 ",attr",而纯粹的 "attr" 仍然有效但含义不同。
expvar 包
在 Go 1 中,RemoveAll 函数已被删除。Iter 函数和 *Map 上的 Iter 方法已被 Do 和 (*Map).Do 替换。
更新:大多数使用 expvar 的代码无需更改。使用 Iter 的罕见代码可以更新为将闭包传递给 Do 以实现相同的效果。
flag 包
在 Go 1 中,flag.Value 接口略有更改。Set 方法现在返回一个 error 而不是 bool 来指示成功或失败。
还有一种新型的标志,Duration,用于支持指定时间间隔的参数值。此类标志的值必须给定单位,就像 time.Duration 格式化它们一样:10s、1h30m 等。
var timeout = flag.Duration("timeout", 30*time.Second, "how long to wait for completion")
更新:实现自己标志的程序需要进行小的手动修复以更新其 Set 方法。Duration 标志是新的,不影响任何现有代码。
go/* 包
go 下的几个包的 API 略有修改。
在 go/scanner、go/parser、go/printer 和 go/doc 包中引入了一个具体的 Mode 类型,用于配置模式标志。
AllowIllegalChars 和 InsertSemis 模式已从 go/scanner 包中删除。它们主要用于扫描除 Go 源文件以外的文本。相反,text/scanner 包应为此目的使用。
提供给扫描器 Init 方法的 ErrorHandler 现在只是一个函数,而不是一个接口。ErrorVector 类型已被删除,取而代之的是(现有的)ErrorList 类型,并且 ErrorVector 方法已迁移。客户端不再将 ErrorVector 嵌入到扫描器中,现在客户端应该维护一个 ErrorList。
go/parser 包提供的解析函数集已减少到主要的解析函数 ParseFile,以及几个便利函数 ParseDir 和 ParseExpr。
go/printer 包支持附加的配置模式 SourcePos;如果设置,打印机将发出 //line 注释,以便生成的输出包含原始源代码位置信息。新类型 CommentedNode 可用于提供与任意 ast.Node 关联的注释(到目前为止,只有 ast.File 携带注释信息)。
go/doc 包的类型名称通过删除 Doc 后缀进行了简化:PackageDoc 现在是 Package,ValueDoc 是 Value 等。此外,所有类型现在都一致地具有 Name 字段(或 Names,在 Value 类型的情况下)和 Type.Factories 已变为 Type.Funcs。现在,创建一个包的文档不再调用 doc.NewPackageDoc(pkg, importpath),而是使用:
doc.New(pkg, importpath, mode)
其中新的 mode 参数指定操作模式:如果设置为 AllDecls,则考虑所有声明(而不仅仅是导出的)。函数 NewFileDoc 已删除,函数 CommentText 已成为 ast.CommentGroup 的方法 Text。
在 go/token 包中,token.FileSet 方法 Files(最初返回一个 *token.File 通道)已被迭代器 Iterate 替换,该迭代器接受一个函数参数。
在 go/build 包中,API 几乎完全被替换。该包仍然计算 Go 包信息,但它不再运行构建:Cmd 和 Script 类型已消失。(要构建代码,请改用新的 go 命令。)DirInfo 类型现在名为 Package。FindTree 和 ScanDir 被 Import 和 ImportDir 替换。
更新:使用 go 中包的代码必须手动更新;编译器将拒绝不正确的用法。与任何 go/doc 类型一起使用的模板可能需要手动修复;重命名的字段将导致运行时错误。
hash 包
在 Go 1 中,hash.Hash 的定义包含一个新方法 BlockSize。这个新方法主要用于加密库。
hash.Hash 接口的 Sum 方法现在接受一个 []byte 参数,哈希值将追加到该参数中。可以通过向调用添加 nil 参数来重新创建以前的行为。
更新:hash.Hash 的现有实现需要添加 BlockSize 方法。逐字节处理输入的哈希可以实现 BlockSize 以返回 1。运行 go fix 将更新对 hash.Hash 的各种实现的 Sum 方法的调用。
更新:由于该包的功能是新的,因此无需更新。
http 包
在 Go 1 中,http 包进行了重构,将一些实用程序放入 httputil 子目录中。这些部分很少被 HTTP 客户端需要。受影响的项目是:
- ClientConn
- DumpRequest
- DumpRequestOut
- DumpResponse
- NewChunkedReader
- NewChunkedWriter
- NewClientConn
- NewProxyClientConn
- NewServerConn
- NewSingleHostReverseProxy
- ReverseProxy
- ServerConn
Request.RawURL 字段已被删除;它是一个历史遗留物。
Handle 和 HandleFunc 函数,以及 ServeMux 中同名的方法,现在如果尝试两次注册相同的模式,则会 panic。
更新:运行 go fix 将更新受影响的少数程序,除了 RawURL 的使用,这必须手动修复。
image 包
image 包进行了一些小的更改、重新排列和重命名。
大多数颜色处理代码已移至其自己的包 image/color。对于移动的元素,出现了对称性;例如,image.RGBA 的每个像素都是 color.RGBA。
旧的 image/ycbcr 包已合并到 image 和 image/color 包中,并进行了一些重命名。
旧的 image.ColorImage 类型仍在 image 包中,但已重命名为 image.Uniform,而 image.Tiled 已被删除。
此表列出了重命名。
| 旧路径 | 新路径 |
|---|---|
| image.Color | color.Color |
| image.ColorModel | color.Model |
| image.ColorModelFunc | color.ModelFunc |
| image.PalettedColorModel | color.Palette |
| image.RGBAColor | color.RGBA |
| image.RGBA64Color | color.RGBA64 |
| image.NRGBAColor | color.NRGBA |
| image.NRGBA64Color | color.NRGBA64 |
| image.AlphaColor | color.Alpha |
| image.Alpha16Color | color.Alpha16 |
| image.GrayColor | color.Gray |
| image.Gray16Color | color.Gray16 |
| image.RGBAColorModel | color.RGBAModel |
| image.RGBA64ColorModel | color.RGBA64Model |
| image.NRGBAColorModel | color.NRGBAModel |
| image.NRGBA64ColorModel | color.NRGBA64Model |
| image.AlphaColorModel | color.AlphaModel |
| image.Alpha16ColorModel | color.Alpha16Model |
| image.GrayColorModel | color.GrayModel |
| image.Gray16ColorModel | color.Gray16Model |
| ycbcr.RGBToYCbCr | color.RGBToYCbCr |
| ycbcr.YCbCrToRGB | color.YCbCrToRGB |
| ycbcr.YCbCrColorModel | color.YCbCrModel |
| ycbcr.YCbCrColor | color.YCbCr |
| ycbcr.YCbCr | image.YCbCr |
| ycbcr.SubsampleRatio444 | image.YCbCrSubsampleRatio444 |
| ycbcr.SubsampleRatio422 | image.YCbCrSubsampleRatio422 |
| ycbcr.SubsampleRatio420 | image.YCbCrSubsampleRatio420 |
| image.ColorImage | image.Uniform |
image 包的 New 函数(NewRGBA、NewRGBA64 等)将 image.Rectangle 作为参数,而不是四个整数。
最后,有新的预定义 color.Color 变量 color.Black、color.White、color.Opaque 和 color.Transparent。
更新:运行 go fix 将更新受此更改影响的几乎所有代码。
log/syslog 包
在 Go 1 中,syslog.NewLogger 函数返回一个错误以及一个 log.Logger。
更新:受影响的少量代码将被编译器捕获,并且必须手动更新。
mime 包
在 Go 1 中,mime 包的 FormatMediaType 函数已简化,使其与 ParseMediaType 保持一致。它现在接受 "text/html" 而不是 "text" 和 "html"。
更新:受影响的少量代码将被编译器捕获,并且必须手动更新。
net 包
在 Go 1 中,各种 SetTimeout、SetReadTimeout 和 SetWriteTimeout 方法已被 SetDeadline、SetReadDeadline 和 SetWriteDeadline 替换。新方法不是接受一个纳秒超时值应用于连接上的任何活动,而是设置一个绝对截止时间(作为 time.Time 值),在此之后读写将超时并且不再阻塞。
还有新的函数 net.DialTimeout,用于简化网络地址拨号超时,以及 net.ListenMulticastUDP,允许多播 UDP 在多个侦听器之间并发侦听。net.ListenMulticastUDP 函数替换了旧的 JoinGroup 和 LeaveGroup 方法。
更新:使用旧方法的代码将无法编译,并且必须手动更新。语义更改使得 fix 工具难以自动更新。
os 包
Time 函数已被删除;调用者应使用 time 包中的 Time 类型。
Exec 函数已被删除;调用者应使用 syscall 包中的 Exec(如果可用)。
ShellExpand 函数已重命名为 ExpandEnv。
NewFile 函数现在接受 uintptr fd,而不是 int。文件上的 Fd 方法现在也返回 uintptr。
os 包中不再有诸如 EINVAL 这样的错误常量,因为值集随底层操作系统而异。有新的可移植函数,如 IsPermission 来测试常见的错误属性,以及一些具有更 Go 风格名称的新错误值,例如 ErrPermission 和 ErrNotExist。
Getenverror 函数已被删除。要区分不存在的环境变量和空字符串,请使用 os.Environ 或 syscall.Getenv。
Process.Wait 方法已取消其选项参数,并且相关常量已从包中删除。此外,函数 Wait 已消失;只有 Process 类型的方法仍然存在。
Process.Wait 返回的 Waitmsg 类型已替换为更可移植的 ProcessState 类型,该类型具有访问器方法以恢复有关进程的信息。由于 Wait 的更改,ProcessState 值始终描述一个已退出的进程。可移植性问题以其他方式简化了接口,但 ProcessState.Sys 和 ProcessState.SysUsage 方法返回的值可以类型断言为底层系统特定数据结构,例如 Unix 上的 syscall.WaitStatus 和 syscall.Rusage。
更新:运行 go fix 将删除 Process.Wait 的零参数。所有其他更改将被编译器捕获,并且必须手动更新。
os.FileInfo 类型
Go 1 重新定义了 os.FileInfo 类型,将其从结构体更改为接口:
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes
Mode() FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() interface{} // underlying data source (can return nil)
}
文件模式信息已移至名为 os.FileMode 的子类型,这是一种简单的整数类型,具有 IsDir、Perm 和 String 方法。
文件模式和属性(例如 Unix 上的 i-number)的系统特定详细信息已从 FileInfo 中完全删除。相反,每个操作系统的 os 包都提供了 FileInfo 接口的实现,该接口有一个 Sys 方法,返回文件元数据的系统特定表示。例如,要查找 Unix 系统上文件的 i-number,请像这样解包 FileInfo:
fi, err := os.Stat("hello.go")
if err != nil {
log.Fatal(err)
}
// Check that it's a Unix file.
unixStat, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
log.Fatal("hello.go: not a Unix file")
}
fmt.Printf("file i-number: %d\n", unixStat.Ino)
假设(这是不明智的)"hello.go" 是一个 Unix 文件,i-number 表达式可以缩写为:
fi.Sys().(*syscall.Stat_t).Ino
FileInfo 的绝大多数用途只需要标准接口的方法。
os 包不再包含 POSIX 错误(例如 ENOENT)的包装器。对于少数需要验证特定错误条件的程序,现在有布尔函数 IsExist、IsNotExist 和 IsPermission。
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if os.IsExist(err) {
log.Printf("%s already exists", name)
}
更新:运行 go fix 将更新使用当前 os.FileInfo 和 os.FileMode API 的旧代码。需要系统特定文件详细信息的代码需要手动更新。使用 os 包中旧 POSIX 错误值的代码将无法编译,并且也需要手动更新。
os/signal 包
Go 1 中的 os/signal 包将 Incoming 函数(该函数返回一个接收所有传入信号的通道)替换为选择性的 Notify 函数,后者请求在现有通道上传递特定信号。
更新:代码必须手动更新。字面翻译:
c := signal.Incoming()
是:
c := make(chan os.Signal, 1)
signal.Notify(c) // ask for all signals
但大多数代码应该列出它想要处理的特定信号:
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT)
path/filepath 包
在 Go 1 中,path/filepath 包的 Walk 函数已更改为接受 WalkFunc 类型的函数值,而不是 Visitor 接口值。WalkFunc 统一了文件和目录的处理。
type WalkFunc func(path string, info os.FileInfo, err error) error
即使无法打开文件或目录,也会调用 WalkFunc 函数;在这种情况下,错误参数将描述失败。如果目录的内容要跳过,函数应返回 filepath.SkipDir 值。
markFn := func(path string, info os.FileInfo, err error) error {
if path == "pictures" { // Will skip walking of directory pictures and its contents.
return filepath.SkipDir
}
if err != nil {
return err
}
log.Println(path)
return nil
}
err := filepath.Walk(".", markFn)
if err != nil {
log.Fatal(err)
}
更新:此更改简化了大多数代码,但具有微妙的后果,因此受影响的程序需要手动更新。编译器将捕获使用旧接口的代码。
regexp 包
regexp 包已重写。它具有相同的接口,但它支持的正则表达式规范已从旧的“egrep”形式更改为 RE2 的形式。
更新:使用该包的代码应手动检查其正则表达式。
runtime 包
在 Go 1 中,runtime 包导出的许多 API 已被删除,取而代之的是其他包提供的功能。使用 runtime.Type 接口或其特定具体类型实现的代码现在应使用 reflect 包。使用 runtime.Semacquire 或 runtime.Semrelease 的代码应使用通道或 sync 包中的抽象。runtime.Alloc、runtime.Free 和 runtime.Lookup 函数(一个用于调试内存分配器的不安全 API)没有替代品。
以前,runtime.MemStats 是一个全局变量,保存内存分配的统计信息,并且调用 runtime.UpdateMemStats 可以确保它是最新的。在 Go 1 中,runtime.MemStats 是一个结构体类型,代码应该使用 runtime.ReadMemStats 来获取当前统计信息。
该包添加了一个新函数 runtime.NumCPU,它返回操作系统内核报告的可用于并行执行的 CPU 数量。其值可以用于设置 GOMAXPROCS。runtime.Cgocalls 和 runtime.Goroutines 函数已重命名为 runtime.NumCgoCall 和 runtime.NumGoroutine。
更新:运行 go fix 将更新函数重命名的代码。其他代码需要手动更新。
strconv 包
在 Go 1 中,strconv 包经过了显著的重写,使其更具 Go 风格,而不再是 C 风格,尽管 Atoi 仍然存在(它类似于 int(ParseInt(x, 10, 0))),Itoa(x) 也是如此(FormatInt(int64(x), 10))。还有一些函数的新变体,它们将数据追加到字节切片而不是返回字符串,以允许控制分配。
此表总结了重命名;有关完整详细信息,请参阅包文档。
| 旧调用 | 新调用 |
|---|---|
| Atob(x) | ParseBool(x) |
| Atof32(x) | ParseFloat(x, 32)§ |
| Atof64(x) | ParseFloat(x, 64) |
| AtofN(x, n) | ParseFloat(x, n) |
| Atoi(x) | Atoi(x) |
| Atoi(x) | ParseInt(x, 10, 0)§ |
| Atoi64(x) | ParseInt(x, 10, 64) |
| Atoui(x) | ParseUint(x, 10, 0)§ |
| Atoui64(x) | ParseUint(x, 10, 64) |
| Btoi64(x, b) | ParseInt(x, b, 64) |
| Btoui64(x, b) | ParseUint(x, b, 64) |
| Btoa(x) | FormatBool(x) |
| Ftoa32(x, f, p) | FormatFloat(float64(x), f, p, 32) |
| Ftoa64(x, f, p) | FormatFloat(x, f, p, 64) |
| FtoaN(x, f, p, n) | FormatFloat(x, f, p, n) |
| Itoa(x) | Itoa(x) |
| Itoa(x) | FormatInt(int64(x), 10) |
| Itoa64(x) | FormatInt(x, 10) |
| Itob(x, b) | FormatInt(int64(x), b) |
| Itob64(x, b) | FormatInt(x, b) |
| Uitoa(x) | FormatUint(uint64(x), 10) |
| Uitoa64(x) | FormatUint(x, 10) |
| Uitob(x, b) | FormatUint(uint64(x), b) |
| Uitob64(x, b) | FormatUint(x, b) |
更新:运行 go fix 将更新受此更改影响的几乎所有代码。
§ Atoi 仍然存在,但 Atoui 和 Atof32 不存在,因此它们可能需要手动添加的强制转换;go fix 工具将对此发出警告。
template 包
template 和 exp/template/html 包已移至 text/template 和 html/template。更重要的是,这些包的接口已简化。模板语言相同,但“模板集”的概念已消失,包的函数和方法也相应地发生了变化,通常是通过消除。
Template 对象不再是集合,而是可以包含多个命名模板定义,实际上为模板调用构建了命名空间。模板可以调用与其关联的任何其他模板,但只能调用与其关联的模板。关联模板的最简单方法是将它们一起解析,这在新包结构下变得更容易。
更新:导入将由 fix 工具更新。单模板使用基本上不受影响。使用多个模板协同工作的代码需要手动更新。text/template 文档中的示例可以提供指导。
testing 包
testing 包有一个类型 B,作为参数传递给基准测试函数。在 Go 1 中,B 有新方法,类似于 T 的方法,可以启用日志记录和故障报告。
func BenchmarkSprintf(b *testing.B) {
// Verify correctness before running benchmark.
b.StopTimer()
got := fmt.Sprintf("%x", 23)
const expect = "17"
if expect != got {
b.Fatalf("expected %q; got %q", expect, got)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
fmt.Sprintf("%x", 23)
}
}
更新:现有代码不受影响,尽管使用 println 或 panic 的基准测试应该更新为使用新方法。
testing/script 包
testing/script 包已被删除。它是一个遗留物。
更新:几乎没有代码会受到影响。
unsafe 包
在 Go 1 中,函数 unsafe.Typeof、unsafe.Reflect、unsafe.Unreflect、unsafe.New 和 unsafe.NewArray 已被删除;它们复制了 reflect 包提供的更安全的功能。
更新:使用这些函数的代码必须重写以使用 reflect 包。对 encoding/gob 和 协议缓冲区库 的更改可能有助于作为示例。
url 包
在 Go 1 中,url.URL 类型中的几个字段已被删除或替换。
String 方法现在可预测地使用 URL 的所有字段(如有必要)重建编码的 URL 字符串。生成的字符串也不再对密码进行转义。
Raw 字段已被删除。在大多数情况下,可以使用 String 方法代替。
旧的 RawUserinfo 字段由 User 字段替换,类型为 *net.Userinfo。可以使用新的 net.User 和 net.UserPassword 函数创建此类型的值。EscapeUserinfo 和 UnescapeUserinfo 函数也已消失。
RawAuthority 字段已被删除。相同的信息可在 Host 和 User 字段中获取。
RawPath 字段和 EncodedPath 方法已被删除。带根 URL(后跟斜杠的 scheme)中的路径信息现在仅在 Path 字段中以解码形式提供。有时,可能需要编码数据才能获取在解码过程中丢失的信息。这些情况必须通过访问构建 URL 的数据来处理。
非带根路径的 URL,例如 "mailto:dev@golang.org?subject=Hi",也以不同方式处理。OpaquePath 布尔字段已被删除,并引入了一个新的 Opaque 字符串字段来保存此类 URL 的编码路径。在 Go 1 中,引用的 URL 解析为:
URL{
Scheme: "mailto",
Opaque: "dev@golang.org",
RawQuery: "subject=Hi",
}
URL 中添加了一个新的 RequestURI 方法。
ParseWithReference 函数已重命名为 ParseWithFragment。
更新:使用旧字段的代码将无法编译,并且必须手动更新。语义更改使得 fix 工具难以自动更新。
go 命令
Go 1 引入了 go 命令,这是一个用于获取、构建和安装 Go 包和命令的工具。go 命令废弃了 makefile,而是使用 Go 源代码来查找依赖项并确定构建条件。大多数现有 Go 程序将不再需要 makefile 来构建。
请参阅 如何编写 Go 代码 获取 go 命令的入门教程,并参阅 go 命令文档 获取完整详细信息。
更新:依赖 Go 项目旧的基于 makefile 的构建基础设施(Make.pkg、Make.cmd 等)的项目应切换到使用 go 命令来构建 Go 代码,并且如有必要,重写其 makefile 以执行任何辅助构建任务。
cgo 命令
在 Go 1 中,cgo 命令使用不同的 _cgo_export.h 文件,该文件是为包含 //export 行的包生成的。_cgo_export.h 文件现在以 C 前导注释开头,以便导出的函数定义可以使用在那里定义的类型。这导致前导被多次编译,因此使用 //export 的包不能将函数定义或变量初始化放在 C 前导中。
打包发布
与 Go 1 相关的最重要变化之一是预打包、可下载发行版的可用性。它们适用于许多架构和操作系统组合(包括 Windows),并且列表将不断增长。安装详情在入门页面上描述,而发行版本身则在下载页面上列出。