受谢大邀请,去年在负责翻译《Go in Action》一书。上周末,这本书的中文版《Go语言实战》终于上架开卖了,可以在这里,或者去各大电商购买。

说起来这本书的翻译过程还很波折。最开始是谢大邀我一起合译,后来谢大因为工作忙,就全部交由我来翻译。我当时因为正好没啥事情,翻译的速度还挺快的。不过翻译完成后,编辑迟迟没有把书出版。后来过了一段时间才跟我说,书被选为优秀译书,要进一步编辑加工。于是又经历了大概辆三轮和编辑往复敲定所有译文细节,而且文风大变,由原来的非常口语化的译文改为了略微正式的译文,同时对很多术语做了纠正。很感谢编辑在这个过程中指出很多我很随意的译文,也让我有机会能更深入的理解文章,并正确翻译。

这本书对Go的很多语义做了更深入的探讨,比如Interface,比如并发,比如测试打包。对于想进阶的Go使用者来说,可以读一读这本书,理解一些语法背后的理论。

这里想再讨论一下最近的Rust语言。

最近看了一下Rust。这个语言的目标非常宏大:在不引入执行期代价的情况下在语法层面解决资源自动回收的问题。传统上解决资源回收问题,都是通过GC来完成的。这个做法最大的问题在于对程序加入了不小的额外负担。Go这几个版本的迭代都在加强Gc,尽量减小Gc对程序逻辑的影响。目前Go 1.8能够做到并行Gc停世界的时间在100ms内,这已经是个非常好的Gc了,但是依然有影响。Rust则是通过对所有资源引入明确的所有者,在编译期自动追踪资源是否不再有所有者,并在适当的时候插入资源释放代码。

不过Rust为了达到这个目的,不得不让程序员手动控制更多的概念。比如在所有者在栈分配的资源上定义的概念,但是遇到堆分配,就不得不再多引入Box,Rc,Weak三个概念。当遇到多线程的时候,又要引入Arc这个概念。同时还要在函数层面明确引入生命周期这个概念来让程序员手动控制所有者的关联关系。比如对于一个输入是两个int的引用,返回是一个int引用的几个函数:

1
2
3
4
fn f1<'a>(a: &'a int, b: &'a int) -> &'a int {}
fn f2<'a, 'b>(a &'a int, b: &'b int) -> &'a int{}
fn f3<'a, 'b>(a &'a int, b: &'b int) -> &'b int{}
fn f4<'a, 'b, 'c>(a &'a int, b: &'b int) -> &'c int{}

这几个函数是完全不同的函数,原因在于输入值和返回值的生命周期是不一致的。这种手动控制生命周期,在我感觉甚至是程序语言的一种倒退:现代程序语言出现时的第一个目的就是为了解决汇编需要手动管理生命周期的问题。而生命周期配合所有权转移,一定会让程序编写变得非常困难,甚至让程序无法表达一些正常的行为。

不过Rust对于并发倒是十分深思熟虑。Rust希望利用所有权来实现并发安全:并发开始时要么转移资源所有权,要么资源是静态的;并发过程中,要么是用特殊的行为跨执行单元转移所有权,要么使用Arc配合锁来访问资源。而且这套规则完全可以通过编译器在编译时进行保障。具体可以参见Rust的这篇文章。由此也可以给Go一个提示:在需要传递资源所有权的时候,使用Chan;在共享访问资源的时候,使用Locker。

不过Rust目前对并发的支持只到此为止,并没有成熟的官方库来真正实现并发。所以现在用Rust写并发并不像Go一样可以拿来就用。

所以,最后我跑去看Haskell了。毕竟Haskell有现在最成熟的类型系统。“其他语言所谓的新特性,只不过是Haskell里已经实现的功能而已”。

我会继续关注Rust。今年Rust社区会以易用为目标。拭目以待。Go依旧是将工程,性能,开发门槛平衡的很好的语言。