Aembit为什么选择 Rust?


Aembit 是一个平台,可将身份分配给工作负载并控制对其交互的服务的访问。Aembit 平台由两个组件组成:

  • 基于云的管理平面,包括管理控制台和系统后端
  • 与客户流量保持一致并按照管理平面的指示执行操作的代理

本文重点介绍我们对代理组件的语言选择。

C++
我们团队的明显选择是C++。这是团队中的工程师们在构建以前的产品时使用的语言,而且大多数现代代理的开发者也使用它。然而,C++的内存管理和并发的危险在过去曾咬过这些人,要对代码的安全性有信心并不容易,特别是当新成员加入团队时,他们的编码能力和成熟度各不相同。

Golang
下一个竞争者是Go,我们的团队在这方面有一些经验。由于Go的高性能、并发支持和安全性,它是网络编程的一个常见选择。由于Go是一种垃圾收集的语言,它可以防止许多可能困扰C++代码的内存问题。然而,垃圾收集器为高性能应用程序带来了另一种挑战。虽然Go的垃圾收集器总体上具有良好的声誉,但高性能应用程序可能会表现出垃圾收集暂停阻碍性能的问题。尽管Go提供了一些调整垃圾收集器的方法,但并没有办法规避所有潜在的挑战。虽然我们通常对在充分了解性能瓶颈之前对性能进行过度优化持谨慎态度,但对于我们这个刚刚起步的产品来说,以后需要用更高性能的语言重写代理的可能性太大。

Rust
我们的第三个选择,也是我们的最终选择,是Rust。Rust结合了C++的性能和Go的内存安全保证。然而,有一个小小的挑战,那就是我们团队中没有人有Rust的经验。我们的第一位工程师从阅读《Rust编程》开始,经过一个半月的经验积累,足以建立起代理的初始原型。请注意,这位工程师有很多过去的开发经验,这不是一个经验不足的人可能会做的事情。

进展顺利
1、支持性社区

如果没有遍布互联网的Rust社区的帮助,我们不可能取得如此大的进展,尤其是考虑到我们团队的Rust经验很少。Stack Overflow、Rust编程语言论坛以及Discord、Slack和Gitter上的特定项目社区对我们在原型设计阶段和之后的工作都起到了重要作用。

2、工具化
Rust的工具生态系统发展良好。Rust的包管理器、通用的瑞士军刀,称为Cargo,使得构建、测试、运行和依赖性管理变得非常简单。它还通过GitHub Actions轻松实现了我们的CI流程。我们成功利用的其他工具包括。

  • 格式化(通过Rustfmt)
  • 提示(通过Clippy)
  • 语言服务器(通过Rust-analyzer)和相关的VS Code插件
  • 调试(通过LLDB)
  • 本地单元和集成测试
  • 代码覆盖率(通过LLVM)

3、信心
到目前为止,用Rust开发已经给我们带来了轻微的信心优势,否则我们可能不会有这样的信心。Rust的Result和Option类型使错误处理变得明确和详尽,而且Rust由于没有null的概念,所以减少了运行时错误。我们可以通过搜索代码库中的 "unwrap "和 "expect "方法找到几乎所有可能发生运行时错误的地方。

Rust的性能与C和C++处于同一数量级。由于它缺乏垃圾收集,所以我们并不担心我们是否有能力调整我们的应用程序,以满足我们的客户在未来可能需要的任何资源和性能要求。我们有信心将我们的代码优化到需要的程度。

最后,Rust的内存安全保证消除了我们在使用C++时可能会出现的一类错误。这让我们感到非常安心,并释放了精神带宽,使我们能够专注于其他优先事项,而不是捕捉内存漏洞。

挑战
1、学习曲线
Rust是一种大语言,有一些具有挑战性的概念,如所有权和寿命。正如我们的一位工程师所指出的,由于Rust的分层复杂性,学习Rust的概念往往具有挑战性。在没有深入了解内存模型、寿命和其他主题的情况下,人们不可能只是潜心研究异步代码这样的主题。这使得开始学习Rust的人在没有彻底理解发生了什么的情况下就跳入深渊是很棘手的。同一位工程师指出,C++的初学者可以比Rust的初学者更快产生代码。不过,他们在刚开始时写的C++代码还是很危险的,而Rust的编译器会首先阻止不安全代码的编译。

2、雇用情况
雇用Rust程序员是很棘手的。由于它仍然是一种相对较新的语言,所以很难找到精通该语言细节的人。我们花了四个月的时间才找到一个有Rust经验的高级开发人员,他是适合我们这个团队的。

3、原型到生产
根据我们的经验,在Rust代码库中对设计进行大量的重新架构是很痛苦的。Rust有严格的规则来支持没有垃圾收集的安全,这迫使你坚持使用Rust友好的架构。作为一个团队,我们还没有形成对Rust的经验,无法预见到什么是可以在Rust中干净地实现的,什么是不可以。因此,当我们试图从最初的原型迁移到第二稿时,我们遇到了多个路障。人们常说,Rust迫使你提前考虑你的设计。这对快速迭代是一个挑战。

总结
如果我们重新开始,我们会再次使用Rust吗?我们仍然不确定。虽然我们很高兴我们选择了Rust而不是C++来保证我们的内存安全,但我们仍然怀疑Go是否足以满足我们的目的。Go似乎可以解决所有列出的挑战。

  • 它的学习曲线要简单得多。
  • 它更容易找到对它有深刻经验的程序员。
  • 它的垃圾收集意味着更容易重新架构。

然而,有了Rust,我们不会为优化性能的能力而失眠,并对我们的运行时稳定性感到比以往更有信心。如果你处于类似的情况,正在评估Rust和Go,我们建议仔细考虑我们上面强调的挑战。仔细考虑Rust所提供的速度和运行时的稳定性是否能证明这些实际的障碍。