编程语言之Rust
2013-07-16 11:19:45 阿炯
Rust是Mozilla目前开发的一个新的编程语言,由Web语言的领军人物Brendan Eich(javascript之父),Dave Herman以及Mozilla公司的Graydon Hoare合力开发。其最初由 Mozilla 工程师 Graydon Hoare 于 2006 年创建,于 2009 年开始接受 Mozilla 赞助。它融合了 C++ 语言的性能与其他高级语言更友好的语法,对代码安全性问题提供了额外的关注。目前已经得到了微软、苹果、AWS、Mozilla、Linux 内核社区等组织机构的青睐,并连续 5 年被评为 Stack Overflow “最受欢迎的” 编程语言。第一个有版本号的 Rust 编译器于 2012 年 1 月发布,而其第一个稳定版本 Rust 1.0 于 2015 年 5 月 15 日发布。采用MIT协议授权。
Rust is a curly-brace, block-structured expression language. It visually resembles the C language family, but differs significantly in syntactic and semantic details. Its design is oriented toward concerns of “programming in the large”, that is, of creating and maintaining boundaries – both abstract and operational – that preserve large-system integrity, availability and concurrency.
It supports a mixture of imperative procedural, concurrent actor, object-oriented and pure functional styles. Rust also supports generic programming and metaprogramming, in both static and dynamic styles.
Features
Type-system-static, nominal, linear, algebraic, locally inferred
Memory-safety no null or dangling pointers, no buffer overflows
Concurrency-lightweight tasks with message passing, no shared memory
Generics-type parameterization with type classes
Exception-handling unrecoverable unwinding with task isolation
Memory-model optional task-local GC, safe pointer types with region analysis
Compilation-model ahead-of-time, C/C++ compatible
License-dual MIT/ApacheV2
特性
Rust的语法跟C比较相似,不过它是一个更现代的语言。Rust是针对多核体系提出的语言,并且吸收一些其他动态语言的重要特性,比如不需要管理内存,比如不会出现Null指针等等。下面是其一些特性:
模式匹配和代数型的数据类型(枚举)
基于任务的并发性,轻量级的任务都可以在不共享内存的情况下并发运行(threads without data races)
高阶函数(闭包)与泛型
多态性,结合类似Java的接口特性和Haskell的类类型。
变量默认情况下不可变(const)
非阻塞的垃圾收集器
零成本抽象保证内存安全(guaranteed memory safety)
模式匹配(pattern matching)
类型推导(type inference)
极小运行环境(minimal runtime)
高效C绑定(efficient c bindings)
创建这个新语言的目的是为了解决一个很顽疾的问题:软件的演进速度大大低于硬件的演进,软件在语言级别上无法真正利用多核计算带来的性能提升。Rust是针对多核体系提出的语言,并且吸收一些其他动态语言的重要特性,比如不需要管理内存,比如不会出现Null指针等等。Rust对自己的定位是和C/C++类似的系统编程语言,由于C和C++都非常容易出现内存错误(如:segmentation faults),并由此引发的一系列相关的问题,而Rust的出现就是用来避免类似问题的发生。可以说,Rust拥有了更高的开发效率和安全性,同时拥有和C/C++一样的性能。那么,Rust是如何在保证系统的安全性的同时,又能够保证程序的效率的呢?
Rust是把安全性检查提前到了编译阶段,对于Rust编译器,当遇到灰色情况它会拒绝编译,直至你搞清楚它,当然这对程序员来说是一种束缚,势必会牺牲一点开发的效率,不过这都是为了安全性考虑。它最早于Mozilla的社区峰会上公之于众的,当时就有人问以后是否会用Rust重写Firefox,Brenda说希望如此。代替C语言基本不可能,可以代替部分C++应用层代码。语言的特殊使用方法非常多导致学习曲线很陡,做不到象C语言对内存的精细控制,想做极致的程序还是用C语言。
关于螃蟹Ferris
Ferris 是 Rust 社区的非官方吉祥物。很多 Rust 程序员自称“Rustaceans”, 它与“crustacean”相似。 我们用“they”、“them”等代词,而不用带性别的代词来指代 Ferris。
Ferris 与形容词“ferrous”相似,它的含义与铁有关。由于 Rust(锈)通常由铁形成, 因此它算得上是个吉祥物名字的有趣来源。
《StackOverflow 2022年开发者调查报告节选》中指出Rust 连续第七年成为最受喜爱的编程语言,在此小结一下其在开发方面的重要特性:
1. Cargo、模式匹配、迭代器、无畏并发、rayon、Traits 系统、性能;
2. windows 下安装非常便捷、高性能、Cargo、通常较为有效的编译错误提示、enums 和模式匹配、内存安全、通过 iterator 做到了声明式编程,同时不牺牲性能;
3. 内存管理类似于 C++,但是相较于为程序执行时的意外随时做好准备,Rust 强大的编译器会帮助和引导写出安全的代码。
Rust vs Go
两者有很多共同点,都是现代编程语言,其诞生都是为了解决软件的安全性和可伸缩性。
1.性能和并发性:都是编译型语言,专注于代码的执行效率;还可以轻松访问当今机器的多个处理器,使其成为编写有效并行代码的理想语言。
2.团队可伸缩,代码可走读:都是为团队的工作方式而设计,通过消除不必要的问题,如格式、安全性和复杂的组织,来改善代码审查。
3.开放意识:都有自己的包管理工具,会自动管理开发者获取和维护开发者构建的软件包列表,使其更专注于自己的业务代码。
4.安全:都很好地解决当今应用程序的安全问题,确保用这些语言构建的代码在运行时不会暴露给用户各种经典的安全漏洞,如缓冲区溢出、释放后可重用等。
5.轻便:都天生支持交叉编译,不需要配置构建环境。
两者的不同之处又在哪里呢?
1.性能。从设计上看,Go 没有提供可以让你获得更多性能方式,而Rust 的目标是使你能从代码中挤出更多的性能。或许找不到比 Rust 更快的语言了。
2.精准控制。Rust最大优势之一是开发者对在内存管理、机器可用资源的使用、代码优化以及问题解决方案的设计等方面拥有大量的控制权。而Go 并没有这种精准控制的设计, 它的设计更适合于更快的探索时间和更快的交付周期。虽然 Go 和 Rust 的设计有很大的不同,但它们的设计发挥了一套兼容的优势,当它们一起使用时,可以实现很大的灵活性和性能。
Go和Rust最本质的不同是内存管理方式的不同。程序运行时总是要申请内存,并在合适的时候释放内存,以往主要有两种方式:一种是像C/C++一样,手动管理;另一种是像Java一样,通过虚拟机管理;Go是把拉圾回收(GC)的功能精减,编译到程序里,虽然不需要外部虚拟机,但本质上和java一样;而rust和传统做法不同,它通过设计一套语法,然后让编译器在编译期就分析出哪些内存什么时候应该释放,并在相应的地方插入释放内存的代码,这样就不用拉圾回收(GC),也能达到内存安全的目的,编译结果本质上和C/C++一样。其结果是,Go因其有GC,运行时比较大,不适合做一些系统级的开发;而Rust编译的结果和C/C++一样,不需要额外的运行时,可以做些系统级的开发,但语法比较复杂。
Rust 成立专门的安全团队
Rust 编程语言的非营利组织 Rust 基金会于2022年9月中旬宣布,将建立一个专门的安全团队;由 OpenSSF 的 Alpha-Omega Initiative (一个专注于开源软件供应链安全的 Linux 基金会项目) 以及该基金会的最新白金会员、Devops 平台供应商 JFrog 提供支持。Alpha-Omega 和 JFrog 的投资还包括专门的员工资源,“这将使 Rust 基金会能够创建和实施安全最佳实践”。作为公司对 Rust 基金会和生态系统投资的一部分,JFrog 已经承诺其安全研究团队的成员将在 Rust 基金会安全团队工作。
Rust 基金会执行董事 Bec Rumbul 称,经常有一种误解,即因为 Rust 确保内存安全,所以大家就误认为它就是 100% 安全的。但 Rust 和其他语言一样,也有可能受到攻击,因此需要采取积极的措施来保护和维持它和社区。“随着 Rust 基金会安全团队的成立,我们将能够以最高水平的安全人才支持更广泛的 Rust 社区,并帮助确保 Rust 对每个人的可靠性。当然,这只是一个开始。希望在未来几个月和几年内继续建立这个团队”。根据介绍,新安全团队的第一项举措将是进行安全审计和威胁建模练习,以确定未来如何以经济的方式维护安全。该团队还将帮助倡导整个 Rust 领域的安全实践,包括 Cargo 和 Crates.io,并将成为维护者社区的资源。
OpenSSF 曾在其今年早些时候发布的 10-Point Open Source Security Mobilization Plan 中建议,业界应该努力消除许多漏洞的根源,方法是用 Rust 和 Go 等语言替换 C 和 C++ 等非内存安全语言。因此,OpenSSF 的 Alpha-Omega Initiative 目前已向 Rust 基金会提供了一笔资金,以支持一名专门的安全工程师。Alpha-Omega 由谷歌和微软资助,其使命是直接参与提高 OSS 项目的安全性。“我们正在学习如何把钱变成安全”。
Rust vs Go
Rust 基金会发布了 2022 年度 Rust 调查报告结果,报告显示其采用率不断提高,超过 90% 的调查受访者表示自己是其用户;Rust 以其卓越的内存安全性和并发性能正日益成为开发者关注的焦点。然而,同样令人难以忽视的是 Go,这门曾被评选为年度编程语言的相对比较 “老牌” 的选手。那么到底选 Rust 还是 Go?
两者生成的程序运行速度都很快,因为它们会被编译成本机机器码,无需通过解释器或虚拟机这个步骤。但 Rust 的性能还要更胜一筹,甚至能够与被称为业界性能标杆的 C 和 C++ 相媲美。不同的是,Rust 还提供内存安全与并发安全机制,同时几乎不影响执行速度。Rust 还允许开发者构建复杂抽象,又无需在运行时承受性能损失。Rust 运行时性能还具有良好的一致性和可预测性,因为它没有使用垃圾收集。Go 的垃圾收集器非常高效,而且做了优化以尽可能缩短暂停时长。Rust 的目标是让程序员完全控制底层硬件,所以使用其编写的程序都能深度优化以接近机器的最大理论性能。如此一来,Rust 就在执行速度胜过其他一切的特定应用场景下成为最佳选项,此类用例包括游戏编程、操作系统内核、网络浏览器组件和实时控制系统等。
值得注意的是,和Rust语言一样,Go语言的创造者也“讨厌”C++,而Go也是云原生的主导语言。
Go 和 Rust 绝对都是优秀的编程语言。它们现代、强大、多功能,并提供出色的性能。但直接比较它们确实没有意义,哪个更好,因为每种编程语言背后都代表着一系列深层的权衡。不同的语言会针对不同的需求进行优化,所以我们在选择语言的时候,也应该考虑我们想要用它来解决什么样的问题。所以将从Go和Rust语言的适用场景出发,讨论其设计之“道”。虽然 Rust 和 Go 在语法和风格上有很大差异,但两者都是构建软件的一流工具。下面开始具体分析。
Go 与 Rust:相似
Rust 和 Go 有很多共同点,这就是人们经常比较两者的原因。他们有什么共同目标?
Rust 是一种低级静态类型多范式编程语言,更注重安全性和性能。
和:
Go 是一种开源编程语言,可以轻松构建简单、可靠且高效的软件。
内存安全
两者都是非常重视内存安全的现代编程语言。在 C 和 C++ 等古老语言的几十年里,已经清楚地认识到,错误和 bug 的核心原因之一是对内存的不安全/不正确访问。所以它们各自给出了不同的解决方案,但两者的目标都是在内存管理方面更加智能、安全,帮助开发者编写出性能优异的正确程序。
快速、紧凑的可执行文件
两者都是编译语言,这意味着程序可以直接翻译成可执行的机器代码,从而可以将程序部署为单个二进制文件。与 Python 和 Ruby 等解释性语言不同,不需要随程序附带解释器和大量库/依赖项。作为这一核心优势的直接体现,Rust 和 Go 程序往往比解释性语言运行得更快。
通用语言
两者都是功能强大且可扩展的通用编程语言,可用于开发各种现代软件 - 从 Web 应用程序到分布式微服务,再到嵌入式微控制器和移动应用程序等等。都拥有优秀的标准库和蓬勃发展的第三方生态系统,再加上强大的商业支持和庞大的用户群。两者都已存在多年,并将在未来几年继续蓬勃发展。如今,学习 Go 或 Rust 将是一项非常合理的时间和精力投资。
务实的编程风格
它们既不是过度函数式语言(例如 Scala 或 Elixir),也不是完全面向对象的语言(例如 Java 和 C#)。相反,Go和Rust虽然都具有函数式和面向对象的编程功能,但它们始终强调一种务实的导向——即以最合适的方式解决问题,而不是通过“意识形态”强迫大家按照特定的方式做事”。
但如果你真的喜欢函数式编程风格,Rust 方面还有更多相关的工具选项,这也是 Rust 相对于 Go 的优势之一。当然可以争论什么是真正的“面向对象”语言。但公平地说,C++、Java 或 C# 用户所期望的面向对象编程风格在 Go 或 Rust 中并不真正存在。
大规模开发
它们都为大规模编程提供了许多有用的功能,因此都可以适应大型开发团队和大型代码库的实际需求。
例如,C程序员多年来一直在争论如何放置括号,以及代码是否应该使用制表符或空格缩进;但Rust和Go已经使用标准格式化工具(Go有gofmt,Rust有rustfmt)完全解决了这些问题。它们会自动使用一致的样式重写您的代码。并不是说这种特殊的格式很微妙,而是 Rust 和 Go 程序员更加务实,更喜欢统一的实现标准。gofmt的风格无人喜欢,但却是所有人的最爱。
这两种语言的另一大优势体现在构建管道上。两者都具有优秀的、内置的、高性能的标准构建和依赖管理工具。这意味着程序员不必与复杂的第三方构建系统抗衡,也不需要每隔几年就学习一个新系统。
Rust 还是 Go
说了这么多问题,而且两种语言都设计得这么好,功能也这么强大,那么这次比赛有什么结果吗?或者,既然两者都是很好的选择,为什么人们仍然在社交媒体上感到愤怒,写长篇评论博客文章说“Rust 是白痴的”或“Go 根本不是一种编程语言”之类的刺耳的话?
当然,有些人只是想发泄情绪,但这显然无助于解决实际问题。至少当涉及到在项目中使用哪种语言,或者使用哪种语言闯入编程世界时,大声的声音显然无助于做出正确的选择。
回到刚刚的讨论中,看看Rust和Go在理性分析下有何优缺点。
Go 与 Rust:性能
如前所述,Go 和 Rust 生成的程序都很快,因为它们无需经过解释器或虚拟机即可编译为本机机器代码。但Rust的性能仍然更好,甚至可以与被誉为业界性能基准的C和C++相媲美。而且与这些老式语言不同的是,Rust 还提供了内存安全和并发安全机制,同时几乎不影响执行速度。Rust 还允许开发人员构建复杂的抽象,而不会在运行时造成性能损失。
相比之下,虽然 Go 程序性能良好,但它们的设计重点是开发速度(包括编译)而不是执行。Go程序员更喜欢清晰易读的代码,因此运行速度会慢一些。Go 编译器也不会花费太多时间生成最高效的机器代码,它更关心快速编译大量代码。因此,在运行时基准测试中,Rust 程序常常击败 Go 程序。
Rust 的运行时性能也非常一致且可预测,因为它不使用垃圾收集。Go 的垃圾收集器非常高效,并且经过优化以尽可能缩短暂停时间(Go 的每个新版本的暂停时间都会变短)。但无论如何,垃圾收集总是会在程序的行为方式中引入一些不可预测性,这对于某些应用程序(例如嵌入式系统)来说可能是严重的甚至是完全不可接受的。
简单
如果一种编程语言太难学习并且将大多数人拒之门外,那么它的功能有多么强大也无济于事。Go 似乎是故意设计的,以将其与 C++ 等日益复杂的语言区分开来:它的语法非常少,关键字非常少,甚至函数也很少。这意味着Go语言很容易学习,稍微了解一下就可以用它来编写各种程序。这里的关键是“简单”二字。当然简单并不意味着容易;但小而简单的语言肯定比大而复杂的语言更容易学习。实现一种效果的方法并不多,因此高质量的 Go 代码几乎总是看起来相同。这样做还有另一个好处:可以快速了解我们不熟悉的服务在做什么。
Go的核心本体很小,但是标准库却很强大。也就是说,除了 Go 语法之外,学习曲线还必须考虑标准库的这一部分。另一方面,将功能从语言转移到标准库意味着大家只需要专注于学习与当前开发需求相关的库即可。在设计上还充分考虑了大规模软件开发的需求,能够强有力地支持大型代码库和开发团队。在这种场景下,新开发人员必须能够快速上手。为此Go 社区始终优先考虑程序的简单性、清晰性、多功能性和直接性。Go 是我使用过的最高效的语言之一。口头禅是:今天就解决实际问题。
功能
Rust 比其他几种编程语言支持更多的复杂性,因此相应的实现范围也更大。其经过专门设计,包含各种强大且有用的功能,帮助程序员用更少的代码做更多的事情。例如,Rust 的 match 函数使得快速编写灵活且富有表现力的逻辑成为可能;但也因为Rust的设计考虑较多,所以学习起来比较困难,尤其是在初级阶段。不过没关系,C++ 或 Java 毕竟有很多东西要学,而且它甚至不提供 Rust 的内存安全等高级功能。
所以批评 Rust 过于复杂的声音确实没有道理:它的设计就是为了强调表现力和丰富的功能,我们不能期望它在享受好处的同时又如此简单纯粹。所以 Rust 当然有它自己的学习曲线。但只要克服了这个困难,前面的路就会平坦。
Rust 与 C++ 和 D 争夺程序员的心智份额,这些程序员准备接受更复杂的语法和语义(可能还有更高的可读性成本),以换取最大可能的性能。虽然 Rust 和 Go 互相借用了一些功能(例如泛型),但可以公平地说 Rust 的功能比 Go 的更好。
并发性
大多数语言都提供某种形式的并发编程支持(即同时执行多个操作),但 Go 是为此而设计的。Go 不使用操作系统线程,而是提供了一种轻量级替代方案:goroutines。每个 goroutine 都是一个独立执行的 Go 函数,Go 调度程序将其映射到受控制的操作系统线程之一。也就是说,调度程序可以非常有效地管理大量并发 goroutine,同时仅使用有限数量的操作系统线程。
因此可以在单个程序中运行数百万个并发 goroutine,而不必担心严重的性能问题。正因为如此,Go 是针对 Web 服务器和微服务等大规模并发应用场景的完整解决方案。Go 还为 goroutine 提供通道,这是一种快速、安全、高效的通信和共享数据的方式。Go的并发设计水平确实很高,使用体验相当轻松愉快。
总的来说,并发程序的设计是非常困难的,用任何语言构建可靠、正确的并发程序绝非易事。不过,由于在项目之初就考虑到了这个需求,Go 中的并发编程机制已经做得尽可能简单,并且集成得很好。
Go 使得构建一个精心分解的应用程序变得非常容易,该应用程序在部署为一组微服务时充分利用并发性。
Rust 也可以做这些事情,但可以说它更难一些。在某些方面,Rust 对防止与内存相关的安全漏洞的痴迷意味着程序员必须不遗余力地执行在其他语言(包括 Go)中更简单的任务。
相比之下,Rust中的并发机制才刚刚落地,尚未稳定下来,所以欢迎大家继续关注这个活跃的发展方向,这也是有好处的。例如Rust的rayon库提供了一种非常优雅且轻量级的方法,可以将顺序计算转换为并行计算。
虽然在 Rust 中实现并发程序可能并不容易,但它仍然是完全可行的,并且这些程序也受益于 Rust 精心设计的内存安全保证。以标准库的 Mutex 类为例:在 Go 中,我们可能会在访问某些东西之前忘记获取互斥锁;但在 Rust 中完全不必担心。
Go 将并发作为首要概念。这并不是说您无法在 Rust 中找到 Go 面向参与者的并发性的各个方面,而是将其留给程序员作为练习。
安全
如前所述,Go 和 Rust 都有自己的方法来防止各种常见的编程错误,尤其是与内存管理相关的问题。但Rust更进了一步,可以说是不遗余力地保证大家不犯意想不到的安全错误。也就是说,Rust 的编程体验与几乎任何其他语言都不同,并且在首次引入时可能非常具有挑战性。但在很多开发者看来,这种努力显然是值得的。包括 Go 在内的许多语言也提供了帮助程序员避免错误的工具,但 Rust 将这种效果提升到了一个新的水平。许多不正确的程序甚至根本无法编译。
Rust 与 Go:差异
虽然 Rust 和 Go 都是流行且广泛使用的现代语言,但它们并不是真正的竞争对手,因为它们可以解决截然不同的用例。Go 的整个编程方法与 Rust 完全不同,这些特性特别适合某些人,但也可能完全激怒其他人。这是有道理的,因为如果 Rust 和 Go 都以基本相似的方式解决基本相同的问题,为什么我们需要两种不同的语言?
那么是否可以从 Rust 和 Go 的做法入手,解读它们各自的本质呢?一起来尝试一下吧。
垃圾收集
“垃圾收集,还是不垃圾收集”始终是一个没有正确答案的问题。总的来说,垃圾收集和自动内存管理可以帮助我们快速、轻松地开发可靠、高效的程序。所以对于一些开发者来说,这些都是必不可少的功能。其他人则认为,垃圾收集及其性能开销和全局暂停可能会导致不可预测的运行时行为并引入不可接受的延迟。当然,这种说法是有道理的。
接近硬件
计算机编程的历史可以说是一个日益复杂的抽象发展过程。它允许程序员解决问题而无需过多关注底层硬件的实际运行方式。这种设计使得程序更容易编写并且更可移植。但对于其他程序来说,访问硬件和精确控制程序的执行方式更为重要。
Rust的目标是让程序员“靠近硬件”,重新获得更多的控制权;而 Go 则抽象了架构细节,让程序员能够更接近问题。
Golang 擅长编写微服务和典型的“DevOps”任务,但它不是一种系统编程语言。Rust 对于并发性、安全性和/或性能很重要的任务来说更强大;但它的学习曲线比 Go 更陡峭。
性能至上
事实上,对于大多数程序来说,性能不如代码可读性重要。但如果某些项目确实将性能放在第一位,那么 Rust 中的许多设计权衡将帮助您将代码的执行速度一路推向极限。相比之下,Go 更关心代码的简单性,甚至愿意为此牺牲一些运行时性能。但 Go 的构建速度是无与伦比的,这对于大型代码项目来说往往更为重要。
Rust 的执行速度比 Go 更快。在基准测试中,Rust 确实更快,在某些情况下快了一个数量级。但在选择 Rust 之前,请明确一点:Go 在大多数基准测试中并没有落后太多,而且相对于 Java、C#、JavaScript 和 Python 等语言,它仍然保持着性能优势。如果需要一流的性能,请选择这两种语言中的任何一种,速度性能永远不会令人失望。另外如果正在构建一个处理高强度负载的Web服务,并且需要灵活的垂直/水平缩放,两种语言也都可以满足需求。
正确性
另一方面,如果你不强迫程序永远不会出错,那么权衡就会不同。大多数代码并不是为长期使用而设计的,但有些程序确实可以在生产环境中运行多年。面对这些现实,可能值得投入一点额外的时间来开发并确保程序正确可靠地工作,而不会在未来带来沉重的维护负担。
Go 和 Rust 都可以帮助您编写正确的程序,但方式不同:Go 提供了出色的内置测试框架,而 Rust 则专注于通过借用检查器消除运行时错误。明天要发布的代码,用Go;如果是未来五年必须保持稳定的代码,那就选择Rust。
虽然 Go 和 Rust 对于严肃的开发项目来说都足够好,但最好充分了解它们的各种功能和优势。简而言之,其他人的想法并不重要:只有您可以决定哪种编程语言更适合您的团队和项目需求。
在快速发展的软件开发领域中,选择合适的编程语言对项目的成功至关重要。Go 和 Rust 是两种现代编程语言,其都各自拥有一系列独特的特性和优势。在此再从不同的角度分析这两种语言,包括性能、语言特性、生态系统、适用场景以及社区支持。
语言概览
Go
设计哲学:Go 由 Google 开发,以简洁、高效和易读性著称。它是一种静态类型、编译型语言,具有优秀的并发支持。
主要特性:并发模型(Goroutines 和 Channels)、垃圾回收、简单的语法结构。
应用场景:云计算平台、微服务架构、网络服务器、分布式系统。
Rust
设计哲学:Rust 由 Mozilla 研究院开发,强调安全性、速度和并发。它是一种多范式编程语言,特别适合系统编程。
主要特性:内存安全(无垃圾回收)、所有权模型、类型系统、函数式编程特性。
应用场景:操作系统、游戏开发、嵌入式系统、WebAssembly。
性能和效率
Go
运行时性能:Go 有很好的运行时性能,但由于其垃圾回收机制,可能会出现延迟。
并发处理:Go 的并发模型使得它在处理高并发任务时表现出色。
Rust
内存管理:Rust 提供了无垃圾回收的内存安全保证,减少了运行时开销。
优化:Rust 的编译器优化和零成本抽象特性提供了接近 C/C++ 的性能。
语言特性和语法
Go
简洁的语法:Go 的语法简洁直观,易于学习和使用。
标准库:Go 拥有丰富的标准库,覆盖了网络、并发、加密等多个领域。
Rust
类型系统:Rust 强大的类型系统和借用检查器提供了编译时的内存安全保证。
模式匹配:Rust 支持模式匹配,使得复杂的控制流和数据结构处理更加直观。
开发生态和工具链
Go
工具链:Go 提供了全面的工具链,包括格式化工具 gofmt、文档生成工具 godoc 等。
依赖管理:Go Modules 提供了便捷的依赖管理。
Rust
Cargo:Rust 的包管理器 Cargo 是一个强大的工具,提供了项目构建、依赖管理和测试工具。
Crates.io:Rust 的包仓库 Crates.io 提供了大量的库和框架。
社区和学习资源
Go社区
支持:由 Google 强力支持,社区活跃,拥有大量的学习资源和活动。
应用案例:被许多科技公司和开源项目采用,包括 Docker、Kubernetes。
Rust社区
增长迅速:Rust 社区虽然较新,但增长迅速,受到开发者的广泛关注。
活跃的开源项目:包括 Servo、Rust-analyzer 等重要项目。
适用场景
Go
微服务和网络应用:Go 在构建高性能的网络服务和微服务方面表现出色。
快速开发:Go 的简单性使其成为快速开发和部署应用的理想选择。
Rust
系统编程:Rust 非常适合系统级应用,如操作系统和游戏引擎。
性能敏感应用:对于需要精细内存控制和性能优化的应用,Rust 是一个不错的选择。
小结
希望上述的对比能帮助您了解 Rust 和 Go 各自的亮点。如果可能的话,最好稍微体验一下这两种语言,因为它们在任何技术路径上都非常有用,即使对于业余编程爱好者来说也是如此。但如果你只有时间认真学习一门语言,请务必弄清楚 Go 和 Rust 各自的专长和倾向后再做选择。当然,编程语言知识只是成功软件工程师的一小部分。除了编程之外,工程师还必须精通设计、工程、架构、沟通和协作。Go 和 Rust 都是现代、高效的编程语言,它们各有所长。选择哪种语言取决于项目需求、团队熟悉度和性能要求。了解每种语言的特点将帮助开发者做出更合适的选择。只要每个人都能做好以下几件事,无论你选择哪种编程语言,你都将成为一名优秀的软件工程大师。更多的对比可见《2023年最大技术分歧:到底该选Rust还是Go?》。
Rust语言记事(202x)
最新版本:1.7
这是官方首次宣布的 Rust 稳定版本。当然1.0版本的发布并不代表Rust语言已经完工,还有很多特性需要完成。
最新版本:1.10
该版本做了一些优化,使其编译性能较之前得以加快。该版本一大特点是,新增封装类型:cdylib。具体信息可查看发布说明。其他改进可直接查看改进记录。
最新版本:1.70
Rust 团队于2023年6月上旬发布了1.70,新版本中值得关注的变化包括:
Crates.io 默认启用稀疏索引
Cargo 的 "sparse" 协议现在默认启用,用于从 crates.io 读取索引。这个功能之前已经在 Rust 1.68.0 中稳定下来,但仍然需要配置才能在 crates.io 中使用。原计划就是在 1.70.0 中默认启用该功能的,现在如期实现。当从 crates.io 的索引中获取信息时,你应该看到性能的大幅提高。如果因为某些原因,你需要保持以前的默认状态,即使用 GitHub 托管的 git 索引,可以使用 registries.crates-io.protocol 配置设置来改变默认状态。需要注意的是,改变访问方式的一个副作用是,这也改变了 crate cache 的路径,所以依赖将被重新下载。
OnceCell 和 OnceLock
OnceCell 和它的线程安全对应类型 OnceLock 两个新的类型已经稳定下来,用于共享数据的一次性初始化。这两种类型可以用在任何不希望立即构建的地方。诸如 lazy_static 和 once_cell 等 crate 在过去填补了这一需求,但现在这些构建块是标准库的一部分,由 once_cell 的 unsync 和 sync 模块移植过来。未来还有更多的方法可能会被稳定化,还有配套 LazyCell 和 LazyLock 存储其初始化函数的类型。
IsTerminal
这个新稳定的特性 is_terminal,用来确定一个给定的文件 descriptor(描述符)或 handle(句柄)是否代表一个终端或 TTY。一个常见的用例是让程序区分运行在脚本模式还是交互模式下,比如在交互式模式下呈现颜色或者是完整的 TUI。
调试信息的命名级别
-Cdebuginfo 编译器选项以前只支持数字 0...=2 来增加调试信息量,Cargo 在开发和测试配置文件中默认为 2,在发布和工作台配置文件中默认为 0。这些调试级别现在可以通过名称来设置:"none"(0)、"limited"(1)和 "full"(2),以及两个新的级别:"line-directives-only" 和 "line-tables-only"。注意这些命名的选项还不能通过 Cargo.toml 使用,在下一个 1.71 版本中会有这方面的支持。
test CLI 选项
当 #[test] 函数被编译时,可执行文件从 test crate 获得一个命令行接口。这个 CLI 有很多选项,包括一些尚未稳定的选项,需要指定 -Zunstable-options,就像 Rust 工具链中的许多其他命令一样。然而,虽然这只是在 nightly 构建中被允许的,但这个限制在 test 中并不适用。不过,从 1.70.0 开始,Rust 的稳定版和测试版将不再允许不稳定的 test 选项。在一些已知的情况下,不稳定的选项可能在用户不知情的情况下被使用,特别是 IntelliJ Rust 和其他 IDE 插件中使用的 --format json。
更多详情可查看此处。
最新版本:1.80
v1.80.0 稳定版现已于2024年7月下旬发布,主要带来以下变化:
1、LazyCell 和 LazyLock
新的 “lazy” 类型将值的初始化延迟到首次访问,它们类似于 1.70 中稳定的OnceCell 和 OnceLock 类型,但单元格中包含了初始化函数。这完成了从流行的和板条箱中采用到标准库中的功能的稳定化。完成了从 lazy_static和 once_cellcrates 到标准库中所采用功能的稳定化。
LazyLock 是线程安全选项,适用于 static values 等地方。
use std::sync::LazyLock;
use std::time::Instant;
static LAZY_TIME: LazyLock<Instant> = LazyLock::new(Instant::now);
fn main() {
let start = Instant::now();
std::thread::scope(|s| {
s.spawn(|| {
println!("Thread lazy time is {:?}", LAZY_TIME.duration_since(start));
});
println!("Main lazy time is {:?}", LAZY_TIME.duration_since(start));
});
}
LazyCell 缺乏线程同步,因此没有实现 static 所需的 Sync,但仍可用于 thread_local! statics。Rust 团队表示,根据线程安全的需要,这两种类型也可用于其他数据结构,因此 lazy initialization 在任何地方都可用。
2、Checked cfg names and values
在 1.79 中,rustc 稳定了一个 --check-cfgflag,现在 Cargo 1.80 正在对其知道的所有 cfg 名称和值启用这些检查(除了来自 rustc 的众所周知的名称和值)。包括来自 Cargo.toml 的功能名称以及来自构建脚本的新 cargo::rustc-check-cfgoutput。
unexpected_cfgs 会被 warning-by-default unexpected_cfgs lint 报告,用于捕获拼写错误或其他错误配置。例如,在具有可选 rayon 依赖项的项目中,此代码配置了错误的 feature 值:
fn main() {
println!("Hello, world!");
#[cfg(feature = "crayon")]
rayon::join(
|| println!("Hello, Thing One!"),
|| println!("Hello, Thing Two!"),
);
}
无论实际的 rayon 功能是否启用,都会报告相同的警告。还可以使用 Cargo.toml 清单中的 [lints] 表来扩展自定义 cfg 的已知名称和值列表。rustc 会自动提供警告中使用的语法。
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(foo, values("bar"))'] }
可以在之前的博客文章中阅读有关此功能的更多信息。
3、Exclusive ranges in patterns
Rust ranged 模式现在可以使用 exclusive endpoints,写成 a..b 或..b,类似于 Range 和 RangeTo 表达式类型。例如,以下模式现在可以在一个模式的终点和下一个模式的起点使用相同的常量:
pub fn size_prefix(n: u32) -> &'static str {
const K: u32 = 10u32.pow(3);
const M: u32 = 10u32.pow(6);
const G: u32 = 10u32.pow(9);
match n {
..K => "",
K..M => "k",
M..G => "M",
G.. => "G",
}
}
Exclusive ranges 一直以来作为一个不稳定的功能提供。Rust 团队表示,阻碍因素在于它们可能会增加混乱并增加模式中出现 off-by-one errors 的可能性。在 Rust 1.80 中,exhaustiveness checking 得到了增强,可以更好地检测模式匹配中的差距,新的 lintnon_contiguous_range_endpoints 和 overlapping_range_endpoints 将有助于检测在哪些情况下需要将 exclusive 模式切换为 inclusive 模式,反之亦然。v1.80 还稳定了许多 API,详情可查看官方公告。
官方主页:http://www.rust-lang.org/
Rust is a curly-brace, block-structured expression language. It visually resembles the C language family, but differs significantly in syntactic and semantic details. Its design is oriented toward concerns of “programming in the large”, that is, of creating and maintaining boundaries – both abstract and operational – that preserve large-system integrity, availability and concurrency.
It supports a mixture of imperative procedural, concurrent actor, object-oriented and pure functional styles. Rust also supports generic programming and metaprogramming, in both static and dynamic styles.
Features
Type-system-static, nominal, linear, algebraic, locally inferred
Memory-safety no null or dangling pointers, no buffer overflows
Concurrency-lightweight tasks with message passing, no shared memory
Generics-type parameterization with type classes
Exception-handling unrecoverable unwinding with task isolation
Memory-model optional task-local GC, safe pointer types with region analysis
Compilation-model ahead-of-time, C/C++ compatible
License-dual MIT/ApacheV2
特性
Rust的语法跟C比较相似,不过它是一个更现代的语言。Rust是针对多核体系提出的语言,并且吸收一些其他动态语言的重要特性,比如不需要管理内存,比如不会出现Null指针等等。下面是其一些特性:
模式匹配和代数型的数据类型(枚举)
基于任务的并发性,轻量级的任务都可以在不共享内存的情况下并发运行(threads without data races)
高阶函数(闭包)与泛型
多态性,结合类似Java的接口特性和Haskell的类类型。
变量默认情况下不可变(const)
非阻塞的垃圾收集器
零成本抽象保证内存安全(guaranteed memory safety)
模式匹配(pattern matching)
类型推导(type inference)
极小运行环境(minimal runtime)
高效C绑定(efficient c bindings)
创建这个新语言的目的是为了解决一个很顽疾的问题:软件的演进速度大大低于硬件的演进,软件在语言级别上无法真正利用多核计算带来的性能提升。Rust是针对多核体系提出的语言,并且吸收一些其他动态语言的重要特性,比如不需要管理内存,比如不会出现Null指针等等。Rust对自己的定位是和C/C++类似的系统编程语言,由于C和C++都非常容易出现内存错误(如:segmentation faults),并由此引发的一系列相关的问题,而Rust的出现就是用来避免类似问题的发生。可以说,Rust拥有了更高的开发效率和安全性,同时拥有和C/C++一样的性能。那么,Rust是如何在保证系统的安全性的同时,又能够保证程序的效率的呢?
Rust是把安全性检查提前到了编译阶段,对于Rust编译器,当遇到灰色情况它会拒绝编译,直至你搞清楚它,当然这对程序员来说是一种束缚,势必会牺牲一点开发的效率,不过这都是为了安全性考虑。它最早于Mozilla的社区峰会上公之于众的,当时就有人问以后是否会用Rust重写Firefox,Brenda说希望如此。代替C语言基本不可能,可以代替部分C++应用层代码。语言的特殊使用方法非常多导致学习曲线很陡,做不到象C语言对内存的精细控制,想做极致的程序还是用C语言。
关于螃蟹Ferris
Ferris 是 Rust 社区的非官方吉祥物。很多 Rust 程序员自称“Rustaceans”, 它与“crustacean”相似。 我们用“they”、“them”等代词,而不用带性别的代词来指代 Ferris。
Ferris 与形容词“ferrous”相似,它的含义与铁有关。由于 Rust(锈)通常由铁形成, 因此它算得上是个吉祥物名字的有趣来源。
《StackOverflow 2022年开发者调查报告节选》中指出Rust 连续第七年成为最受喜爱的编程语言,在此小结一下其在开发方面的重要特性:
1. Cargo、模式匹配、迭代器、无畏并发、rayon、Traits 系统、性能;
2. windows 下安装非常便捷、高性能、Cargo、通常较为有效的编译错误提示、enums 和模式匹配、内存安全、通过 iterator 做到了声明式编程,同时不牺牲性能;
3. 内存管理类似于 C++,但是相较于为程序执行时的意外随时做好准备,Rust 强大的编译器会帮助和引导写出安全的代码。
Rust vs Go
两者有很多共同点,都是现代编程语言,其诞生都是为了解决软件的安全性和可伸缩性。
1.性能和并发性:都是编译型语言,专注于代码的执行效率;还可以轻松访问当今机器的多个处理器,使其成为编写有效并行代码的理想语言。
2.团队可伸缩,代码可走读:都是为团队的工作方式而设计,通过消除不必要的问题,如格式、安全性和复杂的组织,来改善代码审查。
3.开放意识:都有自己的包管理工具,会自动管理开发者获取和维护开发者构建的软件包列表,使其更专注于自己的业务代码。
4.安全:都很好地解决当今应用程序的安全问题,确保用这些语言构建的代码在运行时不会暴露给用户各种经典的安全漏洞,如缓冲区溢出、释放后可重用等。
5.轻便:都天生支持交叉编译,不需要配置构建环境。
两者的不同之处又在哪里呢?
1.性能。从设计上看,Go 没有提供可以让你获得更多性能方式,而Rust 的目标是使你能从代码中挤出更多的性能。或许找不到比 Rust 更快的语言了。
2.精准控制。Rust最大优势之一是开发者对在内存管理、机器可用资源的使用、代码优化以及问题解决方案的设计等方面拥有大量的控制权。而Go 并没有这种精准控制的设计, 它的设计更适合于更快的探索时间和更快的交付周期。虽然 Go 和 Rust 的设计有很大的不同,但它们的设计发挥了一套兼容的优势,当它们一起使用时,可以实现很大的灵活性和性能。
Go和Rust最本质的不同是内存管理方式的不同。程序运行时总是要申请内存,并在合适的时候释放内存,以往主要有两种方式:一种是像C/C++一样,手动管理;另一种是像Java一样,通过虚拟机管理;Go是把拉圾回收(GC)的功能精减,编译到程序里,虽然不需要外部虚拟机,但本质上和java一样;而rust和传统做法不同,它通过设计一套语法,然后让编译器在编译期就分析出哪些内存什么时候应该释放,并在相应的地方插入释放内存的代码,这样就不用拉圾回收(GC),也能达到内存安全的目的,编译结果本质上和C/C++一样。其结果是,Go因其有GC,运行时比较大,不适合做一些系统级的开发;而Rust编译的结果和C/C++一样,不需要额外的运行时,可以做些系统级的开发,但语法比较复杂。
Rust 成立专门的安全团队
Rust 编程语言的非营利组织 Rust 基金会于2022年9月中旬宣布,将建立一个专门的安全团队;由 OpenSSF 的 Alpha-Omega Initiative (一个专注于开源软件供应链安全的 Linux 基金会项目) 以及该基金会的最新白金会员、Devops 平台供应商 JFrog 提供支持。Alpha-Omega 和 JFrog 的投资还包括专门的员工资源,“这将使 Rust 基金会能够创建和实施安全最佳实践”。作为公司对 Rust 基金会和生态系统投资的一部分,JFrog 已经承诺其安全研究团队的成员将在 Rust 基金会安全团队工作。
Rust 基金会执行董事 Bec Rumbul 称,经常有一种误解,即因为 Rust 确保内存安全,所以大家就误认为它就是 100% 安全的。但 Rust 和其他语言一样,也有可能受到攻击,因此需要采取积极的措施来保护和维持它和社区。“随着 Rust 基金会安全团队的成立,我们将能够以最高水平的安全人才支持更广泛的 Rust 社区,并帮助确保 Rust 对每个人的可靠性。当然,这只是一个开始。希望在未来几个月和几年内继续建立这个团队”。根据介绍,新安全团队的第一项举措将是进行安全审计和威胁建模练习,以确定未来如何以经济的方式维护安全。该团队还将帮助倡导整个 Rust 领域的安全实践,包括 Cargo 和 Crates.io,并将成为维护者社区的资源。
OpenSSF 曾在其今年早些时候发布的 10-Point Open Source Security Mobilization Plan 中建议,业界应该努力消除许多漏洞的根源,方法是用 Rust 和 Go 等语言替换 C 和 C++ 等非内存安全语言。因此,OpenSSF 的 Alpha-Omega Initiative 目前已向 Rust 基金会提供了一笔资金,以支持一名专门的安全工程师。Alpha-Omega 由谷歌和微软资助,其使命是直接参与提高 OSS 项目的安全性。“我们正在学习如何把钱变成安全”。
Rust vs Go
Rust 基金会发布了 2022 年度 Rust 调查报告结果,报告显示其采用率不断提高,超过 90% 的调查受访者表示自己是其用户;Rust 以其卓越的内存安全性和并发性能正日益成为开发者关注的焦点。然而,同样令人难以忽视的是 Go,这门曾被评选为年度编程语言的相对比较 “老牌” 的选手。那么到底选 Rust 还是 Go?
两者生成的程序运行速度都很快,因为它们会被编译成本机机器码,无需通过解释器或虚拟机这个步骤。但 Rust 的性能还要更胜一筹,甚至能够与被称为业界性能标杆的 C 和 C++ 相媲美。不同的是,Rust 还提供内存安全与并发安全机制,同时几乎不影响执行速度。Rust 还允许开发者构建复杂抽象,又无需在运行时承受性能损失。Rust 运行时性能还具有良好的一致性和可预测性,因为它没有使用垃圾收集。Go 的垃圾收集器非常高效,而且做了优化以尽可能缩短暂停时长。Rust 的目标是让程序员完全控制底层硬件,所以使用其编写的程序都能深度优化以接近机器的最大理论性能。如此一来,Rust 就在执行速度胜过其他一切的特定应用场景下成为最佳选项,此类用例包括游戏编程、操作系统内核、网络浏览器组件和实时控制系统等。
值得注意的是,和Rust语言一样,Go语言的创造者也“讨厌”C++,而Go也是云原生的主导语言。
Go 和 Rust 绝对都是优秀的编程语言。它们现代、强大、多功能,并提供出色的性能。但直接比较它们确实没有意义,哪个更好,因为每种编程语言背后都代表着一系列深层的权衡。不同的语言会针对不同的需求进行优化,所以我们在选择语言的时候,也应该考虑我们想要用它来解决什么样的问题。所以将从Go和Rust语言的适用场景出发,讨论其设计之“道”。虽然 Rust 和 Go 在语法和风格上有很大差异,但两者都是构建软件的一流工具。下面开始具体分析。
Go 与 Rust:相似
Rust 和 Go 有很多共同点,这就是人们经常比较两者的原因。他们有什么共同目标?
Rust 是一种低级静态类型多范式编程语言,更注重安全性和性能。
和:
Go 是一种开源编程语言,可以轻松构建简单、可靠且高效的软件。
内存安全
两者都是非常重视内存安全的现代编程语言。在 C 和 C++ 等古老语言的几十年里,已经清楚地认识到,错误和 bug 的核心原因之一是对内存的不安全/不正确访问。所以它们各自给出了不同的解决方案,但两者的目标都是在内存管理方面更加智能、安全,帮助开发者编写出性能优异的正确程序。
快速、紧凑的可执行文件
两者都是编译语言,这意味着程序可以直接翻译成可执行的机器代码,从而可以将程序部署为单个二进制文件。与 Python 和 Ruby 等解释性语言不同,不需要随程序附带解释器和大量库/依赖项。作为这一核心优势的直接体现,Rust 和 Go 程序往往比解释性语言运行得更快。
通用语言
两者都是功能强大且可扩展的通用编程语言,可用于开发各种现代软件 - 从 Web 应用程序到分布式微服务,再到嵌入式微控制器和移动应用程序等等。都拥有优秀的标准库和蓬勃发展的第三方生态系统,再加上强大的商业支持和庞大的用户群。两者都已存在多年,并将在未来几年继续蓬勃发展。如今,学习 Go 或 Rust 将是一项非常合理的时间和精力投资。
务实的编程风格
它们既不是过度函数式语言(例如 Scala 或 Elixir),也不是完全面向对象的语言(例如 Java 和 C#)。相反,Go和Rust虽然都具有函数式和面向对象的编程功能,但它们始终强调一种务实的导向——即以最合适的方式解决问题,而不是通过“意识形态”强迫大家按照特定的方式做事”。
但如果你真的喜欢函数式编程风格,Rust 方面还有更多相关的工具选项,这也是 Rust 相对于 Go 的优势之一。当然可以争论什么是真正的“面向对象”语言。但公平地说,C++、Java 或 C# 用户所期望的面向对象编程风格在 Go 或 Rust 中并不真正存在。
大规模开发
它们都为大规模编程提供了许多有用的功能,因此都可以适应大型开发团队和大型代码库的实际需求。
例如,C程序员多年来一直在争论如何放置括号,以及代码是否应该使用制表符或空格缩进;但Rust和Go已经使用标准格式化工具(Go有gofmt,Rust有rustfmt)完全解决了这些问题。它们会自动使用一致的样式重写您的代码。并不是说这种特殊的格式很微妙,而是 Rust 和 Go 程序员更加务实,更喜欢统一的实现标准。gofmt的风格无人喜欢,但却是所有人的最爱。
这两种语言的另一大优势体现在构建管道上。两者都具有优秀的、内置的、高性能的标准构建和依赖管理工具。这意味着程序员不必与复杂的第三方构建系统抗衡,也不需要每隔几年就学习一个新系统。
Rust 还是 Go
说了这么多问题,而且两种语言都设计得这么好,功能也这么强大,那么这次比赛有什么结果吗?或者,既然两者都是很好的选择,为什么人们仍然在社交媒体上感到愤怒,写长篇评论博客文章说“Rust 是白痴的”或“Go 根本不是一种编程语言”之类的刺耳的话?
当然,有些人只是想发泄情绪,但这显然无助于解决实际问题。至少当涉及到在项目中使用哪种语言,或者使用哪种语言闯入编程世界时,大声的声音显然无助于做出正确的选择。
回到刚刚的讨论中,看看Rust和Go在理性分析下有何优缺点。
Go 与 Rust:性能
如前所述,Go 和 Rust 生成的程序都很快,因为它们无需经过解释器或虚拟机即可编译为本机机器代码。但Rust的性能仍然更好,甚至可以与被誉为业界性能基准的C和C++相媲美。而且与这些老式语言不同的是,Rust 还提供了内存安全和并发安全机制,同时几乎不影响执行速度。Rust 还允许开发人员构建复杂的抽象,而不会在运行时造成性能损失。
相比之下,虽然 Go 程序性能良好,但它们的设计重点是开发速度(包括编译)而不是执行。Go程序员更喜欢清晰易读的代码,因此运行速度会慢一些。Go 编译器也不会花费太多时间生成最高效的机器代码,它更关心快速编译大量代码。因此,在运行时基准测试中,Rust 程序常常击败 Go 程序。
Rust 的运行时性能也非常一致且可预测,因为它不使用垃圾收集。Go 的垃圾收集器非常高效,并且经过优化以尽可能缩短暂停时间(Go 的每个新版本的暂停时间都会变短)。但无论如何,垃圾收集总是会在程序的行为方式中引入一些不可预测性,这对于某些应用程序(例如嵌入式系统)来说可能是严重的甚至是完全不可接受的。
简单
如果一种编程语言太难学习并且将大多数人拒之门外,那么它的功能有多么强大也无济于事。Go 似乎是故意设计的,以将其与 C++ 等日益复杂的语言区分开来:它的语法非常少,关键字非常少,甚至函数也很少。这意味着Go语言很容易学习,稍微了解一下就可以用它来编写各种程序。这里的关键是“简单”二字。当然简单并不意味着容易;但小而简单的语言肯定比大而复杂的语言更容易学习。实现一种效果的方法并不多,因此高质量的 Go 代码几乎总是看起来相同。这样做还有另一个好处:可以快速了解我们不熟悉的服务在做什么。
Go的核心本体很小,但是标准库却很强大。也就是说,除了 Go 语法之外,学习曲线还必须考虑标准库的这一部分。另一方面,将功能从语言转移到标准库意味着大家只需要专注于学习与当前开发需求相关的库即可。在设计上还充分考虑了大规模软件开发的需求,能够强有力地支持大型代码库和开发团队。在这种场景下,新开发人员必须能够快速上手。为此Go 社区始终优先考虑程序的简单性、清晰性、多功能性和直接性。Go 是我使用过的最高效的语言之一。口头禅是:今天就解决实际问题。
功能
Rust 比其他几种编程语言支持更多的复杂性,因此相应的实现范围也更大。其经过专门设计,包含各种强大且有用的功能,帮助程序员用更少的代码做更多的事情。例如,Rust 的 match 函数使得快速编写灵活且富有表现力的逻辑成为可能;但也因为Rust的设计考虑较多,所以学习起来比较困难,尤其是在初级阶段。不过没关系,C++ 或 Java 毕竟有很多东西要学,而且它甚至不提供 Rust 的内存安全等高级功能。
所以批评 Rust 过于复杂的声音确实没有道理:它的设计就是为了强调表现力和丰富的功能,我们不能期望它在享受好处的同时又如此简单纯粹。所以 Rust 当然有它自己的学习曲线。但只要克服了这个困难,前面的路就会平坦。
Rust 与 C++ 和 D 争夺程序员的心智份额,这些程序员准备接受更复杂的语法和语义(可能还有更高的可读性成本),以换取最大可能的性能。虽然 Rust 和 Go 互相借用了一些功能(例如泛型),但可以公平地说 Rust 的功能比 Go 的更好。
并发性
大多数语言都提供某种形式的并发编程支持(即同时执行多个操作),但 Go 是为此而设计的。Go 不使用操作系统线程,而是提供了一种轻量级替代方案:goroutines。每个 goroutine 都是一个独立执行的 Go 函数,Go 调度程序将其映射到受控制的操作系统线程之一。也就是说,调度程序可以非常有效地管理大量并发 goroutine,同时仅使用有限数量的操作系统线程。
因此可以在单个程序中运行数百万个并发 goroutine,而不必担心严重的性能问题。正因为如此,Go 是针对 Web 服务器和微服务等大规模并发应用场景的完整解决方案。Go 还为 goroutine 提供通道,这是一种快速、安全、高效的通信和共享数据的方式。Go的并发设计水平确实很高,使用体验相当轻松愉快。
总的来说,并发程序的设计是非常困难的,用任何语言构建可靠、正确的并发程序绝非易事。不过,由于在项目之初就考虑到了这个需求,Go 中的并发编程机制已经做得尽可能简单,并且集成得很好。
Go 使得构建一个精心分解的应用程序变得非常容易,该应用程序在部署为一组微服务时充分利用并发性。
Rust 也可以做这些事情,但可以说它更难一些。在某些方面,Rust 对防止与内存相关的安全漏洞的痴迷意味着程序员必须不遗余力地执行在其他语言(包括 Go)中更简单的任务。
相比之下,Rust中的并发机制才刚刚落地,尚未稳定下来,所以欢迎大家继续关注这个活跃的发展方向,这也是有好处的。例如Rust的rayon库提供了一种非常优雅且轻量级的方法,可以将顺序计算转换为并行计算。
虽然在 Rust 中实现并发程序可能并不容易,但它仍然是完全可行的,并且这些程序也受益于 Rust 精心设计的内存安全保证。以标准库的 Mutex 类为例:在 Go 中,我们可能会在访问某些东西之前忘记获取互斥锁;但在 Rust 中完全不必担心。
Go 将并发作为首要概念。这并不是说您无法在 Rust 中找到 Go 面向参与者的并发性的各个方面,而是将其留给程序员作为练习。
安全
如前所述,Go 和 Rust 都有自己的方法来防止各种常见的编程错误,尤其是与内存管理相关的问题。但Rust更进了一步,可以说是不遗余力地保证大家不犯意想不到的安全错误。也就是说,Rust 的编程体验与几乎任何其他语言都不同,并且在首次引入时可能非常具有挑战性。但在很多开发者看来,这种努力显然是值得的。包括 Go 在内的许多语言也提供了帮助程序员避免错误的工具,但 Rust 将这种效果提升到了一个新的水平。许多不正确的程序甚至根本无法编译。
Rust 与 Go:差异
虽然 Rust 和 Go 都是流行且广泛使用的现代语言,但它们并不是真正的竞争对手,因为它们可以解决截然不同的用例。Go 的整个编程方法与 Rust 完全不同,这些特性特别适合某些人,但也可能完全激怒其他人。这是有道理的,因为如果 Rust 和 Go 都以基本相似的方式解决基本相同的问题,为什么我们需要两种不同的语言?
那么是否可以从 Rust 和 Go 的做法入手,解读它们各自的本质呢?一起来尝试一下吧。
垃圾收集
“垃圾收集,还是不垃圾收集”始终是一个没有正确答案的问题。总的来说,垃圾收集和自动内存管理可以帮助我们快速、轻松地开发可靠、高效的程序。所以对于一些开发者来说,这些都是必不可少的功能。其他人则认为,垃圾收集及其性能开销和全局暂停可能会导致不可预测的运行时行为并引入不可接受的延迟。当然,这种说法是有道理的。
接近硬件
计算机编程的历史可以说是一个日益复杂的抽象发展过程。它允许程序员解决问题而无需过多关注底层硬件的实际运行方式。这种设计使得程序更容易编写并且更可移植。但对于其他程序来说,访问硬件和精确控制程序的执行方式更为重要。
Rust的目标是让程序员“靠近硬件”,重新获得更多的控制权;而 Go 则抽象了架构细节,让程序员能够更接近问题。
Golang 擅长编写微服务和典型的“DevOps”任务,但它不是一种系统编程语言。Rust 对于并发性、安全性和/或性能很重要的任务来说更强大;但它的学习曲线比 Go 更陡峭。
性能至上
事实上,对于大多数程序来说,性能不如代码可读性重要。但如果某些项目确实将性能放在第一位,那么 Rust 中的许多设计权衡将帮助您将代码的执行速度一路推向极限。相比之下,Go 更关心代码的简单性,甚至愿意为此牺牲一些运行时性能。但 Go 的构建速度是无与伦比的,这对于大型代码项目来说往往更为重要。
Rust 的执行速度比 Go 更快。在基准测试中,Rust 确实更快,在某些情况下快了一个数量级。但在选择 Rust 之前,请明确一点:Go 在大多数基准测试中并没有落后太多,而且相对于 Java、C#、JavaScript 和 Python 等语言,它仍然保持着性能优势。如果需要一流的性能,请选择这两种语言中的任何一种,速度性能永远不会令人失望。另外如果正在构建一个处理高强度负载的Web服务,并且需要灵活的垂直/水平缩放,两种语言也都可以满足需求。
正确性
另一方面,如果你不强迫程序永远不会出错,那么权衡就会不同。大多数代码并不是为长期使用而设计的,但有些程序确实可以在生产环境中运行多年。面对这些现实,可能值得投入一点额外的时间来开发并确保程序正确可靠地工作,而不会在未来带来沉重的维护负担。
Go 和 Rust 都可以帮助您编写正确的程序,但方式不同:Go 提供了出色的内置测试框架,而 Rust 则专注于通过借用检查器消除运行时错误。明天要发布的代码,用Go;如果是未来五年必须保持稳定的代码,那就选择Rust。
虽然 Go 和 Rust 对于严肃的开发项目来说都足够好,但最好充分了解它们的各种功能和优势。简而言之,其他人的想法并不重要:只有您可以决定哪种编程语言更适合您的团队和项目需求。
在快速发展的软件开发领域中,选择合适的编程语言对项目的成功至关重要。Go 和 Rust 是两种现代编程语言,其都各自拥有一系列独特的特性和优势。在此再从不同的角度分析这两种语言,包括性能、语言特性、生态系统、适用场景以及社区支持。
语言概览
Go
设计哲学:Go 由 Google 开发,以简洁、高效和易读性著称。它是一种静态类型、编译型语言,具有优秀的并发支持。
主要特性:并发模型(Goroutines 和 Channels)、垃圾回收、简单的语法结构。
应用场景:云计算平台、微服务架构、网络服务器、分布式系统。
Rust
设计哲学:Rust 由 Mozilla 研究院开发,强调安全性、速度和并发。它是一种多范式编程语言,特别适合系统编程。
主要特性:内存安全(无垃圾回收)、所有权模型、类型系统、函数式编程特性。
应用场景:操作系统、游戏开发、嵌入式系统、WebAssembly。
性能和效率
Go
运行时性能:Go 有很好的运行时性能,但由于其垃圾回收机制,可能会出现延迟。
并发处理:Go 的并发模型使得它在处理高并发任务时表现出色。
Rust
内存管理:Rust 提供了无垃圾回收的内存安全保证,减少了运行时开销。
优化:Rust 的编译器优化和零成本抽象特性提供了接近 C/C++ 的性能。
语言特性和语法
Go
简洁的语法:Go 的语法简洁直观,易于学习和使用。
标准库:Go 拥有丰富的标准库,覆盖了网络、并发、加密等多个领域。
Rust
类型系统:Rust 强大的类型系统和借用检查器提供了编译时的内存安全保证。
模式匹配:Rust 支持模式匹配,使得复杂的控制流和数据结构处理更加直观。
开发生态和工具链
Go
工具链:Go 提供了全面的工具链,包括格式化工具 gofmt、文档生成工具 godoc 等。
依赖管理:Go Modules 提供了便捷的依赖管理。
Rust
Cargo:Rust 的包管理器 Cargo 是一个强大的工具,提供了项目构建、依赖管理和测试工具。
Crates.io:Rust 的包仓库 Crates.io 提供了大量的库和框架。
社区和学习资源
Go社区
支持:由 Google 强力支持,社区活跃,拥有大量的学习资源和活动。
应用案例:被许多科技公司和开源项目采用,包括 Docker、Kubernetes。
Rust社区
增长迅速:Rust 社区虽然较新,但增长迅速,受到开发者的广泛关注。
活跃的开源项目:包括 Servo、Rust-analyzer 等重要项目。
适用场景
Go
微服务和网络应用:Go 在构建高性能的网络服务和微服务方面表现出色。
快速开发:Go 的简单性使其成为快速开发和部署应用的理想选择。
Rust
系统编程:Rust 非常适合系统级应用,如操作系统和游戏引擎。
性能敏感应用:对于需要精细内存控制和性能优化的应用,Rust 是一个不错的选择。
小结
希望上述的对比能帮助您了解 Rust 和 Go 各自的亮点。如果可能的话,最好稍微体验一下这两种语言,因为它们在任何技术路径上都非常有用,即使对于业余编程爱好者来说也是如此。但如果你只有时间认真学习一门语言,请务必弄清楚 Go 和 Rust 各自的专长和倾向后再做选择。当然,编程语言知识只是成功软件工程师的一小部分。除了编程之外,工程师还必须精通设计、工程、架构、沟通和协作。Go 和 Rust 都是现代、高效的编程语言,它们各有所长。选择哪种语言取决于项目需求、团队熟悉度和性能要求。了解每种语言的特点将帮助开发者做出更合适的选择。只要每个人都能做好以下几件事,无论你选择哪种编程语言,你都将成为一名优秀的软件工程大师。更多的对比可见《2023年最大技术分歧:到底该选Rust还是Go?》。
Rust语言记事(202x)
最新版本:1.7
这是官方首次宣布的 Rust 稳定版本。当然1.0版本的发布并不代表Rust语言已经完工,还有很多特性需要完成。
最新版本:1.10
该版本做了一些优化,使其编译性能较之前得以加快。该版本一大特点是,新增封装类型:cdylib。具体信息可查看发布说明。其他改进可直接查看改进记录。
最新版本:1.70
Rust 团队于2023年6月上旬发布了1.70,新版本中值得关注的变化包括:
Crates.io 默认启用稀疏索引
Cargo 的 "sparse" 协议现在默认启用,用于从 crates.io 读取索引。这个功能之前已经在 Rust 1.68.0 中稳定下来,但仍然需要配置才能在 crates.io 中使用。原计划就是在 1.70.0 中默认启用该功能的,现在如期实现。当从 crates.io 的索引中获取信息时,你应该看到性能的大幅提高。如果因为某些原因,你需要保持以前的默认状态,即使用 GitHub 托管的 git 索引,可以使用 registries.crates-io.protocol 配置设置来改变默认状态。需要注意的是,改变访问方式的一个副作用是,这也改变了 crate cache 的路径,所以依赖将被重新下载。
OnceCell 和 OnceLock
OnceCell 和它的线程安全对应类型 OnceLock 两个新的类型已经稳定下来,用于共享数据的一次性初始化。这两种类型可以用在任何不希望立即构建的地方。诸如 lazy_static 和 once_cell 等 crate 在过去填补了这一需求,但现在这些构建块是标准库的一部分,由 once_cell 的 unsync 和 sync 模块移植过来。未来还有更多的方法可能会被稳定化,还有配套 LazyCell 和 LazyLock 存储其初始化函数的类型。
IsTerminal
这个新稳定的特性 is_terminal,用来确定一个给定的文件 descriptor(描述符)或 handle(句柄)是否代表一个终端或 TTY。一个常见的用例是让程序区分运行在脚本模式还是交互模式下,比如在交互式模式下呈现颜色或者是完整的 TUI。
调试信息的命名级别
-Cdebuginfo 编译器选项以前只支持数字 0...=2 来增加调试信息量,Cargo 在开发和测试配置文件中默认为 2,在发布和工作台配置文件中默认为 0。这些调试级别现在可以通过名称来设置:"none"(0)、"limited"(1)和 "full"(2),以及两个新的级别:"line-directives-only" 和 "line-tables-only"。注意这些命名的选项还不能通过 Cargo.toml 使用,在下一个 1.71 版本中会有这方面的支持。
test CLI 选项
当 #[test] 函数被编译时,可执行文件从 test crate 获得一个命令行接口。这个 CLI 有很多选项,包括一些尚未稳定的选项,需要指定 -Zunstable-options,就像 Rust 工具链中的许多其他命令一样。然而,虽然这只是在 nightly 构建中被允许的,但这个限制在 test 中并不适用。不过,从 1.70.0 开始,Rust 的稳定版和测试版将不再允许不稳定的 test 选项。在一些已知的情况下,不稳定的选项可能在用户不知情的情况下被使用,特别是 IntelliJ Rust 和其他 IDE 插件中使用的 --format json。
更多详情可查看此处。
最新版本:1.80
v1.80.0 稳定版现已于2024年7月下旬发布,主要带来以下变化:
1、LazyCell 和 LazyLock
新的 “lazy” 类型将值的初始化延迟到首次访问,它们类似于 1.70 中稳定的OnceCell 和 OnceLock 类型,但单元格中包含了初始化函数。这完成了从流行的和板条箱中采用到标准库中的功能的稳定化。完成了从 lazy_static和 once_cellcrates 到标准库中所采用功能的稳定化。
LazyLock 是线程安全选项,适用于 static values 等地方。
use std::sync::LazyLock;
use std::time::Instant;
static LAZY_TIME: LazyLock<Instant> = LazyLock::new(Instant::now);
fn main() {
let start = Instant::now();
std::thread::scope(|s| {
s.spawn(|| {
println!("Thread lazy time is {:?}", LAZY_TIME.duration_since(start));
});
println!("Main lazy time is {:?}", LAZY_TIME.duration_since(start));
});
}
LazyCell 缺乏线程同步,因此没有实现 static 所需的 Sync,但仍可用于 thread_local! statics。Rust 团队表示,根据线程安全的需要,这两种类型也可用于其他数据结构,因此 lazy initialization 在任何地方都可用。
2、Checked cfg names and values
在 1.79 中,rustc 稳定了一个 --check-cfgflag,现在 Cargo 1.80 正在对其知道的所有 cfg 名称和值启用这些检查(除了来自 rustc 的众所周知的名称和值)。包括来自 Cargo.toml 的功能名称以及来自构建脚本的新 cargo::rustc-check-cfgoutput。
unexpected_cfgs 会被 warning-by-default unexpected_cfgs lint 报告,用于捕获拼写错误或其他错误配置。例如,在具有可选 rayon 依赖项的项目中,此代码配置了错误的 feature 值:
fn main() {
println!("Hello, world!");
#[cfg(feature = "crayon")]
rayon::join(
|| println!("Hello, Thing One!"),
|| println!("Hello, Thing Two!"),
);
}
无论实际的 rayon 功能是否启用,都会报告相同的警告。还可以使用 Cargo.toml 清单中的 [lints] 表来扩展自定义 cfg 的已知名称和值列表。rustc 会自动提供警告中使用的语法。
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(foo, values("bar"))'] }
可以在之前的博客文章中阅读有关此功能的更多信息。
3、Exclusive ranges in patterns
Rust ranged 模式现在可以使用 exclusive endpoints,写成 a..b 或..b,类似于 Range 和 RangeTo 表达式类型。例如,以下模式现在可以在一个模式的终点和下一个模式的起点使用相同的常量:
pub fn size_prefix(n: u32) -> &'static str {
const K: u32 = 10u32.pow(3);
const M: u32 = 10u32.pow(6);
const G: u32 = 10u32.pow(9);
match n {
..K => "",
K..M => "k",
M..G => "M",
G.. => "G",
}
}
Exclusive ranges 一直以来作为一个不稳定的功能提供。Rust 团队表示,阻碍因素在于它们可能会增加混乱并增加模式中出现 off-by-one errors 的可能性。在 Rust 1.80 中,exhaustiveness checking 得到了增强,可以更好地检测模式匹配中的差距,新的 lintnon_contiguous_range_endpoints 和 overlapping_range_endpoints 将有助于检测在哪些情况下需要将 exclusive 模式切换为 inclusive 模式,反之亦然。v1.80 还稳定了许多 API,详情可查看官方公告。
官方主页:http://www.rust-lang.org/