Jonathan Turner 和 Steve Francia 2021-03-12
尽管外界常将 Rust 和 Go 视为相互竞争的编程语言,但 Rust 团队和 Go 团队本身并不这么认为。恰恰相反,两个团队都对彼此的工作抱有深深的敬意,并认为这两种语言是互补的,共同致力于推动整个软件开发行业的现代化。
在本文中,我们将讨论 Rust 与 Go 各自的优缺点、它们如何相辅相成,并给出何时更适合使用哪种语言的建议。
越来越多的公司发现同时采用这两种语言能带来显著价值。为了从我们的观点转向一线用户的实际体验,我们采访了三家同时使用 Go 和 Rust 的公司——Dropbox、Fastly 和 Cloudflare——并将在全文穿插他们的实际案例和引述,以提供更丰富的视角。
语言对比
| 语言 | Go | Rust |
|---|---|---|
| 创建年份 | 2009 | 2010 |
| 创建机构 | Mozilla | |
| 著名软件 | Kubernetes、Docker、GitHub CLI、Hugo、Caddy、Drone、Ethereum、Syncthing、Terraform | Firefox、ripgrep、Alacritty、Deno、Habitat |
| 典型工作负载 | API、Web 应用、CLI 工具、DevOps、网络服务、数据处理、云原生应用 | 物联网(IoT)、处理引擎、安全敏感型应用、系统组件、云原生应用 |
| 开发者采用率 | 8.8%(第12位) | 5.1%(第19位) |
| 最受喜爱 | 62.3%(第5位) | 86.1%(第1位) |
| 最被期待 | 17.9%(第3位) | 14.6%(第5位) |
相似之处
Go 和 Rust 有许多共通点。两者都是现代编程语言,诞生于解决当前软件开发中安全性和可扩展性问题的实际需求。它们都是对现有语言缺陷的回应,尤其是针对开发者生产力、可扩展性、安全性以及并发能力方面的不足。
当今主流的许多编程语言设计于 30 多年前。而当时与今天存在五大关键差异:
- 摩尔定律被认为永远成立;
- 大多数软件项目由小型团队开发,通常面对面协作;
- 软件依赖项数量较少,且多为专有;
- 安全性是次要考虑,甚至完全不被重视;
- 软件通常只为单一平台编写。
相比之下,Rust 和 Go 都是为当今世界而设计的,在应对现代开发需求方面采取了相似的设计理念。
1. 性能与并发
Go 和 Rust 都是编译型语言,专注于生成高效代码。它们都能轻松利用现代多核处理器,非常适合编写高效的并行程序。
“使用 Go 使 MercadoLibre 将该服务所需的服务器数量减少到原来的八分之一(从 32 台降至 4 台),而且每台服务器所需的 CPU 核心数也从 4 个减至 2 个。通过 Go,该公司节省了 88% 的服务器,并将剩余服务器的 CPU 使用量减半,从而大幅降低成本。”
——《MercadoLibre Grows with Go》
“在我们严格管理的 Go 运行环境中,与 C++ 相比,CPU 使用率大约降低了 10%,同时代码更清晰、更易维护。”
——Bala Natarajan,PayPal
“在 AWS,我们也非常喜爱 Rust,因为它帮助我们编写高性能、安全的底层网络及其他系统软件。Amazon 首个著名的 Rust 产品 Firecracker 于 2018 年公开发布,为 AWS Lambda 等无服务器服务提供开源虚拟化技术。此外,我们还用 Rust 构建了 Amazon S3、EC2、CloudFront、Route 53 等服务。最近我们发布了 Bottlerocket——一个基于 Linux、用 Rust 编写的容器操作系统。”
——Matt Asay,Amazon Web Services
“我们看到了惊人的 1200%–1500% 的速度提升!在 Scala 中(实现的解析规则更少),Release 模式下耗时 300–450 毫秒;而在 Rust 中(实现了更多解析规则),仅需 25–30 毫秒!”
——Josh Hannaford,IBM
2. 团队友好 —— 易于代码审查
如今的软件由不断扩大的团队构建,通常通过源码控制系统进行分布式协作。Go 和 Rust 都为团队协作而设计,通过消除格式、安全性和复杂结构等不必要的干扰,提升了代码审查效率。两种语言的代码都相对上下文无关,使得审查者能快速理解他人编写的代码,无论是团队成员还是外部开源贡献者。
“从早期 Java 和 Ruby 背景转向 Go 和 Rust 编程,感觉像是卸下了千斤重担。在 Google 时,看到一个用 Go 编写的服务就让我松一口气,因为我知道它很容易构建和运行。Rust 也是如此,尽管我接触规模较小。我希望那种无限可配置的构建系统时代已经终结,未来的语言都应该自带开箱即用的专用构建工具。”
——Sam Rose,CV Partner
“用 Go 编写服务总让我松一口气:它的静态类型系统简单、易于推理;并发是一等公民;标准库既强大又精炼。只需一个标准 Go 安装,加上 gRPC 库和数据库连接器,几乎就能构建任何服务端应用,而且每位工程师都能轻松阅读和理解这些代码。Dropbox 工程师在 2019 年 async/await 稳定之前,曾感受到 Rust 在服务端的‘成长阵痛’,但此后生态逐渐统一,我们现在既能享受异步模式,又能获得‘无畏并发’的优势。”
——Daniel Reiter Horn,Dropbox
3. 开源意识强
如今,一个普通软件项目依赖数百个外部库已成常态。现代开发高度依赖软件复用,开发者通过包仓库集成各种组件,而每个依赖又可能引入更多依赖。因此,现代语言必须能无缝处理这种复杂性。
Go 和 Rust 都内置了包管理系统,开发者只需列出所需依赖,语言工具会自动下载并维护这些包,让开发者专注于自己的逻辑,而非依赖管理。
4. 安全性
当今应用面临的安全挑战,Go 和 Rust 都提供了有力保障。它们能有效防止缓冲区溢出、释放后使用(use-after-free)等经典漏洞,使开发者无需担忧这些低级错误,从而构建出默认更安全的应用。
“Rust 编译器在你遇到错误时真的会‘手把手’引导你。这让你能专注于业务目标,而不是陷入 bug 排查或解读晦涩的错误信息。”
——Josh Hannaford,IBM
“简而言之,Rust 的灵活性、安全性和安全性远超其严格的所有权、借用规则或缺乏垃圾回收器带来的不便。这些特性对云原生软件项目至关重要,能有效避免其中常见的大量 bug。”
——Taylor Thomas, Sr.,Microsoft
“Go 是强静态类型语言,且没有隐式转换,但语法开销却出奇地小。这得益于赋值时的简单类型推导和无类型的数值常量。这让 Go 比 Java(有隐式转换)更安全,但代码读起来却像 Python(变量无类型)一样简洁。”
——Stefan Nilsson,计算机科学教授
“在为 Dropbox 构建 Brotli 压缩库以存储块数据时,我们仅使用了 Rust 的安全子集,甚至限制在 core 库(无 stdlib),并将分配器设为泛型。这种方式不仅便于在客户端 Rust 中调用,也能通过 C FFI 从服务端的 Python 和 Go 调用。这种编译模式还提供了强大的安全保障。经过调优后,这个 100% 安全、带数组边界检查的 Rust Brotli 实现,性能仍优于对应的 C 原生版本。”
——Daniel Reiter Horn,Dropbox
5. 真正的可移植性
在 Go 和 Rust 中,编写一次代码即可轻松编译到多种操作系统和架构上。“一次编写,随处编译。” 此外,两者都原生支持交叉编译,无需像传统编译语言那样依赖“构建农场”。
“Go 在生产优化方面表现出色:内存占用小,适合作为大型项目的构建模块;开箱即用地支持跨架构编译。由于 Go 代码编译为单一静态二进制文件,容器化极其简单,部署到 Kubernetes 等高可用环境几乎毫无障碍。”
——Dewet Diener,Curve
“在基于云的基础设施中,通常使用 Docker 容器部署工作负载。Go 编译出的静态二进制文件,可以让 Docker 镜像只有 10–12MB,而 Node.js、Python 或 Java 镜像动辄数百 MB。因此,交付如此小巧的二进制文件简直太棒了。”
——Brian Ketelsen,Microsoft
“使用 Rust,我们将拥有一个高性能且可移植的平台,能轻松运行在 Mac、iOS、Linux、Android 和 Windows 上。”
——Matt Ronge,Astropad
差异之处
任何设计都涉及权衡。尽管 Go 和 Rust 几乎同期诞生、目标相似,但在关键决策点上选择了不同的权衡,从而形成了各自鲜明的特点。
1. 性能
Go 开箱即用就有出色性能,但设计上没有提供“旋钮”让你进一步压榨性能。而 Rust 则允许你榨取每一滴性能潜力——当今几乎没有语言能比 Rust 更快。但这种极致性能是以更高的复杂性为代价的。
“令人惊讶的是,我们在编写 Rust 版本时只做了最基本的优化。即便如此,Rust 仍轻松超越了我们精心调优的 Go 版本。这充分证明了用 Rust 编写高效程序有多么容易,而 Go 却需要深入调优。”
——Jesse Howarth,Discord
“Dropbox 工程师将 Python 代码逐行迁移到 Go 后,通常能看到 5 倍的性能和延迟提升,内存使用也大幅下降(因为没有 GIL,进程数也可减少)。但在内存受限的场景(如桌面客户端或某些服务器进程),我们会转向 Rust,因为其手动内存管理比 Go 的 GC 更高效。”
——Daniel Reiter Horn,Dropbox
2. 适应性 / 互操作性
Go 的优势在于快速迭代,开发者能迅速尝试想法并聚焦于解决问题。很多时候这就足够了,可以让他们转向其他任务。而 Rust 编译时间较长,迭代周期更慢。因此,Go 更适合需求频繁变化、需要快速响应的场景;而 Rust 则在需求稳定、追求极致性能和精细实现的场景中大放异彩。
“Go 类型系统的精妙之处在于:调用方可定义接口,使得库可以返回丰富的结构体,但只需暴露窄接口。Rust 类型系统的精妙之处则在于 match 语法与 Result<> 的结合,能静态确保所有情况都被处理,无需用 null 值填充未使用的返回参数。”
——Daniel Reiter Horn,Dropbox
“如果你的用例更贴近用户,需求更容易变动,那么 Go 会好得多,因为持续重构的成本低得多。你能更快地表达新需求并进行验证。”
——Peter Bourgon,Fastly
3. 学习曲线
简单来说,几乎没有比 Go 更易上手的语言了。很多团队在几周内就能掌握 Go 并投入生产。此外,Go 在十余年的发展中保持了高度一致的语言设计和实践风格,因此学习 Go 的投入具有长期价值。相比之下,Rust 因其复杂性被认为较难学习,通常需要数月才能熟练掌握,但换来的是对资源的精确控制和更高性能。
“当时团队中没人会 Go,但一个月内,所有人都开始用 Go 编码。”
——Jaime Garcia,Capital One
“Go 与其他语言最大的不同在于认知负荷。你能用更少的代码做更多的事,代码也更容易理解和推理。大多数 Go 代码风格高度一致,即使面对全新的代码库,你也能快速上手。”
——Glen Balliet,American Express 忠诚度平台工程总监
“与其他语言不同,Go 的设计初衷就是最大化用户效率。因此,有 Java 或 PHP 背景的开发者通常几周内就能上手 Go——而且据我们观察,很多人最终更喜欢它。”
——Dewet Diener,Curve
4. 精细控制
Rust 最大的优势之一,是开发者对内存管理、机器资源利用、代码优化和问题解决方案的精细控制能力。但这带来了比 Go 更高的复杂性。Go 的设计更侧重于快速探索和快速交付,而非精细打磨。
“随着我们对 Rust 经验的积累,它在另外两个维度上展现出优势:作为具备强内存安全性的语言,它非常适合边缘计算;同时,它在开发者社区中拥有巨大热情,成为全新组件的热门选择。”
——John Graham-Cumming,Cloudflare
总结 / 关键要点
- Go 的简洁性、性能和开发效率,使其成为构建面向用户的应用和服务的理想选择。快速迭代能力让团队能灵活应对不断变化的用户需求。
- Rust 的精细控制能力,使其非常适合底层、需求稳定、且能从微小性能提升中获益的场景,尤其是在超大规模部署中。
- Rust 的优势在“贴近硬件”处最为突出,Go 的优势在“贴近用户”处最为明显。 这并非说两者不能跨界,但那样会增加摩擦。
- 当你的需求从“灵活性”转向“效率”时,将关键库重写为 Rust 就变得更有价值。
尽管 Go 和 Rust 的设计理念差异显著,但它们的优势高度互补。当两者结合使用时,既能获得极大的灵活性,又能实现极致的性能。
建议
对大多数公司和用户而言,Go 是默认的首选语言。它性能强劲、易于采用,其高度模块化的特性特别适合需求不断演进的场景。
随着产品成熟、需求趋于稳定,若能从微小的性能提升中获得巨大收益(例如在核心基础设施或高频路径中),那么投资 Rust 以最大化性能就非常值得。