当前位置: 首页 > 科技观察

Rust开发者对Go的初体验

时间:2023-03-20 17:25:26 科技观察

过去几周我一直在用Go编写程序。这是我第一次在大型重要项目中使用Go。在研究Rust的特性时,我还阅读了很多关于Go的内容,包括尝试示例和编写玩具程序。但实际上用它编程是一种完全不同的体验。我认为写下这段经历会很有趣。在这篇文章中,我将尽量避免过多地比较Go和Rust,但由于我正在从Rust转向Go,所以不可避免地会包含一些比较。应该预先警告的是,我更倾向于Rust方面,但会尽量保持客观。总体印象Go编程感觉很棒。库程序有我想要的一切,整体实现比较完整。学习体验也很顺利,不得不说Go是一门精心设计的实用语言。举个例子:一旦你了解了Go的语法,你就可以将其他语言的习语带入Go中。一旦你学习了一些Go,推测Go语言的其他特性就相对容易了。凭借其他语言的一些知识,我无需太多搜索(谷歌)就能阅读和理解Go代码。与C/C++、Java、Python等相比,Go没有那么多痛点,而且生产力更高。然而,它仍然与这些语言同时代。虽然它从其他语言中吸取了一些教训,甚至我个人认为它可能是那个时代最好的语言,但它绝对仍然是那个时代的语言。这是一个增量改进,而不是一个新的改进(需要说明的是,这并不是对其价值的批判,从软件工程的角度来看,增量改进通常会有很好的效果)。一个很好的例子是nil:像Rust和Swift这样的语言已经删除了null的概念,以及与之相关的所有错误类。Go降低了一些风险:没有空值,区分nil和0。但它的核心思想没有改变,同样会出现解引用空指针等常见的运行时错误。易于学习围棋非常容易学习。我知道人们经常吹捧这一点,但我对我工作效率的飞速提高感到非常震惊。感谢Go语言及其文档和工具,我能够在短短两天内编写出“有价值的”可提交代码。有几个因素有助于学习:Go是紧凑的。很多语言都试图看起来很小,但Go确实提供了(这基本上是一件好事,我对自律印象深刻)。标准库非常好(再次,小)。从生态系统中查找和使用图书馆程序非常容易。几乎没有其他语言无法提供的东西。Go从其他现有语言中汲取了很多东西,对其进行了改进,并将它们很好地组合在一起。它竭尽全力避免非传统。样板代码Go代码很快就会变得非常重复。这是由于缺少用于减少重复的宏或泛型(接口有利于抽象,但对于减少代码重复不是很好)。我最终写了很多除了类型之外都相同的函数。错误处理也可能导致重复。在许多函数中,像iferr!=nil{returnerr}这样的样板代码甚至比实际有价值的代码还要多。使用泛型或宏来减少样板代码有时会受到批评,理由是代码不应以牺牲可读性为代价而易于编写。我发现Go提供的恰恰相反,复制和粘贴代码通常又快又容易,但阅读代码可能会令人沮丧,因为你必须忽略很多不相关的代码,或者在很多相同的代码中找到细微的差异。编译时间:绝对快,绝对比Rust快很多。但实际上,它并没有我想象的那么快(对于中大型项目,我感觉它的速度只是接近C/C++,或者稍微快一点。而且我更期待即时编译)。Goroutines和通道:值得赞扬的是,Go提供了一种轻量级语法来生成goroutines和使用通道。虽然一个小细节让Go的并发编程体验优于其他语言,但它确实揭示了语法的强大之处。接口:它们并不复杂,但易于理解和使用,并且在很多地方都有用。如果...;...{}语法:能够将变量的范围限制在if语句中真是太好了。这与Swift和Rust中的iflet有类似的效果,但更通用(Go没有像Swift和Rust那样的模式匹配,所以它不能使用iflet)。测试和文档注释都易于使用。Go工具链非常友好:将所有内容都集中在一个地方,而无需在命令行上使用多个工具。拥有垃圾收集器(GC):不必考虑内存管理确实让编程变得更容易。可变参数。我不喜欢的事情以下排名不分先后。nilslices:知道nil、nilslices和emptyslices是不一样的,我可以保证我们只需要其中两个,不需要第三个。枚举类型不是第一公民:用常量模拟枚举感觉像是倒退了一步。不允许循环引用:这实际上限制了包在划分项目模块时的用处,因为它鼓励在一个包中堆积很多文件(或者有很多零散的包,如果应该放在一起的文件分散了周围,??同样糟糕)。switch允许丢失匹配项。for...range语句返回一个索引/值对。只获取索引很容易(忽略值即可);但要仅获取值,您需要显式声明它。在我看来,这种做法应该反过来,因为在大多数情况下,我需要的价值多于索引。语法:定义和用法之间存在不一致。编译器有时会很挑剔(例如要求或不允许尾随逗号);这可以通过好的工具来缓解,但它有时仍然会产生烦人的额外步骤。使用多值返回类型时,类型上需要括号,但返回语句中不需要。声明结构需要两个关键字(类型和结构)。用大写字母标记公共或私有变量看起来像匈牙利符号,但更糟。隐式接口。我知道它也出现在我喜欢的东西中,但有时它真的很烦人——尤其是当你试图找出实现该接口的所有类型,或者为给定类型实现了哪些接口时。你不能在不同的包中编写带有接收器的函数,所以即使一个接口是“duck-typed”,你也不能为其他包中的类型实现它,这使得它们的用处大大降低。另外,正如我已经提到的,Go缺少泛型和宏。一致性作为一名语言设计者和程序员,也许Go最令我惊讶的是内置内容与用户可用内容之间经常出现不一致。许多语言的目标之一是尽可能多地去除编译器魔法,让用户也可以使用内置功能。运算符重载是一个简单但有争议的例子。但是Go有很多魔力!您很容易遇到无法执行内置函数可以执行的操作的问题。一些让我印象深刻的事情:返回多个值和通道的语法很棒,但是这两者不能一起使用,因为没有元组类型。数组和切片可以使用for...range语句进行迭代,但不能使用其他集合,因为它缺少迭代器的概念。像len或append这样的函数是全局函数,但是你自己的函数不能变成全局函数。这些全局函数只能使用内置类型。即使Go“没有泛型”,它们也可以被做成泛型。如果没有运算符重载,==会很烦人。因为这意味着您不能将自定义类型用作字典中的键,除非它们具有可比性。此属性派生自类型结构,程序员无法覆盖。总结Go是一种简单、小巧且令人愉悦的语言。它有一些角落和缝隙,但在大多数情况下它们都经过精心设计。学习和规避其他语言的一些鲜为人知的功能的速度非常快。Go也是一种与Rust截然不同的语言。虽然两者都可以广泛地描述为“系统语言”或“C的替代品”,但它们在设计目标、??应用领域、语言风格和优先级方面有所不同。垃圾收集确实有很大的不同:使用GC使Go更简单、更小、更容易理解。不使用GC使Rust非常快(特别是当您需要保证延迟,而不仅仅是高吞吐量时),并启用Go中不可能的功能或编程模式(或者至少在不牺牲性能的情况下这样做)。前提是不可能的)。Go是一种编译语言,具有实现良好的运行时以实现无可否认的速度。Rust也是一种编译型语言,但运行时更小,速度非常快。在没有其他限制的情况下,我认为选择使用Go还是Rust实际上是一种权衡:一方面,Go的学习曲线更短,程序更简单(这意味着开发速度更快);另一方面,Go的学习曲线更短,程序更简单(这意味着开发速度更快);Rust确实非常高效,并且具有更具表现力的类型系统(这使程序更安全,也意味着更快的调试和错误查找)。作者简介:NickCameron,PingCAP研发工程师,Rust语言核心成员。感谢Rust语言中文社区小伙伴的翻译和审校:译文:尚卓然审校:吴聪,张汉东