新智元报道
来源:go.googlesource.com
编译:三石
Go语言的开发者正着手准备开发2.0版本,并从以下三个方面发布了初步的设计方案(非官方正式版),以供社区开展讨论:
Go 2.0的总体目标是解决无法扩展到大型代码库以及无法满足大型项目开发人员需求等问题。
泛型
改进目标
想必大多数用户都对Go语言的泛型会表示无奈,很多网友甚至会说“根本就没有泛型支持”。
Go 2.0的目标是通过允许带有类型参数的参数多态(parametric polymorphism)来解决编写Go库的问题。
除了预期的容器类型之外,还希望能够编写有意义的库来操作任意的map和channel值,并理想地编写能够同时操作[ ]byte和string值的多态函数。
Go的泛型必须明确记录对类型参数的约束,作为调用者和实现之间明确的强制协议。当调用者不满足这些约束或实现超出限制时,编译器需将错误清楚地报告出来。
Go中的多态性应该在编译和运行时都可以实现,这样,有关实现策略的决策就可以留给编译器来决定。这种灵活性将解决Go目前存在的一些难题。
草案设计
设计草案添加了一个新的语法,用于在类型或函数声明中引入类型参数列表,例如:
1type List(type T) []T
2
3func Keys(type K, V)(m map[K]V) []K
4
参数化声明的使用,采用普通调用语法来提供类型参数:
1var ints List(int)
2
3keys := Keys(int, string)(map[int]string{1:"one", 2: "two"})
这些示例中的概括不需要T,K和V类型:任何类型都可以。 通常,实现可能需要约束可以使用的类型。例如,我们可能想要定义一个Set(T),以列表或映射的形式实现,在这种情况下,类型T的值必须能够进行相等的比较。为了表达这一点,设计草案引入了contract的概念。contract就像一个函数体,说明了类型必须支持的操作。 例如,要声明类型T的值必须是可比较的:
1contract Equal(t T) {
2 t == t
3}
错误处理
改进目标
Go 语言的错误处理是基于明确的目的而设计的。用户应该从函数中返回所有可能的错误,并且检查/处理这些返回值。和其他语言相比,这一点可能看起来有些繁琐和不人性化。
Go 2希望错误检查更加轻量级,减少用于错误检查的Go程序文本的数量。
还希望使编写错误处理变得更方便,从而提高程序员花时间处理错误的可能性。
且错误检查和错误处理必须保持显式,即在程序文本中可见。
草案设计
草案设计引入了两种新的句法形式。
首先,它引入一个检查表达式来检查f(x, y, z)或检查err,并标记一个显式错误检查。
其次,它引入了一个定义错误处理程序的handle语句。当错误检查失败时,它将控制转移到最内层处理程序,该处理程序将控制转移到它上面的下一个处理程序,以此类推,直到处理程序执行返回语句为止。例如:
1func CopyFile(src, dst string) error {
2 handle err {
3 return fmt.Errorf("copy %s %s: %v", src, dst, err)
4 }
5
6 r := check os.Open(src)
7 defer r.Close()
8
9 w := check os.Create(dst)
10 handle err {
11 w.Close()
12 os.Remove(dst) // (only if a check fails)
13 }
14
15 check io.Copy(w, r)
16 check w.Close()
17 return nil
18}
在不返回错误的函数中允许check/handle组合。例如,一下是一个有用却很简单的程序功能:
1func main() {
2 hex, err := ioutil.ReadAll(os.Stdin)
3 if err != nil {
4 log.Fatal(err)
5 }
6
7 data, err := parseHexdump(string(hex))
8 if err != nil {
9 log.Fatal(err)
10 }
11
12 os.Stdout.Write(data)
13}
这么写会更简单、清晰:
1func main() {
2 handle err {
3 log.Fatal(err)
4 }
5
6 hex := check ioutil.ReadAll(os.Stdin)
7 data := check parseHexdump(string(hex))
8 os.Stdout.Write(data)
9}
错误值语义
改进目标
也许用户对于Go的程序化的err有许多问题:这是一个 RPCError吗?这是net.OpError吗?它适应net.Error的接口吗?这是os.PathError吗?
对于错误值,第一个问题,就是很难回答上述那些疑问。函数os.IsExist,os.IsNotExist,os.IsPermission和os.IsTimeout是主要问题。它们在通用性方面有两个缺陷:每个函数仅测试一种特定类型的错误,第二,每个函数只能理解非常有限数量的包类型。
第二个问题看似没什么,却也很重要:深度嵌套错误(nested error)的报告太难以阅读,并且没有留给额外的细节空间,比如程序中的相关文件位置。
针对上述存在的两个问题,Go 2首先希望能让程序的错误检查更容易,更不容易出错,以提高实际程序的错误处理和鲁棒性。其次,希望能够以标准格式打印带有附加细节的错误。
草案设计
这里有两个主要问题:错误检查和错误格式化,分别用两个不同的方案解决。需要保持与现有代码的互操作性,并允许包继续定义自身的错误类型的约束,指向定义错误实现可以满足的可选界面。
错误检查(Error inspection)
对于错误检查,设计草案遵循现有包(如github.com/pkg/errors)的规则,并为错误定义了一个可选接口,以返回错误包装链中的下一个错误:
1package errors
2
3type Wrapper interface {
4 Unwrap() error
5}
例如,上面假设的WriteError需要:
1func (e *WriteError) Unwrap() error { return e.Err }
利用这种方法,方案设计中添加了两个新函数对错误打包:
1// Is reports whether err or any of the errors in its chain is equal to target.
2func Is(err, target error) bool
3
4// As checks whether err or any of the errors in its chain is a value of type E.
5// If so, it returns the discovered value of type E, with ok set to true.
6// If not, it returns the zero value of type E, with ok set to false.
7func As(type E)(err error) (e E, ok bool)
8
错误格式(Error formatting)
对于错误格式,设计草案定义了根据错误来实现的可选接口:
1package errors
2
3type Formatter interface {
4 Format(p Printer) (next error)
5}